From b662de4efb4d360025a8096bf4f9de6586c33981 Mon Sep 17 00:00:00 2001 From: Reisen Date: Mon, 4 Oct 2021 11:52:11 +0000 Subject: [PATCH] terra: columbus-5 contracts by default Change-Id: I007689b032c6182e35421b47b56787a657a6919c --- terra/Cargo.lock | 1254 +++++++++++------ terra/Cargo.toml | 2 +- terra/Dockerfile | 2 +- terra/artifacts/cw20_base.wasm | Bin 238117 -> 266211 bytes terra/contracts-5/README.md | 3 - terra/contracts-5/cw20-wrapped/.cargo/config | 5 - terra/contracts-5/cw20-wrapped/Cargo.toml | 28 - .../contracts-5/cw20-wrapped/src/contract.rs | 374 ----- terra/contracts-5/cw20-wrapped/src/error.rs | 27 - terra/contracts-5/cw20-wrapped/src/lib.rs | 7 - terra/contracts-5/cw20-wrapped/src/msg.rs | 122 -- terra/contracts-5/cw20-wrapped/src/state.rs | 37 - .../cw20-wrapped/tests/integration.rs | 253 ---- terra/contracts-5/token-bridge/.cargo/config | 5 - terra/contracts-5/token-bridge/Cargo.toml | 36 - .../contracts-5/token-bridge/src/contract.rs | 733 ---------- terra/contracts-5/token-bridge/src/lib.rs | 10 - terra/contracts-5/token-bridge/src/msg.rs | 71 - terra/contracts-5/token-bridge/src/state.rs | 249 ---- .../token-bridge/tests/integration.rs | 114 -- terra/contracts-5/wormhole/.cargo/config | 5 - terra/contracts-5/wormhole/Cargo.toml | 33 - terra/contracts-5/wormhole/src/byte_utils.rs | 76 - terra/contracts-5/wormhole/src/contract.rs | 387 ----- terra/contracts-5/wormhole/src/error.rs | 113 -- terra/contracts-5/wormhole/src/lib.rs | 7 - terra/contracts-5/wormhole/src/msg.rs | 66 - terra/contracts-5/wormhole/src/state.rs | 444 ------ terra/contracts/cw20-wrapped/Cargo.toml | 14 +- terra/contracts/cw20-wrapped/src/contract.rs | 284 ++-- terra/contracts/cw20-wrapped/src/lib.rs | 6 +- terra/contracts/cw20-wrapped/src/msg.rs | 14 +- terra/contracts/cw20-wrapped/src/state.rs | 9 +- terra/contracts/token-bridge/Cargo.toml | 19 +- terra/contracts/token-bridge/src/contract.rs | 691 ++++----- terra/contracts/token-bridge/src/lib.rs | 4 - terra/contracts/token-bridge/src/msg.rs | 14 +- terra/contracts/token-bridge/src/state.rs | 56 +- terra/contracts/wormhole/Cargo.toml | 17 +- terra/contracts/wormhole/src/contract.rs | 247 ++-- terra/contracts/wormhole/src/error.rs | 1 - terra/contracts/wormhole/src/lib.rs | 3 - terra/contracts/wormhole/src/msg.rs | 12 +- terra/contracts/wormhole/src/state.rs | 69 +- terra/tools/deploy.js | 271 ++-- terra/tools/deploy.sh | 1 + terra/tools/migrate.js | 133 ++ 47 files changed, 1855 insertions(+), 4473 deletions(-) delete mode 100644 terra/contracts-5/README.md delete mode 100644 terra/contracts-5/cw20-wrapped/.cargo/config delete mode 100644 terra/contracts-5/cw20-wrapped/Cargo.toml delete mode 100644 terra/contracts-5/cw20-wrapped/src/contract.rs delete mode 100644 terra/contracts-5/cw20-wrapped/src/error.rs delete mode 100644 terra/contracts-5/cw20-wrapped/src/lib.rs delete mode 100644 terra/contracts-5/cw20-wrapped/src/msg.rs delete mode 100644 terra/contracts-5/cw20-wrapped/src/state.rs delete mode 100644 terra/contracts-5/cw20-wrapped/tests/integration.rs delete mode 100644 terra/contracts-5/token-bridge/.cargo/config delete mode 100644 terra/contracts-5/token-bridge/Cargo.toml delete mode 100644 terra/contracts-5/token-bridge/src/contract.rs delete mode 100644 terra/contracts-5/token-bridge/src/lib.rs delete mode 100644 terra/contracts-5/token-bridge/src/msg.rs delete mode 100644 terra/contracts-5/token-bridge/src/state.rs delete mode 100644 terra/contracts-5/token-bridge/tests/integration.rs delete mode 100644 terra/contracts-5/wormhole/.cargo/config delete mode 100644 terra/contracts-5/wormhole/Cargo.toml delete mode 100644 terra/contracts-5/wormhole/src/byte_utils.rs delete mode 100644 terra/contracts-5/wormhole/src/contract.rs delete mode 100644 terra/contracts-5/wormhole/src/error.rs delete mode 100644 terra/contracts-5/wormhole/src/lib.rs delete mode 100644 terra/contracts-5/wormhole/src/msg.rs delete mode 100644 terra/contracts-5/wormhole/src/state.rs create mode 100644 terra/tools/migrate.js diff --git a/terra/Cargo.lock b/terra/Cargo.lock index 0e7d6f6d..71c364d3 100644 --- a/terra/Cargo.lock +++ b/terra/Cargo.lock @@ -8,7 +8,7 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a" dependencies = [ - "gimli 0.24.0", + "gimli", ] [[package]] @@ -17,18 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "autocfg" version = "1.0.1" @@ -43,18 +31,18 @@ checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.24.0", "rustc-demangle", ] [[package]] name = "base64" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bigint" @@ -63,16 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" dependencies = [ "byteorder", - "crunchy", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", + "crunchy 0.1.6", ] [[package]] @@ -81,32 +60,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "bitvec" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98fcd36dda4e17b7d7abc64cb549bf0201f4ab71e00700c798ca7e62ed3761fa" -dependencies = [ - "funty", - "radium", - "wyz", -] - -[[package]] -name = "blake3" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if 0.1.10", - "constant_time_eq", - "crypto-mac 0.8.0", - "digest 0.9.0", -] - [[package]] name = "block-buffer" version = "0.9.0" @@ -114,7 +67,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding", - "generic-array 0.14.4", + "generic-array", ] [[package]] @@ -135,12 +88,6 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -148,38 +95,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cloudabi" -version = "0.0.3" +name = "clru" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +checksum = "591ff76ca0691bd91c1b0b5b987e5cf93b21ec810ad96665c5a569c60846dd93" + +[[package]] +name = "const-oid" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df" + +[[package]] +name = "cosmwasm-crypto" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec9bdd1f4da5fc0d085251b0322661c5aaf773ab299e3e205fb18130b7f6ba3" dependencies = [ - "bitflags", + "digest", + "ed25519-zebra", + "k256", + "rand_core 0.5.1", + "thiserror", ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "cosmwasm-derive" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "5ac17a14b4ab09a5d89b5301218067acca33d9311376e5c34c9877f09e562395" +dependencies = [ + "syn", +] [[package]] name = "cosmwasm-std" -version = "0.10.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85908a2696117c8f2c1b3ce201d34a1aa9a6b3c1583a65cfb794ec66e1cfde4" +checksum = "e47306c113f4d964c35a74a87ceb8ccfb5811e9810a9dc427101148b5b9134ca" dependencies = [ "base64", + "cosmwasm-crypto", + "cosmwasm-derive", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", + "uint", ] [[package]] name = "cosmwasm-storage" -version = "0.10.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e103531a2ce636e86b7639cec25d348c4d360832ab8e0e7f9a6e00f08aac1379" +checksum = "2e3472d8e0e7155c5f4d89674ad47adede4b1491ad14f4141610e1522028a6a7" dependencies = [ "cosmwasm-std", "serde", @@ -187,22 +156,23 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "0.10.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12d56a7ad7bbbf04b94a782f25fe50a9372067737f661931acf9d30668003efd" +checksum = "8d90f1d30e2d01d815c520dad2738f93188f2e64b3dda3e11609c13eb73109b8" dependencies = [ + "clru", + "cosmwasm-crypto", "cosmwasm-std", "hex", - "memmap", + "loupe", "parity-wasm", "schemars", "serde", "serde_json", "sha2", - "snafu", - "wasmer-clif-backend", - "wasmer-middleware-common", - "wasmer-runtime-core", + "thiserror", + "wasmer", + "wasmer-middlewares", ] [[package]] @@ -216,36 +186,35 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.59.0" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a9c21f8042b9857bda93f6c1910b9f9f24100187a3d3d52f214a34e3dc5818" +checksum = "c8ca3560686e7c9c7ed7e0fe77469f2410ba5d7781b1acaa9adc8d8deea28e3e" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.59.0" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7853f77a6e4a33c67a69c40f5e1bb982bd2dc5c4a22e17e67b65bbccf9b33b2e" +checksum = "baf9bf1ffffb6ce3d2e5ebc83549bd2436426c99b31cc550d521364cbe35d276" dependencies = [ - "byteorder", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.20.0", + "gimli", "log", + "regalloc", "smallvec", "target-lexicon", - "thiserror", ] [[package]] name = "cranelift-codegen-meta" -version = "0.59.0" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084cd6d5fb0d1da28acd72c199471bfb09acc703ec8f3bf07b1699584272a3b9" +checksum = "4cc21936a5a6d07e23849ffe83e5c1f6f50305c074f4b2970ca50c13bf55b821" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -253,34 +222,44 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.59.0" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701b599783305a58c25027a4d73f2d6b599b2d8ef3f26677275f480b4d51e05d" +checksum = "ca5b6ffaa87560bebe69a5446449da18090b126037920b0c1c6d5945f72faf6b" [[package]] name = "cranelift-entity" -version = "0.59.0" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88e792b28e1ebbc0187b72ba5ba880dad083abe9231a99d19604d10c9e73f38" +checksum = "7d6b4a8bef04f82e4296782646f733c641d09497df2fabf791323fefaa44c64c" [[package]] -name = "cranelift-native" -version = "0.59.0" +name = "cranelift-frontend" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32daf082da21c0c05d93394ff4842c2ab7c4991b1f3186a1d952f8ac660edd0b" +checksum = "c31b783b351f966fce33e3c03498cb116d16d97a8f9978164a60920bd0d3a99c" dependencies = [ "cranelift-codegen", - "raw-cpuid", + "log", + "smallvec", "target-lexicon", ] +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -290,7 +269,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] @@ -301,7 +280,7 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", "lazy_static", "memoffset", @@ -315,7 +294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "lazy_static", ] @@ -326,30 +305,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" [[package]] -name = "crypto-mac" -version = "0.8.0" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d12477e115c0d570c12a2dfd859f80b55b60ddb5075df210d3af06d133a69f45" dependencies = [ - "generic-array 0.14.4", + "generic-array", + "rand_core 0.6.3", "subtle", + "zeroize", ] [[package]] name = "crypto-mac" -version = "0.9.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.4", + "generic-array", "subtle", ] [[package]] -name = "cw0" -version = "0.2.3" +name = "curve25519-dalek" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b469bb5d63a036339cbb3042fb1254016b5d4889b15f420fea1c1339497b19de" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-storage-plus" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e867b9972b83b32e00e878dfbff48299ba26618dabeb19b9c56fae176dc225" dependencies = [ "cosmwasm-std", "schemars", @@ -357,22 +357,34 @@ dependencies = [ ] [[package]] -name = "cw2" -version = "0.2.3" +name = "cw0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a5dd960277ae9180e0077fcc8769928da2d9c1c42cc8c8c734c625c6f2a90b" +checksum = "c497f885a40918a02df7d938c81809965fa05cfc21b3dc591e9950237b5de0a9" dependencies = [ "cosmwasm-std", - "cosmwasm-storage", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d48454f96494aa1018556cd457977375cc8c57ef3e5c767cfa2ea5ec24b0258" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", "schemars", "serde", ] [[package]] name = "cw20" -version = "0.2.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd85ddcac2bf5c1899dd13e52985d260dae02db21a618d9d4c2c36dab63a915" +checksum = "a11a2adbd52258f5b4ed5323f62bc6e559f2cefbe52ef0e58290016fde5bb083" dependencies = [ "cosmwasm-std", "cw0", @@ -382,18 +394,36 @@ dependencies = [ [[package]] name = "cw20-base" -version = "0.2.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09657718df5243810b10d49359a352eed197966f578366624198fe1740fb52" +checksum = "fe3791e0f6b4a0a82b86541d48dcc67c2d607da8e5691a91b40b2c06ddf09c52" dependencies = [ "cosmwasm-std", - "cosmwasm-storage", + "cw-storage-plus", "cw0", "cw2", "cw20", "schemars", "serde", - "snafu", + "thiserror", +] + +[[package]] +name = "cw20-legacy" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d27b11827323369993519abb494f3ae9b6aac4d716d058bea5e181b9b0074b7" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw0", + "cw2", + "cw20", + "cw20-base", + "schemars", + "serde", + "thiserror", ] [[package]] @@ -403,20 +433,57 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", + "cw-storage-plus", + "cw2", "cw20", - "cw20-base", + "cw20-legacy", "schemars", "serde", "thiserror", ] [[package]] -name = "digest" -version = "0.8.1" +name = "darling" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" dependencies = [ - "generic-array 0.12.4", + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2adca118c71ecd9ae094d4b68257b3fdfcb711a612b9eec7b5a0d27a5a70a5b4" +dependencies = [ + "const-oid", ] [[package]] @@ -425,26 +492,67 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array", ] [[package]] -name = "doc-comment" -version = "0.3.3" +name = "dyn-clone" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + +[[package]] +name = "dynasm" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc2d9a5e44da60059bd38db2d05cbb478619541b8c79890547861ec1e3194f0" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dynasmrt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42276e3f205fe63887cca255aa9a65a63fb72764c30b9a6252a7c7e46994f689" +dependencies = [ + "byteorder", + "dynasm", + "memmap2", +] [[package]] name = "ecdsa" -version = "0.8.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87bf8bfb05ea8a6f74ddf48c7d1774851ba77bbe51ac984fdfa6c30310e1ff5f" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ + "der", "elliptic-curve", "hmac", "signature", ] +[[package]] +name = "ed25519-zebra" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.5.1", + "serde", + "sha2", + "thiserror", +] + [[package]] name = "either" version = "1.6.1" @@ -453,72 +561,62 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "elliptic-curve" -version = "0.6.6" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396db09c483e7fca5d4fdb9112685632b3e76c9a607a2649c1bf904404a01366" +checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" dependencies = [ - "bitvec", - "digest 0.9.0", + "crypto-bigint", "ff", - "generic-array 0.14.4", + "generic-array", "group", - "rand_core", + "pkcs8", + "rand_core 0.6.3", "subtle", "zeroize", ] [[package]] -name = "errno" -version = "0.2.7" +name = "enumset" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +checksum = "7e76129da36102af021b8e5000dab2c1c30dbef85c1e482beeff8da5dde0e0b0" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", + "enumset_derive", ] [[package]] -name = "errno-dragonfly" -version = "0.1.1" +name = "enumset_derive" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" dependencies = [ - "gcc", - "libc", + "darling", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "ff" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" dependencies = [ - "bitvec", - "rand_core", + "rand_core 0.6.3", "subtle", ] [[package]] -name = "funty" -version = "1.1.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" @@ -531,13 +629,25 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.20.0" +name = "getrandom" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "byteorder", - "indexmap", + "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", ] [[package]] @@ -545,15 +655,20 @@ name = "gimli" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] [[package]] name = "group" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.3", "subtle", ] @@ -580,14 +695,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hmac" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.9.1", - "digest 0.9.0", + "crypto-mac", + "digest", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "1.6.2" @@ -607,13 +728,14 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "k256" -version = "0.5.10" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3934640b1efbc660af5889d041854b6985d403771dc4d5fee984e13e8f82f313" +checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ecdsa", "elliptic-curve", + "sha2", ] [[package]] @@ -628,6 +750,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leb128" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" + [[package]] name = "libc" version = "0.2.95" @@ -635,12 +763,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" [[package]] -name = "lock_api" -version = "0.3.4" +name = "libloading" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ - "scopeguard", + "cfg-if", + "winapi", ] [[package]] @@ -649,17 +778,52 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] -name = "memmap" -version = "0.7.0" +name = "loupe" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" +dependencies = [ + "indexmap", + "loupe-derive", + "rustversion", +] + +[[package]] +name = "loupe-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memmap2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" dependencies = [ "libc", - "winapi", ] [[package]] @@ -682,17 +846,10 @@ dependencies = [ ] [[package]] -name = "nix" -version = "0.15.0" +name = "more-asserts" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" -dependencies = [ - "bitflags", - "cc", - "cfg-if 0.1.10", - "libc", - "void", -] +checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" [[package]] name = "num_cpus" @@ -710,50 +867,73 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" +[[package]] +name = "object" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7" +dependencies = [ + "crc32fast", + "indexmap", + "memchr", +] + [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "page_size" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] -name = "parking_lot" -version = "0.10.2" +name = "pin-project-lite" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" dependencies = [ - "lock_api", - "parking_lot_core", + "der", + "spki", ] [[package]] -name = "parking_lot_core" -version = "0.7.2" +name = "ppv-lite86" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] @@ -765,6 +945,26 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.9" @@ -775,26 +975,52 @@ dependencies = [ ] [[package]] -name = "radium" -version = "0.3.0" +name = "rand" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" +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 = "raw-cpuid" -version = "7.0.4" +name = "rand_core" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb71f708fe39b2c5e98076204c3cc094ee5a4c12c4cdb119a2b72dc34164f41" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "bitflags", - "cc", - "rustc_version", + "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", ] [[package]] @@ -824,9 +1050,67 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regalloc" +version = "0.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +dependencies = [ + "log", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rkyv" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb135b3e5e3311f0a254bfb00333f4bac9ef1d89888b84242a89eb8722b09a07" +dependencies = [ + "memoffset", + "ptr_meta", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8f489f6b6d8551bb15904293c1ad58a6abafa7d8390d15f7ed05a2afcd87d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "rustc-demangle" @@ -835,13 +1119,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" [[package]] -name = "rustc_version" -version = "0.2.3" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" [[package]] name = "ryu" @@ -851,10 +1138,11 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schemars" -version = "0.7.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61" +checksum = "d7a48d098c2a7fdf5740b19deb1181b4fb8a9e68e03ae517c14cde04b5725409" dependencies = [ + "dyn-clone", "schemars_derive", "serde", "serde_json", @@ -862,9 +1150,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.7.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d" +checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8" dependencies = [ "proc-macro2", "quote", @@ -879,19 +1167,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "semver" -version = "0.9.0" +name = "seahash" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" @@ -902,21 +1181,11 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-bench" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d733da87e79faaac25616e33d26299a41143fd4cd42746cbb0e91d8feea243fd" -dependencies = [ - "byteorder", - "serde", -] - [[package]] name = "serde-json-wasm" -version = "0.2.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120bad73306616e91acd7ceed522ba96032a51cffeef3cc813de7f367df71e37" +checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449" dependencies = [ "serde", ] @@ -970,9 +1239,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" dependencies = [ "block-buffer", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", - "digest 0.9.0", + "digest", "opaque-debug", ] @@ -983,19 +1252,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" dependencies = [ "block-buffer", - "digest 0.9.0", + "digest", "keccak", "opaque-debug", ] [[package]] name = "signature" -version = "1.2.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210" +checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335" dependencies = [ - "digest 0.9.0", - "rand_core", + "digest", + "rand_core 0.6.3", ] [[package]] @@ -1005,26 +1274,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] -name = "snafu" -version = "0.6.10" +name = "spki" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", + "der", ] [[package]] -name = "snafu-derive" -version = "0.6.10" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +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" @@ -1045,15 +1319,29 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.10.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" +checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] [[package]] name = "terra-cosmwasm" -version = "1.2.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7275aacd385e4f41647634c35692b1982085917b4dcfc1fdfa3984ee4ce45d" +checksum = "552f18cba2b535d1f8c0e3b3f37696820b954bc7535d2e33909f2a6342302718" dependencies = [ "cosmwasm-std", "schemars", @@ -1062,9 +1350,9 @@ dependencies = [ [[package]] name = "terraswap" -version = "1.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02334ec5ad280fcc09c86467d40383ea1e4d977103345e53a4f03006c4be51c2" +checksum = "96f2c2a6371e9ddf2c942368e64645cc3e8fc2855da70c8c6bed238dcdd5522f" dependencies = [ "cosmwasm-std", "cosmwasm-storage", @@ -1105,7 +1393,7 @@ dependencies = [ "cw20", "cw20-base", "cw20-wrapped", - "generic-array 0.14.4", + "generic-array", "hex", "k256", "lazy_static", @@ -1118,12 +1406,56 @@ dependencies = [ "wormhole", ] +[[package]] +name = "tracing" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" +dependencies = [ + "lazy_static", +] + [[package]] name = "typenum" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "uint" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +dependencies = [ + "byteorder", + "crunchy 0.2.2", + "hex", + "static_assertions", +] + [[package]] name = "unicode-xid" version = "0.2.2" @@ -1137,118 +1469,247 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] -name = "void" -version = "1.0.2" +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] -name = "wasmer-clif-backend" -version = "0.17.0" +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691ea323652d540a10722066dbf049936f4367bb22a96f8992a262a942a8b11b" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasmer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f52e455a01d0fac439cd7a96ba9b519bdc84e923a5b96034054697ebb17cd75" +dependencies = [ + "cfg-if", + "indexmap", + "loupe", + "more-asserts", + "target-lexicon", + "thiserror", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-compiler-singlepass", + "wasmer-derive", + "wasmer-engine", + "wasmer-engine-dylib", + "wasmer-engine-universal", + "wasmer-types", + "wasmer-vm", + "winapi", +] + +[[package]] +name = "wasmer-compiler" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc86dda6f715f03104800be575a38382b35c3962953af9e9d8722dcf0bd2458f" +dependencies = [ + "enumset", + "loupe", + "rkyv", + "serde", + "serde_bytes", + "smallvec", + "target-lexicon", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser", +] + +[[package]] +name = "wasmer-compiler-cranelift" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a570746cbec434179e2d53357973a34dfdb208043104e8fac3b7b0023015cf6" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "gimli", + "loupe", + "more-asserts", + "rayon", + "smallvec", + "tracing", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-compiler-singlepass" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9429b9f7708c582d855b1787f09c7029ff23fb692550d4a1cc351c8ea84c3014" dependencies = [ "byteorder", - "cranelift-codegen", - "cranelift-entity", - "cranelift-native", - "libc", - "nix", - "rayon", - "serde", - "serde-bench", - "serde_bytes", - "serde_derive", - "target-lexicon", - "wasmer-clif-fork-frontend", - "wasmer-clif-fork-wasm", - "wasmer-runtime-core", - "wasmer-win-exception-handler", - "wasmparser", - "winapi", -] - -[[package]] -name = "wasmer-clif-fork-frontend" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c23f2824f354a00a77e4b040eef6e1d4c595a8a3e9013bad65199cc8dade9a5a" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "wasmer-clif-fork-wasm" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35e21d3aebc51cc6ebc0e830cf8458a9891c3482fb3c65ad18d408102929ae5" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "log", - "thiserror", - "wasmer-clif-fork-frontend", - "wasmparser", -] - -[[package]] -name = "wasmer-middleware-common" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd94068186b25fbe5213442648ffe0fa65ee77389bed020404486fd22056cc87" -dependencies = [ - "wasmer-runtime-core", -] - -[[package]] -name = "wasmer-runtime-core" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45d4253f097502423d8b19d54cb18745f61b984b9dbce32424cba7945cfef367" -dependencies = [ - "bincode", - "blake3", - "cc", - "digest 0.8.1", - "errno", - "hex", - "indexmap", + "dynasm", + "dynasmrt", "lazy_static", - "libc", - "nix", - "page_size", - "parking_lot", - "rustc_version", - "serde", - "serde-bench", - "serde_bytes", - "serde_derive", + "loupe", + "more-asserts", + "rayon", "smallvec", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee7b351bcc1e782997c72dc0b5b328f3ddcad4813b8ce3cac3f25ae5a4ab56b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmer-engine" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8454ead320a4017ba36ddd9ab4fbf7776fceea6ab0b79b5e53664a1682569fc3" +dependencies = [ + "backtrace", + "lazy_static", + "loupe", + "memmap2", + "more-asserts", + "rustc-demangle", + "serde", + "serde_bytes", "target-lexicon", - "wasmparser", + "thiserror", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-engine-dylib" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa390d123ebe23d5315c39f6063fcc18319661d03c8000f23d0fe1c011e8135" +dependencies = [ + "cfg-if", + "leb128", + "libloading", + "loupe", + "rkyv", + "serde", + "tempfile", + "tracing", + "wasmer-compiler", + "wasmer-engine", + "wasmer-object", + "wasmer-types", + "wasmer-vm", + "which", +] + +[[package]] +name = "wasmer-engine-universal" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dffe8015f08915eb4939ebc8e521cde8246f272f5197ea60d46214ac5aef285" +dependencies = [ + "cfg-if", + "leb128", + "loupe", + "region", + "rkyv", + "wasmer-compiler", + "wasmer-engine", + "wasmer-types", + "wasmer-vm", "winapi", ] [[package]] -name = "wasmer-win-exception-handler" -version = "0.17.0" +name = "wasmer-middlewares" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf22ce6dc66d893099aac853d451bf9443fa8f5443f5bf4fc63f3aebd7b592b1" +checksum = "95d2b4722d64c850893f7a7eab3ab76181efbafcd366827801d8bcd64bff525f" dependencies = [ + "loupe", + "wasmer", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-object" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c541c985799fc1444702501c15d41becfb066c92d9673defc1c7417fd8739e15" +dependencies = [ + "object 0.25.3", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91f75d3c31f8b1f8d818ff49624fc974220243cbc07a2252f408192e97c6b51" +dependencies = [ + "indexmap", + "loupe", + "rkyv", + "serde", + "thiserror", +] + +[[package]] +name = "wasmer-vm" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469a12346a4831e7dac639b9646d8c9b24c7d2cf0cf458b77f489edb35060c1f" +dependencies = [ + "backtrace", "cc", + "cfg-if", + "indexmap", "libc", - "wasmer-runtime-core", + "loupe", + "memoffset", + "more-asserts", + "region", + "rkyv", + "serde", + "thiserror", + "wasmer-types", "winapi", ] [[package]] name = "wasmparser" -version = "0.51.4" +version = "0.78.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" +checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" + +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", +] [[package]] name = "winapi" @@ -1282,7 +1743,8 @@ dependencies = [ "cw20", "cw20-base", "cw20-wrapped", - "generic-array 0.14.4", + "generic-array", + "getrandom 0.2.3", "hex", "k256", "lazy_static", @@ -1293,12 +1755,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - [[package]] name = "zeroize" version = "1.3.0" diff --git a/terra/Cargo.toml b/terra/Cargo.toml index 924e0591..97a55deb 100644 --- a/terra/Cargo.toml +++ b/terra/Cargo.toml @@ -10,4 +10,4 @@ debug-assertions = false codegen-units = 1 panic = 'abort' incremental = false -overflow-checks = true \ No newline at end of file +overflow-checks = true diff --git a/terra/Dockerfile b/terra/Dockerfile index ab42d0fc..7a2d4704 100644 --- a/terra/Dockerfile +++ b/terra/Dockerfile @@ -1,6 +1,6 @@ # This is a multi-stage docker file, first stage builds contracts # And the second one creates node.js environment to deploy them -FROM cosmwasm/workspace-optimizer:0.10.4@sha256:a976db4ee7add887a6af26724b804bbd9e9d534554506447e72ac57e65357db9 AS builder +FROM cosmwasm/workspace-optimizer:0.12.1@sha256:1508cf7545f4b656ecafa34e29c1acf200cdab47fced85c2bc076c0c158b1338 AS builder ADD Cargo.lock /code/ ADD Cargo.toml /code/ ADD contracts /code/contracts diff --git a/terra/artifacts/cw20_base.wasm b/terra/artifacts/cw20_base.wasm index ed4cd3fdcaf6b321536eadcdaf5e282a4fc9e768..a28e3f3a2da66843b09a2903c91a666e1b07e910 100644 GIT binary patch literal 266211 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

RFp;1PP6o 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 R$sKYR3O}hO;I@^g{|kjZop}HN literal 238117 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

W2|2WmfxqUj21UdUCcrjJapBMvb4zwtUemI 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 diff --git a/terra/contracts-5/README.md b/terra/contracts-5/README.md deleted file mode 100644 index 27ec9c8e..00000000 --- a/terra/contracts-5/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Terra Wormhole Contracts - -The Wormhole Terra integration is developed and maintained by Everstake / @ysavchenko. diff --git a/terra/contracts-5/cw20-wrapped/.cargo/config b/terra/contracts-5/cw20-wrapped/.cargo/config deleted file mode 100644 index 2d5cce4e..00000000 --- a/terra/contracts-5/cw20-wrapped/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" \ No newline at end of file diff --git a/terra/contracts-5/cw20-wrapped/Cargo.toml b/terra/contracts-5/cw20-wrapped/Cargo.toml deleted file mode 100644 index f35f2042..00000000 --- a/terra/contracts-5/cw20-wrapped/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "cw20-wrapped" -version = "0.1.0" -authors = ["Yuriy Savchenko "] -edition = "2018" -description = "Wrapped CW20 token contract" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -cosmwasm-std = { version = "0.16.0" } -cosmwasm-storage = { version = "0.16.0" } -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw2 = { version = "0.8.0" } -cw20 = { version = "0.8.0" } -cw20-legacy = { version = "0.2.0", features = ["library"]} -cw-storage-plus = { version = "0.8.0" } -thiserror = { version = "1.0.20" } - -[dev-dependencies] -cosmwasm-vm = { version = "0.16.0", default-features = false } diff --git a/terra/contracts-5/cw20-wrapped/src/contract.rs b/terra/contracts-5/cw20-wrapped/src/contract.rs deleted file mode 100644 index 528af955..00000000 --- a/terra/contracts-5/cw20-wrapped/src/contract.rs +++ /dev/null @@ -1,374 +0,0 @@ -use cosmwasm_std::{ - entry_point, - to_binary, - Binary, - CosmosMsg, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - WasmMsg, -}; - -use cw2::set_contract_version; -use cw20_legacy::{ - allowances::{ - execute_burn_from, - execute_decrease_allowance, - execute_increase_allowance, - execute_send_from, - execute_transfer_from, - query_allowance, - }, - contract::{ - execute_mint, - execute_send, - execute_transfer, - query_balance, - }, - state::{ - MinterData, - TokenInfo, - TOKEN_INFO, - }, - ContractError, -}; - -use crate::{ - msg::{ - ExecuteMsg, - InstantiateMsg, - QueryMsg, - WrappedAssetInfoResponse, - }, - state::{ - wrapped_asset_info, - wrapped_asset_info_read, - WrappedAssetInfo, - }, -}; -use cw20::TokenInfoResponse; -use std::string::String; - -type HumanAddr = String; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:cw20-base"; -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, -) -> StdResult { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - // store token info using cw20-base format - let data = TokenInfo { - name: msg.name, - symbol: msg.symbol, - decimals: msg.decimals, - total_supply: Uint128::new(0), - // set creator as minter - mint: Some(MinterData { - minter: deps.api.addr_canonicalize(&info.sender.as_str())?, - cap: None, - }), - }; - TOKEN_INFO.save(deps.storage, &data)?; - - // save wrapped asset info - let data = WrappedAssetInfo { - asset_chain: msg.asset_chain, - asset_address: msg.asset_address, - bridge: deps.api.addr_canonicalize(&info.sender.as_str())?, - }; - wrapped_asset_info(deps.storage).save(&data)?; - - if let Some(mint_info) = msg.mint { - execute_mint(deps, env, info, mint_info.recipient, mint_info.amount) - .map_err(|e| StdError::generic_err(format!("{}", e)))?; - } - - if let Some(hook) = msg.init_hook { - Ok( - Response::new().add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: hook.contract_addr, - msg: hook.msg, - funds: vec![], - })), - ) - } else { - Ok(Response::default()) - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - // these all come from cw20-base to implement the cw20 standard - ExecuteMsg::Transfer { recipient, amount } => { - Ok(execute_transfer(deps, env, info, recipient, amount)?) - } - ExecuteMsg::Burn { account, amount } => { - Ok(execute_burn_from(deps, env, info, account, amount)?) - } - ExecuteMsg::Send { - contract, - amount, - msg, - } => Ok(execute_send(deps, env, info, contract, amount, msg)?), - ExecuteMsg::Mint { recipient, amount } => { - execute_mint_wrapped(deps, env, info, recipient, amount) - } - ExecuteMsg::IncreaseAllowance { - spender, - amount, - expires, - } => Ok(execute_increase_allowance( - deps, env, info, spender, amount, expires, - )?), - ExecuteMsg::DecreaseAllowance { - spender, - amount, - expires, - } => Ok(execute_decrease_allowance( - deps, env, info, spender, amount, expires, - )?), - ExecuteMsg::TransferFrom { - owner, - recipient, - amount, - } => Ok(execute_transfer_from( - deps, env, info, owner, recipient, amount, - )?), - ExecuteMsg::BurnFrom { owner, amount } => { - Ok(execute_burn_from(deps, env, info, owner, amount)?) - } - ExecuteMsg::SendFrom { - owner, - contract, - amount, - msg, - } => Ok(execute_send_from( - deps, env, info, owner, contract, amount, msg, - )?), - } -} - -fn execute_mint_wrapped( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: HumanAddr, - amount: Uint128, -) -> Result { - // Only bridge can mint - let wrapped_info = wrapped_asset_info_read(deps.storage).load()?; - if wrapped_info.bridge != deps.api.addr_canonicalize(&info.sender.as_str())? { - return Err(ContractError::Unauthorized {}); - } - - Ok(execute_mint(deps, env, info, recipient, amount)?) -} - -pub fn query(deps: Deps, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::WrappedAssetInfo {} => to_binary(&query_wrapped_asset_info(deps)?), - // inherited from cw20-base - QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?), - QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?), - QueryMsg::Allowance { owner, spender } => { - to_binary(&query_allowance(deps, owner, spender)?) - } - } -} - -pub fn query_token_info(deps: Deps) -> StdResult { - let info = TOKEN_INFO.load(deps.storage)?; - Ok(TokenInfoResponse { - name: String::from("Wormhole:") + info.name.as_str(), - symbol: String::from("wh") + info.symbol.as_str(), - decimals: info.decimals, - total_supply: info.total_supply, - }) -} - -pub fn query_wrapped_asset_info(deps: Deps) -> StdResult { - let info = wrapped_asset_info_read(deps.storage).load()?; - Ok(WrappedAssetInfoResponse { - asset_chain: info.asset_chain, - asset_address: info.asset_address, - bridge: deps.api.addr_humanize(&info.bridge)?, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{ - mock_dependencies, - mock_env, - mock_info, - }; - use cw20::TokenInfoResponse; - - const CANONICAL_LENGTH: usize = 20; - - fn get_balance(deps: Deps, address: HumanAddr) -> Uint128 { - query_balance(deps, address.into()).unwrap().balance - } - - fn do_init(mut deps: DepsMut, creator: &HumanAddr) { - let init_msg = InstantiateMsg { - name: "Integers".to_string(), - symbol: "INT".to_string(), - asset_chain: 1, - asset_address: vec![1; 32].into(), - decimals: 10, - mint: None, - init_hook: None, - }; - let env = mock_env(); - let info = mock_info(creator, &[]); - let res = instantiate(deps, env, info, init_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - assert_eq!( - query_token_info(deps.as_ref()).unwrap(), - TokenInfoResponse { - name: "Wormhole Wrapped".to_string(), - symbol: "WWT".to_string(), - decimals: 10, - total_supply: Uint128::from(0u128), - } - ); - - assert_eq!( - query_wrapped_asset_info(deps.as_ref()).unwrap(), - WrappedAssetInfoResponse { - asset_chain: 1, - asset_address: vec![1; 32].into(), - bridge: deps.api.addr_validate(creator).unwrap(), - } - ); - } - - fn do_init_and_mint( - mut deps: DepsMut, - creator: &HumanAddr, - mint_to: &HumanAddr, - amount: Uint128, - ) { - do_init(deps, creator); - - let msg = ExecuteMsg::Mint { - recipient: mint_to.clone(), - amount, - }; - - let env = mock_env(); - let info = mock_info(creator, &[]); - let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap(); - assert_eq!(0, res.messages.len()); - assert_eq!(get_balance(deps.as_ref(), mint_to.clone(),), amount); - - assert_eq!( - query_token_info(deps.as_ref()).unwrap(), - TokenInfoResponse { - name: "Wormhole Wrapped".to_string(), - symbol: "WWT".to_string(), - decimals: 10, - total_supply: amount, - } - ); - } - - #[test] - fn can_mint_by_minter() { - let mut deps = mock_dependencies(&[]); - let minter = HumanAddr::from("minter"); - let recipient = HumanAddr::from("recipient"); - let amount = Uint128::new(222_222_222); - do_init_and_mint(deps.as_mut(), &minter, &recipient, amount); - } - - #[test] - fn others_cannot_mint() { - let mut deps = mock_dependencies(&[]); - let minter = HumanAddr::from("minter"); - let recipient = HumanAddr::from("recipient"); - do_init(deps.as_mut(), &minter); - - let amount = Uint128::new(222_222_222); - let msg = ExecuteMsg::Mint { - recipient: recipient.clone(), - amount, - }; - - let other_address = HumanAddr::from("other"); - let env = mock_env(); - let info = mock_info(&other_address, &[]); - let res = execute(deps.as_mut(), env, info, msg); - assert_eq!( - format!("{}", res.unwrap_err()), - format!("{}", crate::error::ContractError::Unauthorized {}) - ); - } - - #[test] - fn transfer_balance_success() { - let mut deps = mock_dependencies(&[]); - let minter = HumanAddr::from("minter"); - let owner = HumanAddr::from("owner"); - let amount_initial = Uint128::new(222_222_222); - do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial); - - // Transfer - let recipient = HumanAddr::from("recipient"); - let amount_transfer = Uint128::new(222_222); - let msg = ExecuteMsg::Transfer { - recipient: recipient.clone(), - amount: amount_transfer, - }; - - let env = mock_env(); - let info = mock_info(&owner, &[]); - let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap(); - assert_eq!(0, res.messages.len()); - assert_eq!(get_balance(deps.as_ref(), owner), Uint128::new(222_000_000)); - assert_eq!(get_balance(deps.as_ref(), recipient), amount_transfer); - } - - #[test] - fn transfer_balance_not_enough() { - let mut deps = mock_dependencies(&[]); - let minter = HumanAddr::from("minter"); - let owner = HumanAddr::from("owner"); - let amount_initial = Uint128::new(222_221); - do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial); - - // Transfer - let recipient = HumanAddr::from("recipient"); - let amount_transfer = Uint128::new(222_222); - let msg = ExecuteMsg::Transfer { - recipient: recipient.clone(), - amount: amount_transfer, - }; - - let env = mock_env(); - let info = mock_info(&owner, &[]); - let _ = execute(deps.as_mut(), env, info, msg.clone()).unwrap_err(); // Will panic if no error - } -} diff --git a/terra/contracts-5/cw20-wrapped/src/error.rs b/terra/contracts-5/cw20-wrapped/src/error.rs deleted file mode 100644 index 95eba93c..00000000 --- a/terra/contracts-5/cw20-wrapped/src/error.rs +++ /dev/null @@ -1,27 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - // CW20 errors - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Cannot set to own account")] - CannotSetOwnAccount {}, - - #[error("Invalid zero amount")] - InvalidZeroAmount {}, - - #[error("Allowance is expired")] - Expired {}, - - #[error("No allowance for this account")] - NoAllowance {}, - - #[error("Minting cannot exceed the cap")] - CannotExceedCap {}, -} diff --git a/terra/contracts-5/cw20-wrapped/src/lib.rs b/terra/contracts-5/cw20-wrapped/src/lib.rs deleted file mode 100644 index 6e2ebd88..00000000 --- a/terra/contracts-5/cw20-wrapped/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod error; - -pub mod contract; -pub mod msg; -pub mod state; - -pub use crate::error::ContractError; diff --git a/terra/contracts-5/cw20-wrapped/src/msg.rs b/terra/contracts-5/cw20-wrapped/src/msg.rs deleted file mode 100644 index 25ca6377..00000000 --- a/terra/contracts-5/cw20-wrapped/src/msg.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![allow(clippy::field_reassign_with_default)] -use schemars::JsonSchema; -use serde::{ - Deserialize, - Serialize, -}; - -use cosmwasm_std::{ - Addr, - Binary, - Uint128, -}; -use cw20::Expiration; - -type HumanAddr = String; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - pub name: String, - pub symbol: String, - pub asset_chain: u16, - pub asset_address: Binary, - pub decimals: u8, - pub mint: Option, - pub init_hook: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitHook { - pub msg: Binary, - pub contract_addr: HumanAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMint { - pub recipient: HumanAddr, - pub amount: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - /// Implements CW20. Transfer is a base message to move tokens to another account without triggering actions - Transfer { - recipient: HumanAddr, - amount: Uint128, - }, - /// Slightly different than CW20. Burn is a base message to destroy tokens forever - Burn { account: HumanAddr, amount: Uint128 }, - /// Implements CW20. Send is a base message to transfer tokens to a contract and trigger an action - /// on the receiving contract. - Send { - contract: HumanAddr, - amount: Uint128, - msg: Binary, - }, - /// Implements CW20 "mintable" extension. If authorized, creates amount new tokens - /// and adds to the recipient balance. - Mint { - recipient: HumanAddr, - amount: Uint128, - }, - /// Implements CW20 "approval" extension. Allows spender to access an additional amount tokens - /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance - /// expiration with this one. - IncreaseAllowance { - spender: HumanAddr, - amount: Uint128, - expires: Option, - }, - /// Implements CW20 "approval" extension. Lowers the spender's access of tokens - /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current - /// allowance expiration with this one. - DecreaseAllowance { - spender: HumanAddr, - amount: Uint128, - expires: Option, - }, - /// Implements CW20 "approval" extension. Transfers amount tokens from owner -> recipient - /// if `env.sender` has sufficient pre-approval. - TransferFrom { - owner: HumanAddr, - recipient: HumanAddr, - amount: Uint128, - }, - /// Implements CW20 "approval" extension. Sends amount tokens from owner -> contract - /// if `env.sender` has sufficient pre-approval. - SendFrom { - owner: HumanAddr, - contract: HumanAddr, - amount: Uint128, - msg: Binary, - }, - /// Implements CW20 "approval" extension. Destroys tokens forever - BurnFrom { owner: HumanAddr, amount: Uint128 }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - // Generic information about the wrapped asset - WrappedAssetInfo {}, - /// Implements CW20. Returns the current balance of the given address, 0 if unset. - Balance { - address: HumanAddr, - }, - /// Implements CW20. Returns metadata on the contract - name, decimals, supply, etc. - TokenInfo {}, - /// Implements CW20 "allowance" extension. - /// Returns how much spender can use from owner account, 0 if unset. - Allowance { - owner: HumanAddr, - spender: HumanAddr, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct WrappedAssetInfoResponse { - pub asset_chain: u16, // Asset chain id - pub asset_address: Binary, // Asset smart contract address in the original chain - pub bridge: Addr, // Bridge address, authorized to mint and burn wrapped tokens -} diff --git a/terra/contracts-5/cw20-wrapped/src/state.rs b/terra/contracts-5/cw20-wrapped/src/state.rs deleted file mode 100644 index 2d9ba10f..00000000 --- a/terra/contracts-5/cw20-wrapped/src/state.rs +++ /dev/null @@ -1,37 +0,0 @@ -use schemars::JsonSchema; -use serde::{ - Deserialize, - Serialize, -}; - -use cosmwasm_std::{ - Binary, - CanonicalAddr, - Storage, -}; -use cosmwasm_storage::{ - singleton, - singleton_read, - ReadonlySingleton, - Singleton, -}; - -pub const KEY_WRAPPED_ASSET: &[u8] = b"wrappedAsset"; - -// Created at initialization and reference original asset and bridge address -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct WrappedAssetInfo { - pub asset_chain: u16, // Asset chain id - pub asset_address: Binary, // Asset smart contract address on the original chain - pub bridge: CanonicalAddr, // Bridge address, authorized to mint and burn wrapped tokens -} - -pub fn wrapped_asset_info(storage: &mut dyn Storage) -> Singleton { - singleton(storage, KEY_WRAPPED_ASSET) -} - -pub fn wrapped_asset_info_read( - storage: &dyn Storage, -) -> ReadonlySingleton { - singleton_read(storage, KEY_WRAPPED_ASSET) -} diff --git a/terra/contracts-5/cw20-wrapped/tests/integration.rs b/terra/contracts-5/cw20-wrapped/tests/integration.rs deleted file mode 100644 index 141cbea5..00000000 --- a/terra/contracts-5/cw20-wrapped/tests/integration.rs +++ /dev/null @@ -1,253 +0,0 @@ -static WASM: &[u8] = - include_bytes!("../../../target/wasm32-unknown-unknown/release/cw20_wrapped.wasm"); - -use cosmwasm_std::{ - from_slice, - Binary, - Env, - HandleResponse, - HandleResult, - HumanAddr, - InitResponse, - Uint128, -}; -use cosmwasm_storage::to_length_prefixed; -use cosmwasm_vm::{ - testing::{ - handle, - init, - mock_env, - mock_instance, - query, - MockApi, - MockQuerier, - MockStorage, - }, - Api, - Instance, - Storage, -}; -use cw20_wrapped::{ - msg::{ - HandleMsg, - InitMsg, - QueryMsg, - }, - state::{ - WrappedAssetInfo, - KEY_WRAPPED_ASSET, - }, - ContractError, -}; - -enum TestAddress { - INITIALIZER, - RECIPIENT, - SENDER, -} - -impl TestAddress { - fn value(&self) -> HumanAddr { - match self { - TestAddress::INITIALIZER => HumanAddr::from("addr0000"), - TestAddress::RECIPIENT => HumanAddr::from("addr2222"), - TestAddress::SENDER => HumanAddr::from("addr3333"), - } - } -} - -fn mock_env_height(signer: &HumanAddr, height: u64, time: u64) -> Env { - let mut env = mock_env(signer, &[]); - env.block.height = height; - env.block.time = time; - env -} - -fn get_wrapped_asset_info(storage: &S) -> WrappedAssetInfo { - let key = to_length_prefixed(KEY_WRAPPED_ASSET); - let data = storage - .get(&key) - .0 - .expect("error getting data") - .expect("data should exist"); - from_slice(&data).expect("invalid data") -} - -fn do_init(height: u64) -> Instance { - let mut deps = mock_instance(WASM, &[]); - let init_msg = InitMsg { - asset_chain: 1, - asset_address: vec![1; 32].into(), - decimals: 10, - mint: None, - init_hook: None, - }; - let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0); - let res: InitResponse = init(&mut deps, env, init_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // query the store directly - let api = deps.api; - deps.with_storage(|storage| { - assert_eq!( - get_wrapped_asset_info(storage), - WrappedAssetInfo { - asset_chain: 1, - asset_address: vec![1; 32].into(), - bridge: api.canonical_address(&TestAddress::INITIALIZER.value()).0?, - } - ); - Ok(()) - }) - .unwrap(); - deps -} - -fn do_mint( - deps: &mut Instance, - height: u64, - recipient: &HumanAddr, - amount: &Uint128, -) { - let mint_msg = HandleMsg::Mint { - recipient: recipient.clone(), - amount: amount.clone(), - }; - let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0); - let handle_response: HandleResponse = handle(deps, env, mint_msg).unwrap(); - assert_eq!(0, handle_response.messages.len()); -} - -fn do_transfer( - deps: &mut Instance, - height: u64, - sender: &HumanAddr, - recipient: &HumanAddr, - amount: &Uint128, -) { - let transfer_msg = HandleMsg::Transfer { - recipient: recipient.clone(), - amount: amount.clone(), - }; - let env = mock_env_height(sender, height, 0); - let handle_response: HandleResponse = handle(deps, env, transfer_msg).unwrap(); - assert_eq!(0, handle_response.messages.len()); -} - -fn check_balance( - deps: &mut Instance, - address: &HumanAddr, - amount: &Uint128, -) { - let query_response = query( - deps, - QueryMsg::Balance { - address: address.clone(), - }, - ) - .unwrap(); - assert_eq!( - query_response.as_slice(), - format!("{{\"balance\":\"{}\"}}", amount.u128()).as_bytes() - ); -} - -fn check_token_details(deps: &mut Instance, supply: &Uint128) { - let query_response = query(deps, QueryMsg::TokenInfo {}).unwrap(); - assert_eq!( - query_response.as_slice(), - format!( - "{{\"name\":\"Wormhole Wrapped\",\ - \"symbol\":\"WWT\",\ - \"decimals\":10,\ - \"total_supply\":\"{}\"}}", - supply.u128() - ) - .as_bytes() - ); -} - -#[test] -fn init_works() { - let mut deps = do_init(111); - check_token_details(&mut deps, &Uint128(0)); -} - -#[test] -fn query_works() { - let mut deps = do_init(111); - - let query_response = query(&mut deps, QueryMsg::WrappedAssetInfo {}).unwrap(); - assert_eq!( - query_response.as_slice(), - format!( - "{{\"asset_chain\":1,\ - \"asset_address\":\"{}\",\ - \"bridge\":\"{}\"}}", - Binary::from(vec![1; 32]).to_base64(), - TestAddress::INITIALIZER.value().as_str() - ) - .as_bytes() - ); -} - -#[test] -fn mint_works() { - let mut deps = do_init(111); - - do_mint( - &mut deps, - 112, - &TestAddress::RECIPIENT.value(), - &Uint128(123_123_123), - ); - - check_balance( - &mut deps, - &TestAddress::RECIPIENT.value(), - &Uint128(123_123_123), - ); - check_token_details(&mut deps, &Uint128(123_123_123)); -} - -#[test] -fn others_cannot_mint() { - let mut deps = do_init(111); - - let mint_msg = HandleMsg::Mint { - recipient: TestAddress::RECIPIENT.value(), - amount: Uint128(123_123_123), - }; - let env = mock_env_height(&TestAddress::RECIPIENT.value(), 112, 0); - let handle_result: HandleResult = handle(&mut deps, env, mint_msg); - assert_eq!( - format!("{}", handle_result.unwrap_err()), - format!("{}", ContractError::Unauthorized {}) - ); -} - -#[test] -fn transfer_works() { - let mut deps = do_init(111); - - do_mint( - &mut deps, - 112, - &TestAddress::SENDER.value(), - &Uint128(123_123_123), - ); - do_transfer( - &mut deps, - 113, - &TestAddress::SENDER.value(), - &TestAddress::RECIPIENT.value(), - &Uint128(123_123_000), - ); - - check_balance(&mut deps, &TestAddress::SENDER.value(), &Uint128(123)); - check_balance( - &mut deps, - &TestAddress::RECIPIENT.value(), - &Uint128(123_123_000), - ); -} diff --git a/terra/contracts-5/token-bridge/.cargo/config b/terra/contracts-5/token-bridge/.cargo/config deleted file mode 100644 index 2d5cce4e..00000000 --- a/terra/contracts-5/token-bridge/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" \ No newline at end of file diff --git a/terra/contracts-5/token-bridge/Cargo.toml b/terra/contracts-5/token-bridge/Cargo.toml deleted file mode 100644 index 7465bd1d..00000000 --- a/terra/contracts-5/token-bridge/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "token-bridge" -version = "0.1.0" -authors = ["Yuriy Savchenko "] -edition = "2018" -description = "Wormhole token bridge" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -cosmwasm-std = { version = "0.16.0" } -cosmwasm-storage = { version = "0.16.0" } -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw20 = "0.8.0" -cw20-base = { version = "0.8.0", features = ["library"] } -cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] } -terraswap = "2.4.0" -wormhole = { path = "../wormhole", features = ["library"] } -thiserror = { version = "1.0.20" } -k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] } -sha3 = { version = "0.9.1", default-features = false } -generic-array = { version = "0.14.4" } -hex = "0.4.2" -lazy_static = "1.4.0" -bigint = "4" - -[dev-dependencies] -cosmwasm-vm = { version = "0.16.0", default-features = false } -serde_json = "1.0" diff --git a/terra/contracts-5/token-bridge/src/contract.rs b/terra/contracts-5/token-bridge/src/contract.rs deleted file mode 100644 index d682ec3d..00000000 --- a/terra/contracts-5/token-bridge/src/contract.rs +++ /dev/null @@ -1,733 +0,0 @@ -use crate::msg::WrappedRegistryResponse; -use cosmwasm_std::{ - entry_point, - to_binary, - Binary, - CanonicalAddr, - Coin, - CosmosMsg, - Deps, - DepsMut, - Empty, - Env, - MessageInfo, - QueryRequest, - Response, - StdError, - StdResult, - Uint128, - WasmMsg, - WasmQuery, -}; - -use crate::{ - msg::{ - ExecuteMsg, - InstantiateMsg, - QueryMsg, - }, - state::{ - bridge_contracts, - bridge_contracts_read, - config, - config_read, - receive_native, - send_native, - wrapped_asset, - wrapped_asset_address, - wrapped_asset_address_read, - wrapped_asset_read, - Action, - AssetMeta, - ConfigInfo, - RegisterChain, - TokenBridgeMessage, - TransferInfo, - }, -}; -use wormhole::{ - byte_utils::{ - extend_address_to_32, - extend_string_to_32, - get_string_from_32, - ByteUtils, - }, - error::ContractError, -}; - -use cw20_base::msg::{ - ExecuteMsg as TokenMsg, - QueryMsg as TokenQuery, -}; - -use wormhole::msg::{ - ExecuteMsg as WormholeExecuteMsg, - QueryMsg as WormholeQueryMsg, -}; - -use wormhole::state::{ - vaa_archive_add, - vaa_archive_check, - GovernancePacket, - ParsedVAA, -}; - -use cw20::TokenInfoResponse; - -use cw20_wrapped::msg::{ - ExecuteMsg as WrappedMsg, - InitHook, - InstantiateMsg as WrappedInit, - QueryMsg as WrappedQuery, - WrappedAssetInfoResponse, -}; -use terraswap::asset::{ - Asset, - AssetInfo, -}; - -use sha3::{ - Digest, - Keccak256, -}; -use std::cmp::{ - max, - min, -}; - -type HumanAddr = String; - -// Chain ID of Terra -const CHAIN_ID: u16 = 3; - -const WRAPPED_ASSET_UPDATING: &str = "updating"; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - // Save general wormhole info - let state = ConfigInfo { - gov_chain: msg.gov_chain, - gov_address: msg.gov_address.as_slice().to_vec(), - wormhole_contract: msg.wormhole_contract, - wrapped_asset_code_id: msg.wrapped_asset_code_id, - }; - config(deps.storage).save(&state)?; - - Ok(Response::default()) -} - -pub fn coins_after_tax(deps: DepsMut, coins: Vec) -> StdResult> { - let mut res = vec![]; - for coin in coins { - let asset = Asset { - amount: coin.amount.clone(), - info: AssetInfo::NativeToken { - denom: coin.denom.clone(), - }, - }; - res.push(asset.deduct_tax(&deps.querier)?); - } - Ok(res) -} - -pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult { - let cfg = config_read(deps.storage).load()?; - let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: cfg.wormhole_contract.clone(), - msg: to_binary(&WormholeQueryMsg::VerifyVAA { - vaa: data.clone(), - block_time, - })?, - }))?; - Ok(vaa) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::RegisterAssetHook { asset_id } => { - handle_register_asset(deps, env, info, &asset_id.as_slice()) - } - ExecuteMsg::InitiateTransfer { - asset, - amount, - recipient_chain, - recipient, - fee, - nonce, - } => handle_initiate_transfer( - deps, - env, - info, - asset, - amount, - recipient_chain, - recipient.as_slice().to_vec(), - fee, - nonce, - ), - ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data), - ExecuteMsg::CreateAssetMeta { - asset_address, - nonce, - } => handle_create_asset_meta(deps, env, info, &asset_address, nonce), - } -} - -/// Handle wrapped asset registration messages -fn handle_register_asset( - deps: DepsMut, - _env: Env, - info: MessageInfo, - asset_id: &[u8], -) -> StdResult { - let mut bucket = wrapped_asset(deps.storage); - let result = bucket.load(asset_id); - let result = result.map_err(|_| ContractError::RegistrationForbidden.std())?; - if result != HumanAddr::from(WRAPPED_ASSET_UPDATING) { - return ContractError::AssetAlreadyRegistered.std_err(); - } - - bucket.save(asset_id, &info.sender.to_string())?; - - let contract_address: CanonicalAddr = deps.api.addr_canonicalize(&info.sender.as_str())?; - wrapped_asset_address(deps.storage).save(contract_address.as_slice(), &asset_id.to_vec())?; - - Ok(Response::new() - .add_attribute("action", "register_asset") - .add_attribute("asset_id", format!("{:?}", asset_id)) - .add_attribute("contract_addr", info.sender)) -} - -fn handle_attest_meta( - deps: DepsMut, - env: Env, - emitter_chain: u16, - emitter_address: Vec, - data: &Vec, -) -> StdResult { - let meta = AssetMeta::deserialize(data)?; - - let expected_contract = - bridge_contracts_read(deps.storage).load(&emitter_chain.to_be_bytes())?; - - // must be sent by a registered token bridge contract - if expected_contract != emitter_address { - return Err(StdError::generic_err("invalid emitter")); - } - - if CHAIN_ID == meta.token_chain { - return Err(StdError::generic_err( - "this asset is native to this chain and should not be attested", - )); - } - - let cfg = config_read(deps.storage).load()?; - let asset_id = build_asset_id(meta.token_chain, &meta.token_address.as_slice()); - - if wrapped_asset_read(deps.storage).load(&asset_id).is_ok() { - return Err(StdError::generic_err( - "this asset has already been attested", - )); - } - - wrapped_asset(deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?; - - Ok( - Response::new().add_message(CosmosMsg::Wasm(WasmMsg::Instantiate { - admin: None, - code_id: cfg.wrapped_asset_code_id, - msg: to_binary(&WrappedInit { - name: get_string_from_32(&meta.name)?, - symbol: get_string_from_32(&meta.symbol)?, - asset_chain: meta.token_chain, - asset_address: meta.token_address.to_vec().into(), - decimals: min(meta.decimals, 8u8), - mint: None, - init_hook: Some(InitHook { - contract_addr: env.contract.address.to_string(), - msg: to_binary(&ExecuteMsg::RegisterAssetHook { - asset_id: asset_id.to_vec().into(), - })?, - }), - })?, - funds: vec![], - label: String::new(), - })), - ) -} - -fn handle_create_asset_meta( - deps: DepsMut, - env: Env, - info: MessageInfo, - asset_address: &HumanAddr, - nonce: u32, -) -> StdResult { - let cfg = config_read(deps.storage).load()?; - - let request = QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: asset_address.clone(), - msg: to_binary(&TokenQuery::TokenInfo {})?, - }); - - let asset_canonical = deps.api.addr_canonicalize(asset_address)?; - let token_info: TokenInfoResponse = deps.querier.query(&request)?; - - let meta: AssetMeta = AssetMeta { - token_chain: CHAIN_ID, - token_address: extend_address_to_32(&asset_canonical), - decimals: token_info.decimals, - symbol: extend_string_to_32(&token_info.symbol)?, - name: extend_string_to_32(&token_info.name)?, - }; - - let token_bridge_message = TokenBridgeMessage { - action: Action::ATTEST_META, - payload: meta.serialize().to_vec(), - }; - - Ok(Response::new() - .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cfg.wormhole_contract, - msg: to_binary(&WormholeExecuteMsg::PostMessage { - message: Binary::from(token_bridge_message.serialize()), - nonce, - })?, - // forward coins sent to this message - funds: coins_after_tax(deps, info.funds.clone())?, - })) - .add_attribute("meta.token_chain", CHAIN_ID.to_string()) - .add_attribute("meta.token", asset_address) - .add_attribute("meta.nonce", nonce.to_string()) - .add_attribute("meta.block_time", env.block.time.seconds().to_string())) -} - -fn submit_vaa( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - data: &Binary, -) -> StdResult { - let state = config_read(deps.storage).load()?; - - let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?; - let data = vaa.payload; - - if vaa_archive_check(deps.storage, vaa.hash.as_slice()) { - return ContractError::VaaAlreadyExecuted.std_err(); - } - vaa_archive_add(deps.storage, vaa.hash.as_slice())?; - - // check if vaa is from governance - if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address { - return handle_governance_payload(deps, env, &data); - } - - let message = TokenBridgeMessage::deserialize(&data)?; - - match message.action { - Action::TRANSFER => handle_complete_transfer( - deps, - env, - info, - vaa.emitter_chain, - vaa.emitter_address, - &message.payload, - ), - Action::ATTEST_META => handle_attest_meta( - deps, - env, - vaa.emitter_chain, - vaa.emitter_address, - &message.payload, - ), - _ => ContractError::InvalidVAAAction.std_err(), - } -} - -fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec) -> StdResult { - let gov_packet = GovernancePacket::deserialize(&data)?; - let module = get_string_from_32(&gov_packet.module)?; - - if module != "TokenBridge" { - return Err(StdError::generic_err("this is not a valid module")); - } - - if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID { - return Err(StdError::generic_err( - "the governance VAA is for another chain", - )); - } - - match gov_packet.action { - 1u8 => handle_register_chain(deps, env, &gov_packet.payload), - _ => ContractError::InvalidVAAAction.std_err(), - } -} - -fn handle_register_chain(deps: DepsMut, _env: Env, data: &Vec) -> StdResult { - let RegisterChain { - chain_id, - chain_address, - } = RegisterChain::deserialize(&data)?; - - let existing = bridge_contracts_read(deps.storage).load(&chain_id.to_be_bytes()); - if existing.is_ok() { - return Err(StdError::generic_err( - "bridge contract already exists for this chain", - )); - } - - let mut bucket = bridge_contracts(deps.storage); - bucket.save(&chain_id.to_be_bytes(), &chain_address)?; - - Ok(Response::new() - .add_attribute("chain_id", chain_id.to_string()) - .add_attribute("chain_address", hex::encode(chain_address))) -} - -fn handle_complete_transfer( - deps: DepsMut, - _env: Env, - info: MessageInfo, - emitter_chain: u16, - emitter_address: Vec, - data: &Vec, -) -> StdResult { - let transfer_info = TransferInfo::deserialize(&data)?; - - let expected_contract = - bridge_contracts_read(deps.storage).load(&emitter_chain.to_be_bytes())?; - - // must be sent by a registered token bridge contract - if expected_contract != emitter_address { - return Err(StdError::generic_err("invalid emitter")); - } - - if transfer_info.recipient_chain != CHAIN_ID { - return Err(StdError::generic_err( - "this transfer is not directed at this chain", - )); - } - - let token_chain = transfer_info.token_chain; - let target_address = (&transfer_info.recipient.as_slice()).get_address(0); - - let (not_supported_amount, mut amount) = transfer_info.amount; - let (not_supported_fee, mut fee) = transfer_info.fee; - - amount = amount.checked_sub(fee).unwrap(); - - // Check high 128 bit of amount value to be empty - if not_supported_amount != 0 || not_supported_fee != 0 { - return ContractError::AmountTooHigh.std_err(); - } - - if token_chain != CHAIN_ID { - let asset_address = transfer_info.token_address; - let asset_id = build_asset_id(token_chain, &asset_address); - - // Check if this asset is already deployed - let contract_addr = wrapped_asset_read(deps.storage).load(&asset_id).ok(); - - return if let Some(contract_addr) = contract_addr { - // Asset already deployed, just mint - - let recipient = deps - .api - .addr_humanize(&target_address) - .or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?; - - let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.clone(), - msg: to_binary(&WrappedMsg::Mint { - recipient: recipient.to_string(), - amount: Uint128::from(amount), - })?, - funds: vec![], - })]; - if fee != 0 { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.clone(), - msg: to_binary(&WrappedMsg::Mint { - recipient: info.sender.to_string(), - amount: Uint128::from(fee), - })?, - funds: vec![], - })) - } - - Ok(Response::new() - .add_messages(messages) - .add_attribute("action", "complete_transfer_wrapped") - .add_attribute("contract", contract_addr) - .add_attribute("recipient", recipient) - .add_attribute("amount", amount.to_string())) - } else { - Err(StdError::generic_err("Wrapped asset not deployed. To deploy, invoke CreateWrapped with the associated AssetMeta")) - }; - } else { - let token_address = transfer_info.token_address.as_slice().get_address(0); - - let recipient = deps.api.addr_humanize(&target_address)?; - let contract_addr = deps.api.addr_humanize(&token_address)?; - - // note -- here the amount is the amount the recipient will receive; - // amount + fee is the total sent - receive_native(deps.storage, &token_address, Uint128::new(amount + fee))?; - - // undo normalization to 8 decimals - let token_info: TokenInfoResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: contract_addr.to_string(), - msg: to_binary(&TokenQuery::TokenInfo {})?, - }))?; - - let decimals = token_info.decimals; - let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32); - amount = amount.checked_mul(multiplier).unwrap(); - fee = fee.checked_mul(multiplier).unwrap(); - - let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.to_string(), - msg: to_binary(&TokenMsg::Transfer { - recipient: recipient.to_string(), - amount: Uint128::from(amount), - })?, - funds: vec![], - })]; - - if fee != 0 { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.to_string(), - msg: to_binary(&TokenMsg::Transfer { - recipient: info.sender.to_string(), - amount: Uint128::from(fee), - })?, - funds: vec![], - })) - } - - Ok(Response::new() - .add_messages(messages) - .add_attribute("action", "complete_transfer_native") - .add_attribute("recipient", recipient) - .add_attribute("contract", contract_addr) - .add_attribute("amount", amount.to_string())) - } -} - -fn handle_initiate_transfer( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - asset: HumanAddr, - mut amount: Uint128, - recipient_chain: u16, - recipient: Vec, - mut fee: Uint128, - nonce: u32, -) -> StdResult { - if recipient_chain == CHAIN_ID { - return ContractError::SameSourceAndTarget.std_err(); - } - - if amount.is_zero() { - return ContractError::AmountTooLow.std_err(); - } - - if fee > amount { - return Err(StdError::generic_err("fee greater than sent amount")); - } - - let asset_chain: u16; - let asset_address: Vec; - - let cfg: ConfigInfo = config_read(deps.storage).load()?; - let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&asset)?; - - let mut messages: Vec = vec![]; - - match wrapped_asset_address_read(deps.storage).load(asset_canonical.as_slice()) { - Ok(_) => { - // This is a deployed wrapped asset, burn it - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset.clone(), - msg: to_binary(&WrappedMsg::Burn { - account: info.sender.to_string(), - amount, - })?, - funds: vec![], - })); - let request = QueryRequest::::Wasm(WasmQuery::Smart { - contract_addr: asset, - msg: to_binary(&WrappedQuery::WrappedAssetInfo {})?, - }); - let wrapped_token_info: WrappedAssetInfoResponse = - deps.querier.custom_query(&request)?; - asset_chain = wrapped_token_info.asset_chain; - asset_address = wrapped_token_info.asset_address.as_slice().to_vec(); - } - Err(_) => { - // normalize amount to 8 decimals when it sent over the wormhole - let token_info: TokenInfoResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: asset.clone(), - msg: to_binary(&TokenQuery::TokenInfo {})?, - }))?; - - let decimals = token_info.decimals; - let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32); - // chop off dust - amount = Uint128::new( - amount - .u128() - .checked_sub(amount.u128().checked_rem(multiplier).unwrap()) - .unwrap(), - ); - fee = Uint128::new( - fee.u128() - .checked_sub(fee.u128().checked_rem(multiplier).unwrap()) - .unwrap(), - ); - - // This is a regular asset, transfer its balance - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset, - msg: to_binary(&TokenMsg::TransferFrom { - owner: info.sender.to_string(), - recipient: env.contract.address.to_string(), - amount, - })?, - funds: vec![], - })); - asset_address = extend_address_to_32(&asset_canonical); - asset_chain = CHAIN_ID; - - // convert to normalized amounts before recording & posting vaa - amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap()); - fee = Uint128::new(fee.u128().checked_div(multiplier).unwrap()); - - send_native(deps.storage, &asset_canonical, amount)?; - } - }; - - let transfer_info = TransferInfo { - token_chain: asset_chain, - token_address: asset_address.clone(), - amount: (0, amount.u128()), - recipient_chain, - recipient: recipient.clone(), - fee: (0, fee.u128()), - }; - - let token_bridge_message = TokenBridgeMessage { - action: Action::TRANSFER, - payload: transfer_info.serialize(), - }; - - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cfg.wormhole_contract, - msg: to_binary(&WormholeExecuteMsg::PostMessage { - message: Binary::from(token_bridge_message.serialize()), - nonce, - })?, - // forward coins sent to this message - funds: coins_after_tax(deps.branch(), info.funds.clone())?, - })); - - Ok(Response::new() - .add_messages(messages) - .add_attribute("transfer.token_chain", asset_chain.to_string()) - .add_attribute("transfer.token", hex::encode(asset_address)) - .add_attribute( - "transfer.sender", - hex::encode(extend_address_to_32( - &deps.api.addr_canonicalize(&info.sender.as_str())?, - )), - ) - .add_attribute("transfer.recipient_chain", recipient_chain.to_string()) - .add_attribute("transfer.recipient", hex::encode(recipient)) - .add_attribute("transfer.amount", amount.to_string()) - .add_attribute("transfer.nonce", nonce.to_string()) - .add_attribute("transfer.block_time", env.block.time.seconds().to_string())) -} - -pub fn query(deps: Deps, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::WrappedRegistry { chain, address } => { - to_binary(&query_wrapped_registry(deps, chain, address.as_slice())?) - } - } -} - -pub fn query_wrapped_registry( - deps: Deps, - chain: u16, - address: &[u8], -) -> StdResult { - let asset_id = build_asset_id(chain, address); - // Check if this asset is already deployed - match wrapped_asset_read(deps.storage).load(&asset_id) { - Ok(address) => Ok(WrappedRegistryResponse { address }), - Err(_) => ContractError::AssetNotFound.std_err(), - } -} - -fn build_asset_id(chain: u16, address: &[u8]) -> Vec { - let mut asset_id: Vec = vec![]; - asset_id.extend_from_slice(&chain.to_be_bytes()); - asset_id.extend_from_slice(address); - - let mut hasher = Keccak256::new(); - hasher.update(asset_id); - hasher.finalize().to_vec() -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::{ - to_binary, - Binary, - StdResult, - }; - - #[test] - fn test_me() -> StdResult<()> { - let x = vec![ - 1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 96u8, 180u8, 94u8, 195u8, 0u8, 0u8, - 0u8, 1u8, 0u8, 3u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 38u8, - 229u8, 4u8, 215u8, 149u8, 163u8, 42u8, 54u8, 156u8, 236u8, 173u8, 168u8, 72u8, 220u8, - 100u8, 90u8, 154u8, 159u8, 160u8, 215u8, 0u8, 91u8, 48u8, 44u8, 48u8, 44u8, 51u8, 44u8, - 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, - 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 53u8, 55u8, 44u8, 52u8, - 54u8, 44u8, 50u8, 53u8, 53u8, 44u8, 53u8, 48u8, 44u8, 50u8, 52u8, 51u8, 44u8, 49u8, - 48u8, 54u8, 44u8, 49u8, 50u8, 50u8, 44u8, 49u8, 49u8, 48u8, 44u8, 49u8, 50u8, 53u8, - 44u8, 56u8, 56u8, 44u8, 55u8, 51u8, 44u8, 49u8, 56u8, 57u8, 44u8, 50u8, 48u8, 55u8, - 44u8, 49u8, 48u8, 52u8, 44u8, 56u8, 51u8, 44u8, 49u8, 49u8, 57u8, 44u8, 49u8, 50u8, - 55u8, 44u8, 49u8, 57u8, 50u8, 44u8, 49u8, 52u8, 55u8, 44u8, 56u8, 57u8, 44u8, 48u8, - 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, - 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, - 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, - 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, - 44u8, 48u8, 44u8, 51u8, 44u8, 50u8, 51u8, 50u8, 44u8, 48u8, 44u8, 51u8, 44u8, 48u8, - 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, - 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 53u8, 51u8, 44u8, 49u8, 49u8, - 54u8, 44u8, 52u8, 56u8, 44u8, 49u8, 49u8, 54u8, 44u8, 49u8, 52u8, 57u8, 44u8, 49u8, - 48u8, 56u8, 44u8, 49u8, 49u8, 51u8, 44u8, 56u8, 44u8, 48u8, 44u8, 50u8, 51u8, 50u8, - 44u8, 52u8, 57u8, 44u8, 49u8, 53u8, 50u8, 44u8, 49u8, 44u8, 50u8, 56u8, 44u8, 50u8, - 48u8, 51u8, 44u8, 50u8, 49u8, 50u8, 44u8, 50u8, 50u8, 49u8, 44u8, 50u8, 52u8, 49u8, - 44u8, 56u8, 53u8, 44u8, 49u8, 48u8, 57u8, 93u8, - ]; - let b = Binary::from(x.clone()); - let y = b.as_slice().to_vec(); - assert_eq!(x, y); - Ok(()) - } -} diff --git a/terra/contracts-5/token-bridge/src/lib.rs b/terra/contracts-5/token-bridge/src/lib.rs deleted file mode 100644 index 1d2cc8b3..00000000 --- a/terra/contracts-5/token-bridge/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[cfg(test)] -#[macro_use] -extern crate lazy_static; - -pub mod contract; -pub mod msg; -pub mod state; - -#[cfg(all(target_arch = "wasm32", not(feature = "library")))] -cosmwasm_std::create_entry_points!(contract); diff --git a/terra/contracts-5/token-bridge/src/msg.rs b/terra/contracts-5/token-bridge/src/msg.rs deleted file mode 100644 index fb2b0bd6..00000000 --- a/terra/contracts-5/token-bridge/src/msg.rs +++ /dev/null @@ -1,71 +0,0 @@ -use cosmwasm_std::{ - Binary, - Uint128, -}; -use terraswap::asset::{Asset, AssetInfo}; -use schemars::JsonSchema; -use serde::{ - Deserialize, - Serialize, -}; - -type HumanAddr = String; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - // governance contract details - pub gov_chain: u16, - pub gov_address: Binary, - - pub wormhole_contract: HumanAddr, - pub wrapped_asset_code_id: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - RegisterAssetHook { - asset_id: Binary, - }, - - DepositTokens {}, - WithdrawTokens { - asset: AssetInfo, - }, - - - InitiateTransfer { - asset: Asset, - recipient_chain: u16, - recipient: Binary, - fee: Uint128, - nonce: u32, - }, - - SubmitVaa { - data: Binary, - }, - - CreateAssetMeta { - asset_info: AssetInfo, - nonce: u32, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - WrappedRegistry { chain: u16, address: Binary }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct WrappedRegistryResponse { - pub address: HumanAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum WormholeQueryMsg { - VerifyVAA { vaa: Binary, block_time: u64 }, -} diff --git a/terra/contracts-5/token-bridge/src/state.rs b/terra/contracts-5/token-bridge/src/state.rs deleted file mode 100644 index a9a2364b..00000000 --- a/terra/contracts-5/token-bridge/src/state.rs +++ /dev/null @@ -1,249 +0,0 @@ -use schemars::JsonSchema; -use serde::{ - Deserialize, - Serialize, -}; - -use cosmwasm_std::{ - CanonicalAddr, - StdError, - StdResult, - Storage, - Uint128, -}; -use cosmwasm_storage::{ - bucket, - bucket_read, - singleton, - singleton_read, - Bucket, - ReadonlyBucket, - ReadonlySingleton, - Singleton, -}; - -use wormhole::byte_utils::ByteUtils; - -type HumanAddr = String; - -pub static CONFIG_KEY: &[u8] = b"config"; -pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset"; -pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address"; -pub static BRIDGE_CONTRACTS: &[u8] = b"bridge_contracts"; -pub static NATIVE_COUNTER: &[u8] = b"native_counter"; - -// Guardian set information -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigInfo { - // governance contract details - pub gov_chain: u16, - pub gov_address: Vec, - - pub wormhole_contract: HumanAddr, - pub wrapped_asset_code_id: u64, -} - -pub fn config(storage: &mut dyn Storage) -> Singleton { - singleton(storage, CONFIG_KEY) -} - -pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, CONFIG_KEY) -} - -pub fn bridge_contracts(storage: &mut dyn Storage) -> Bucket> { - bucket(storage, BRIDGE_CONTRACTS) -} - -pub fn bridge_contracts_read(storage: &dyn Storage) -> ReadonlyBucket> { - bucket_read(storage, BRIDGE_CONTRACTS) -} - -pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket { - bucket(storage, WRAPPED_ASSET_KEY) -} - -pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, WRAPPED_ASSET_KEY) -} - -pub fn wrapped_asset_address(storage: &mut dyn Storage) -> Bucket> { - bucket(storage, WRAPPED_ASSET_ADDRESS_KEY) -} - -pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket> { - bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY) -} - -pub fn send_native( - storage: &mut dyn Storage, - asset_address: &CanonicalAddr, - amount: Uint128, -) -> StdResult<()> { - let mut counter_bucket = bucket(storage, NATIVE_COUNTER); - let new_total = amount - + counter_bucket - .load(asset_address.as_slice()) - .unwrap_or(Uint128::zero()); - if new_total > Uint128::new(u64::MAX as u128) { - return Err(StdError::generic_err( - "transfer exceeds max outstanding bridged token amount", - )); - } - counter_bucket.save(asset_address.as_slice(), &new_total) -} - -pub fn receive_native( - storage: &mut dyn Storage, - asset_address: &CanonicalAddr, - amount: Uint128, -) -> StdResult<()> { - let mut counter_bucket = bucket(storage, NATIVE_COUNTER); - let total: Uint128 = counter_bucket.load(asset_address.as_slice())?; - let result = total.checked_sub(amount)?; - counter_bucket.save(asset_address.as_slice(), &result) -} - -pub struct Action; - -impl Action { - pub const TRANSFER: u8 = 1; - pub const ATTEST_META: u8 = 2; -} - -// 0 u8 action -// 1 [u8] payload - -pub struct TokenBridgeMessage { - pub action: u8, - pub payload: Vec, -} - -impl TokenBridgeMessage { - pub fn deserialize(data: &Vec) -> StdResult { - let data = data.as_slice(); - let action = data.get_u8(0); - let payload = &data[1..]; - - Ok(TokenBridgeMessage { - action, - payload: payload.to_vec(), - }) - } - - pub fn serialize(&self) -> Vec { - [self.action.to_be_bytes().to_vec(), self.payload.clone()].concat() - } -} - -// 0 u256 amount -// 32 [u8; 32] token_address -// 64 u16 token_chain -// 66 [u8; 32] recipient -// 98 u16 recipient_chain -// 100 u256 fee - -pub struct TransferInfo { - pub amount: (u128, u128), - pub token_address: Vec, - pub token_chain: u16, - pub recipient: Vec, - pub recipient_chain: u16, - pub fee: (u128, u128), -} - -impl TransferInfo { - pub fn deserialize(data: &Vec) -> StdResult { - let data = data.as_slice(); - let amount = data.get_u256(0); - let token_address = data.get_bytes32(32).to_vec(); - let token_chain = data.get_u16(64); - let recipient = data.get_bytes32(66).to_vec(); - let recipient_chain = data.get_u16(98); - let fee = data.get_u256(100); - - Ok(TransferInfo { - amount, - token_address, - token_chain, - recipient, - recipient_chain, - fee, - }) - } - pub fn serialize(&self) -> Vec { - [ - self.amount.0.to_be_bytes().to_vec(), - self.amount.1.to_be_bytes().to_vec(), - self.token_address.clone(), - self.token_chain.to_be_bytes().to_vec(), - self.recipient.to_vec(), - self.recipient_chain.to_be_bytes().to_vec(), - self.fee.0.to_be_bytes().to_vec(), - self.fee.1.to_be_bytes().to_vec(), - ] - .concat() - } -} - -// 0 [32]uint8 TokenAddress -// 32 uint16 TokenChain -// 34 uint8 Decimals -// 35 [32]uint8 Symbol -// 67 [32]uint8 Name - -pub struct AssetMeta { - pub token_address: Vec, - pub token_chain: u16, - pub decimals: u8, - pub symbol: Vec, - pub name: Vec, -} - -impl AssetMeta { - pub fn deserialize(data: &Vec) -> StdResult { - let data = data.as_slice(); - let token_address = data.get_bytes32(0).to_vec(); - let token_chain = data.get_u16(32); - let decimals = data.get_u8(34); - let symbol = data.get_bytes32(35).to_vec(); - let name = data.get_bytes32(67).to_vec(); - - Ok(AssetMeta { - token_chain, - token_address, - decimals, - symbol, - name, - }) - } - - pub fn serialize(&self) -> Vec { - [ - self.token_address.clone(), - self.token_chain.to_be_bytes().to_vec(), - self.decimals.to_be_bytes().to_vec(), - self.symbol.clone(), - self.name.clone(), - ] - .concat() - } -} - -pub struct RegisterChain { - pub chain_id: u16, - pub chain_address: Vec, -} - -impl RegisterChain { - pub fn deserialize(data: &Vec) -> StdResult { - let data = data.as_slice(); - let chain_id = data.get_u16(0); - let chain_address = data[2..].to_vec(); - - Ok(RegisterChain { - chain_id, - chain_address, - }) - } -} diff --git a/terra/contracts-5/token-bridge/tests/integration.rs b/terra/contracts-5/token-bridge/tests/integration.rs deleted file mode 100644 index 88d0359b..00000000 --- a/terra/contracts-5/token-bridge/tests/integration.rs +++ /dev/null @@ -1,114 +0,0 @@ -static WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/release/wormhole.wasm"); - -use cosmwasm_std::{ - from_slice, - Coin, - Env, - HumanAddr, - InitResponse, -}; -use cosmwasm_storage::to_length_prefixed; -use cosmwasm_vm::{ - testing::{ - init, - mock_env, - mock_instance, - MockApi, - MockQuerier, - MockStorage, - }, - Api, - Instance, - Storage, -}; - -use wormhole::{ - msg::InitMsg, - state::{ - ConfigInfo, - GuardianAddress, - GuardianSetInfo, - CONFIG_KEY, - }, -}; - -use hex; - -enum TestAddress { - INITIALIZER, -} - -impl TestAddress { - fn value(&self) -> HumanAddr { - match self { - TestAddress::INITIALIZER => HumanAddr::from("initializer"), - } - } -} - -fn mock_env_height(signer: &HumanAddr, height: u64, time: u64) -> Env { - let mut env = mock_env(signer, &[]); - env.block.height = height; - env.block.time = time; - env -} - -fn get_config_info(storage: &S) -> ConfigInfo { - let key = to_length_prefixed(CONFIG_KEY); - let data = storage - .get(&key) - .0 - .expect("error getting data") - .expect("data should exist"); - from_slice(&data).expect("invalid data") -} - -fn do_init( - height: u64, - guardians: &Vec, -) -> Instance { - let mut deps = mock_instance(WASM, &[]); - let init_msg = InitMsg { - initial_guardian_set: GuardianSetInfo { - addresses: guardians.clone(), - expiration_time: 100, - }, - guardian_set_expirity: 50, - wrapped_asset_code_id: 999, - }; - let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0); - let owner = deps - .api - .canonical_address(&TestAddress::INITIALIZER.value()) - .0 - .unwrap(); - let res: InitResponse = init(&mut deps, env, init_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // query the store directly - deps.with_storage(|storage| { - assert_eq!( - get_config_info(storage), - ConfigInfo { - guardian_set_index: 0, - guardian_set_expirity: 50, - wrapped_asset_code_id: 999, - owner, - fee: Coin::new(10000, "uluna"), - } - ); - Ok(()) - }) - .unwrap(); - deps -} - -#[test] -fn init_works() { - let guardians = vec![GuardianAddress::from(GuardianAddress { - bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe") - .expect("Decoding failed") - .into(), - })]; - let _deps = do_init(111, &guardians); -} diff --git a/terra/contracts-5/wormhole/.cargo/config b/terra/contracts-5/wormhole/.cargo/config deleted file mode 100644 index 2d5cce4e..00000000 --- a/terra/contracts-5/wormhole/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" \ No newline at end of file diff --git a/terra/contracts-5/wormhole/Cargo.toml b/terra/contracts-5/wormhole/Cargo.toml deleted file mode 100644 index b7464e6d..00000000 --- a/terra/contracts-5/wormhole/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "wormhole" -version = "0.1.0" -authors = ["Yuriy Savchenko "] -edition = "2018" -description = "Wormhole contract" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -cosmwasm-std = { version = "0.16.0" } -cosmwasm-storage = { version = "0.16.0" } -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw20 = "0.8.0" -cw20-base = { version = "0.8.0", features = ["library"] } -cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] } -thiserror = { version = "1.0.20" } -k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] } -sha3 = { version = "0.9.1", default-features = false } -generic-array = { version = "0.14.4" } -hex = "0.4.2" -lazy_static = "1.4.0" - -[dev-dependencies] -cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] } -serde_json = "1.0" diff --git a/terra/contracts-5/wormhole/src/byte_utils.rs b/terra/contracts-5/wormhole/src/byte_utils.rs deleted file mode 100644 index 9f3a0e63..00000000 --- a/terra/contracts-5/wormhole/src/byte_utils.rs +++ /dev/null @@ -1,76 +0,0 @@ -use cosmwasm_std::{ - CanonicalAddr, - StdError, - StdResult, -}; - -pub trait ByteUtils { - fn get_u8(&self, index: usize) -> u8; - fn get_u16(&self, index: usize) -> u16; - fn get_u32(&self, index: usize) -> u32; - fn get_u64(&self, index: usize) -> u64; - - fn get_u128_be(&self, index: usize) -> u128; - /// High 128 then low 128 - fn get_u256(&self, index: usize) -> (u128, u128); - fn get_address(&self, index: usize) -> CanonicalAddr; - fn get_bytes32(&self, index: usize) -> &[u8]; -} - -impl ByteUtils for &[u8] { - fn get_u8(&self, index: usize) -> u8 { - self[index] - } - fn get_u16(&self, index: usize) -> u16 { - let mut bytes: [u8; 16 / 8] = [0; 16 / 8]; - bytes.copy_from_slice(&self[index..index + 2]); - u16::from_be_bytes(bytes) - } - fn get_u32(&self, index: usize) -> u32 { - let mut bytes: [u8; 32 / 8] = [0; 32 / 8]; - bytes.copy_from_slice(&self[index..index + 4]); - u32::from_be_bytes(bytes) - } - fn get_u64(&self, index: usize) -> u64 { - let mut bytes: [u8; 64 / 8] = [0; 64 / 8]; - bytes.copy_from_slice(&self[index..index + 8]); - u64::from_be_bytes(bytes) - } - fn get_u128_be(&self, index: usize) -> u128 { - let mut bytes: [u8; 128 / 8] = [0; 128 / 8]; - bytes.copy_from_slice(&self[index..index + 128 / 8]); - u128::from_be_bytes(bytes) - } - fn get_u256(&self, index: usize) -> (u128, u128) { - (self.get_u128_be(index), self.get_u128_be(index + 128 / 8)) - } - fn get_address(&self, index: usize) -> CanonicalAddr { - // 32 bytes are reserved for addresses, but only the last 20 bytes are taken by the actual address - CanonicalAddr::from(&self[index + 32 - 20..index + 32]) - } - fn get_bytes32(&self, index: usize) -> &[u8] { - &self[index..index + 32] - } -} - -pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec { - let mut result: Vec = vec![0; 12]; - result.extend(addr.as_slice()); - result -} - -pub fn extend_string_to_32(s: &String) -> StdResult> { - let bytes = s.as_bytes(); - if bytes.len() > 32 { - return Err(StdError::generic_err("string more than 32 ")); - } - - let result = vec![0; 32 - bytes.len()]; - Ok([bytes.to_vec(), result].concat()) -} - -pub fn get_string_from_32(v: &Vec) -> StdResult { - let s = String::from_utf8(v.clone()) - .or_else(|_| Err(StdError::generic_err("could not parse string")))?; - Ok(s.chars().filter(|c| c != &'\0').collect()) -} diff --git a/terra/contracts-5/wormhole/src/contract.rs b/terra/contracts-5/wormhole/src/contract.rs deleted file mode 100644 index 378cbb1e..00000000 --- a/terra/contracts-5/wormhole/src/contract.rs +++ /dev/null @@ -1,387 +0,0 @@ -use cosmwasm_std::{ - entry_point, - has_coins, - to_binary, - BankMsg, - Binary, - Coin, - CosmosMsg, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Storage, -}; - -use crate::{ - byte_utils::{ - extend_address_to_32, - ByteUtils, - }, - error::ContractError, - msg::{ - ExecuteMsg, - GetAddressHexResponse, - GetStateResponse, - GuardianSetInfoResponse, - InstantiateMsg, - QueryMsg, - }, - state::{ - config, - config_read, - guardian_set_get, - guardian_set_set, - sequence_read, - sequence_set, - vaa_archive_add, - vaa_archive_check, - ConfigInfo, - GovernancePacket, - GuardianAddress, - GuardianSetInfo, - GuardianSetUpgrade, - ParsedVAA, - SetFee, - TransferFee, - }, -}; - -use k256::{ - ecdsa::{ - recoverable::{ - Id as RecoverableId, - Signature as RecoverableSignature, - }, - Signature, - VerifyingKey, - }, - EncodedPoint, -}; -use sha3::{ - Digest, - Keccak256, -}; - -use generic_array::GenericArray; -use std::convert::TryFrom; - -type HumanAddr = String; - -// Chain ID of Terra -const CHAIN_ID: u16 = 3; - -// Lock assets fee amount and denomination -const FEE_AMOUNT: u128 = 10000; -pub const FEE_DENOMINATION: &str = "uluna"; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate(deps: DepsMut, _env: Env, msg: InstantiateMsg) -> StdResult { - // Save general wormhole info - let state = ConfigInfo { - gov_chain: msg.gov_chain, - gov_address: msg.gov_address.as_slice().to_vec(), - guardian_set_index: 0, - guardian_set_expirity: msg.guardian_set_expirity, - fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default - }; - config(deps.storage).save(&state)?; - - // Add initial guardian set to storage - guardian_set_set( - deps.storage, - state.guardian_set_index, - &msg.initial_guardian_set, - )?; - - Ok(Response::default()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::PostMessage { message, nonce } => { - handle_post_message(deps, env, info, &message.as_slice(), nonce) - } - ExecuteMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, info, vaa.as_slice()), - } -} - -/// Process VAA message signed by quardians -fn handle_submit_vaa( - deps: DepsMut, - env: Env, - _info: MessageInfo, - data: &[u8], -) -> StdResult { - let state = config_read(deps.storage).load()?; - - let vaa = parse_and_verify_vaa(deps.storage, data, env.block.time.seconds())?; - vaa_archive_add(deps.storage, vaa.hash.as_slice())?; - - if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address { - if state.guardian_set_index != vaa.guardian_set_index { - return Err(StdError::generic_err( - "governance VAAs must be signed by the current guardian set", - )); - } - return handle_governance_payload(deps, env, &vaa.payload); - } - - ContractError::InvalidVAAAction.std_err() -} - -fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec) -> StdResult { - let gov_packet = GovernancePacket::deserialize(&data)?; - - let module = String::from_utf8(gov_packet.module).unwrap(); - let module: String = module.chars().filter(|c| c != &'\0').collect(); - - if module != "Core" { - return Err(StdError::generic_err("this is not a valid module")); - } - - if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID { - return Err(StdError::generic_err( - "the governance VAA is for another chain", - )); - } - - match gov_packet.action { - // 1 is reserved for upgrade / migration - 2u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload), - 3u8 => handle_set_fee(deps, env, &gov_packet.payload), - 4u8 => handle_transfer_fee(deps, env, &gov_packet.payload), - _ => ContractError::InvalidVAAAction.std_err(), - } -} - -/// Parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an -/// active guardian set i.e. is valid according to Wormhole consensus rules -fn parse_and_verify_vaa( - storage: &dyn Storage, - data: &[u8], - block_time: u64, -) -> StdResult { - let vaa = ParsedVAA::deserialize(data)?; - - if vaa.version != 1 { - return ContractError::InvalidVersion.std_err(); - } - - // Check if VAA with this hash was already accepted - if vaa_archive_check(storage, vaa.hash.as_slice()) { - return ContractError::VaaAlreadyExecuted.std_err(); - } - - // Load and check guardian set - let guardian_set = guardian_set_get(storage, vaa.guardian_set_index); - let guardian_set: GuardianSetInfo = - guardian_set.or_else(|_| ContractError::InvalidGuardianSetIndex.std_err())?; - - if guardian_set.expiration_time != 0 && guardian_set.expiration_time < block_time { - return ContractError::GuardianSetExpired.std_err(); - } - if (vaa.len_signers as usize) < guardian_set.quorum() { - return ContractError::NoQuorum.std_err(); - } - - // Verify guardian signatures - let mut last_index: i32 = -1; - let mut pos = ParsedVAA::HEADER_LEN; - - for _ in 0..vaa.len_signers { - if pos + ParsedVAA::SIGNATURE_LEN > data.len() { - return ContractError::InvalidVAA.std_err(); - } - let index = data.get_u8(pos) as i32; - if index <= last_index { - return ContractError::WrongGuardianIndexOrder.std_err(); - } - last_index = index; - - let signature = Signature::try_from( - &data[pos + ParsedVAA::SIG_DATA_POS - ..pos + ParsedVAA::SIG_DATA_POS + ParsedVAA::SIG_DATA_LEN], - ) - .or_else(|_| ContractError::CannotDecodeSignature.std_err())?; - let id = RecoverableId::new(data.get_u8(pos + ParsedVAA::SIG_RECOVERY_POS)) - .or_else(|_| ContractError::CannotDecodeSignature.std_err())?; - let recoverable_signature = RecoverableSignature::new(&signature, id) - .or_else(|_| ContractError::CannotDecodeSignature.std_err())?; - - let verify_key = recoverable_signature - .recover_verify_key_from_digest_bytes(GenericArray::from_slice(vaa.hash.as_slice())) - .or_else(|_| ContractError::CannotRecoverKey.std_err())?; - - let index = index as usize; - if index >= guardian_set.addresses.len() { - return ContractError::TooManySignatures.std_err(); - } - if !keys_equal(&verify_key, &guardian_set.addresses[index]) { - return ContractError::GuardianSignatureError.std_err(); - } - pos += ParsedVAA::SIGNATURE_LEN; - } - - Ok(vaa) -} - -fn vaa_update_guardian_set(deps: DepsMut, env: Env, data: &Vec) -> StdResult { - /* Payload format - 0 uint32 new_index - 4 uint8 len(keys) - 5 [][20]uint8 guardian addresses - */ - - let mut state = config_read(deps.storage).load()?; - - let GuardianSetUpgrade { - new_guardian_set_index, - new_guardian_set, - } = GuardianSetUpgrade::deserialize(&data)?; - - if new_guardian_set_index != state.guardian_set_index + 1 { - return ContractError::GuardianSetIndexIncreaseError.std_err(); - } - - let old_guardian_set_index = state.guardian_set_index; - - state.guardian_set_index = new_guardian_set_index; - - guardian_set_set(deps.storage, state.guardian_set_index, &new_guardian_set)?; - - config(deps.storage).save(&state)?; - - let mut old_guardian_set = guardian_set_get(deps.storage, old_guardian_set_index)?; - old_guardian_set.expiration_time = env.block.time.seconds() + state.guardian_set_expirity; - guardian_set_set(deps.storage, old_guardian_set_index, &old_guardian_set)?; - - Ok(Response::new() - .add_attribute("action", "guardian_set_change") - .add_attribute("old", old_guardian_set_index.to_string()) - .add_attribute("new", state.guardian_set_index.to_string())) -} - -pub fn handle_set_fee(deps: DepsMut, _env: Env, data: &Vec) -> StdResult { - let set_fee_msg = SetFee::deserialize(&data)?; - - // Save new fees - let mut state = config_read(deps.storage).load()?; - state.fee = set_fee_msg.fee; - config(deps.storage).save(&state)?; - - Ok(Response::new() - .add_attribute("action", "fee_change") - .add_attribute("new_fee.amount", state.fee.amount.to_string()) - .add_attribute("new_fee.denom", state.fee.denom.to_string())) -} - -pub fn handle_transfer_fee(deps: DepsMut, _env: Env, data: &Vec) -> StdResult { - let transfer_msg = TransferFee::deserialize(&data)?; - - Ok(Response::new().add_message(CosmosMsg::Bank(BankMsg::Send { - to_address: deps.api.addr_humanize(&transfer_msg.recipient)?.to_string(), - amount: vec![transfer_msg.amount], - }))) -} - -fn handle_post_message( - deps: DepsMut, - env: Env, - info: MessageInfo, - message: &[u8], - nonce: u32, -) -> StdResult { - let state = config_read(deps.storage).load()?; - let fee = state.fee; - - // Check fee - if !has_coins(info.funds.as_ref(), &fee) { - return ContractError::FeeTooLow.std_err(); - } - - let emitter = extend_address_to_32(&deps.api.addr_canonicalize(&info.sender.as_str())?); - let sequence = sequence_read(deps.storage, emitter.as_slice()); - sequence_set(deps.storage, emitter.as_slice(), sequence + 1)?; - - Ok(Response::new() - .add_attribute("message.message", hex::encode(message)) - .add_attribute("message.sender", hex::encode(emitter)) - .add_attribute("message.chain_id", CHAIN_ID.to_string()) - .add_attribute("message.nonce", nonce.to_string()) - .add_attribute("message.sequence", sequence.to_string()) - .add_attribute("message.block_time", env.block.time.seconds().to_string())) -} - -pub fn query(deps: Deps, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GuardianSetInfo {} => to_binary(&query_guardian_set_info(deps)?), - QueryMsg::VerifyVAA { vaa, block_time } => to_binary(&query_parse_and_verify_vaa( - deps, - &vaa.as_slice(), - block_time, - )?), - QueryMsg::GetState {} => to_binary(&query_state(deps)?), - QueryMsg::QueryAddressHex { address } => to_binary(&query_address_hex(deps, &address)?), - } -} - -pub fn query_guardian_set_info(deps: Deps) -> StdResult { - let state = config_read(deps.storage).load()?; - let guardian_set = guardian_set_get(deps.storage, state.guardian_set_index)?; - let res = GuardianSetInfoResponse { - guardian_set_index: state.guardian_set_index, - addresses: guardian_set.addresses, - }; - Ok(res) -} - -pub fn query_parse_and_verify_vaa( - deps: Deps, - data: &[u8], - block_time: u64, -) -> StdResult { - parse_and_verify_vaa(deps.storage, data, block_time) -} - -// returns the hex of the 32 byte address we use for some address on this chain -pub fn query_address_hex(deps: Deps, address: &HumanAddr) -> StdResult { - Ok(GetAddressHexResponse { - hex: hex::encode(extend_address_to_32(&deps.api.addr_canonicalize(&address)?)), - }) -} - -pub fn query_state(deps: Deps) -> StdResult { - let state = config_read(deps.storage).load()?; - let res = GetStateResponse { fee: state.fee }; - Ok(res) -} - -fn keys_equal(a: &VerifyingKey, b: &GuardianAddress) -> bool { - let mut hasher = Keccak256::new(); - - let point: EncodedPoint = EncodedPoint::from(a); - let point = point.decompress(); - if bool::from(point.is_none()) { - return false; - } - let point = point.unwrap(); - - hasher.update(&point.as_bytes()[1..]); - let a = &hasher.finalize()[12..]; - - let b = &b.bytes; - if a.len() != b.len() { - return false; - } - for (ai, bi) in a.iter().zip(b.as_slice().iter()) { - if ai != bi { - return false; - } - } - true -} diff --git a/terra/contracts-5/wormhole/src/error.rs b/terra/contracts-5/wormhole/src/error.rs deleted file mode 100644 index 67603f5d..00000000 --- a/terra/contracts-5/wormhole/src/error.rs +++ /dev/null @@ -1,113 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - /// Invalid VAA version - #[error("InvalidVersion")] - InvalidVersion, - - /// Guardian set with this index does not exist - #[error("InvalidGuardianSetIndex")] - InvalidGuardianSetIndex, - - /// Guardian set expiration date is zero or in the past - #[error("GuardianSetExpired")] - GuardianSetExpired, - - /// Not enough signers on the VAA - #[error("NoQuorum")] - NoQuorum, - - /// Wrong guardian index order, order must be ascending - #[error("WrongGuardianIndexOrder")] - WrongGuardianIndexOrder, - - /// Some problem with signature decoding from bytes - #[error("CannotDecodeSignature")] - CannotDecodeSignature, - - /// Some problem with public key recovery from the signature - #[error("CannotRecoverKey")] - CannotRecoverKey, - - /// Recovered pubkey from signature does not match guardian address - #[error("GuardianSignatureError")] - GuardianSignatureError, - - /// VAA action code not recognized - #[error("InvalidVAAAction")] - InvalidVAAAction, - - /// VAA guardian set is not current - #[error("NotCurrentGuardianSet")] - NotCurrentGuardianSet, - - /// Only 128-bit amounts are supported - #[error("AmountTooHigh")] - AmountTooHigh, - - /// Amount should be higher than zero - #[error("AmountTooLow")] - AmountTooLow, - - /// Source and target chain ids must be different - #[error("SameSourceAndTarget")] - SameSourceAndTarget, - - /// Target chain id must be the same as the current CHAIN_ID - #[error("WrongTargetChain")] - WrongTargetChain, - - /// Wrapped asset init hook sent twice for the same asset id - #[error("AssetAlreadyRegistered")] - AssetAlreadyRegistered, - - /// Guardian set must increase in steps of 1 - #[error("GuardianSetIndexIncreaseError")] - GuardianSetIndexIncreaseError, - - /// VAA was already executed - #[error("VaaAlreadyExecuted")] - VaaAlreadyExecuted, - - /// Message sender not permitted to execute this operation - #[error("PermissionDenied")] - PermissionDenied, - - /// Could not decode target address from canonical to human-readable form - #[error("WrongTargetAddressFormat")] - WrongTargetAddressFormat, - - /// More signatures than active guardians found - #[error("TooManySignatures")] - TooManySignatures, - - /// Wrapped asset not found in the registry - #[error("AssetNotFound")] - AssetNotFound, - - /// Generic error when there is a problem with VAA structure - #[error("InvalidVAA")] - InvalidVAA, - - /// Thrown when fee is enabled for the action, but was not sent with the transaction - #[error("FeeTooLow")] - FeeTooLow, - - /// Registering asset outside of the wormhole - #[error("RegistrationForbidden")] - RegistrationForbidden, -} - -impl ContractError { - pub fn std(&self) -> StdError { - StdError::GenericErr { - msg: format!("{}", self), - } - } - - pub fn std_err(&self) -> Result { - Err(self.std()) - } -} diff --git a/terra/contracts-5/wormhole/src/lib.rs b/terra/contracts-5/wormhole/src/lib.rs deleted file mode 100644 index 610d16ed..00000000 --- a/terra/contracts-5/wormhole/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod byte_utils; -pub mod contract; -pub mod error; -pub mod msg; -pub mod state; - -pub use crate::error::ContractError; diff --git a/terra/contracts-5/wormhole/src/msg.rs b/terra/contracts-5/wormhole/src/msg.rs deleted file mode 100644 index 810aa92b..00000000 --- a/terra/contracts-5/wormhole/src/msg.rs +++ /dev/null @@ -1,66 +0,0 @@ -use cosmwasm_std::{ - Binary, - Coin, -}; -use schemars::JsonSchema; -use serde::{ - Deserialize, - Serialize, -}; - -use crate::state::{ - GuardianAddress, - GuardianSetInfo, -}; - -type HumanAddr = String; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - pub gov_chain: u16, - pub gov_address: Binary, - - pub initial_guardian_set: GuardianSetInfo, - pub guardian_set_expirity: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - SubmitVAA { vaa: Binary }, - PostMessage { message: Binary, nonce: u32 }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - GuardianSetInfo {}, - VerifyVAA { vaa: Binary, block_time: u64 }, - GetState {}, - QueryAddressHex { address: HumanAddr }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct GuardianSetInfoResponse { - pub guardian_set_index: u32, // Current guardian set index - pub addresses: Vec, // List of querdian addresses -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct WrappedRegistryResponse { - pub address: HumanAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct GetStateResponse { - pub fee: Coin, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct GetAddressHexResponse { - pub hex: String, -} diff --git a/terra/contracts-5/wormhole/src/state.rs b/terra/contracts-5/wormhole/src/state.rs deleted file mode 100644 index c0ce806f..00000000 --- a/terra/contracts-5/wormhole/src/state.rs +++ /dev/null @@ -1,444 +0,0 @@ -use schemars::{ - JsonSchema, -}; -use serde::{ - Deserialize, - Serialize, -}; - -use cosmwasm_std::{ - Binary, - CanonicalAddr, - Coin, - StdResult, - Storage, - Uint128, -}; -use cosmwasm_storage::{ - bucket, - bucket_read, - singleton, - singleton_read, - Bucket, - ReadonlyBucket, - ReadonlySingleton, - Singleton, -}; - -use crate::{ - byte_utils::ByteUtils, - error::ContractError, -}; - -use sha3::{ - Digest, - Keccak256, -}; - -type HumanAddr = String; - -pub static CONFIG_KEY: &[u8] = b"config"; -pub static GUARDIAN_SET_KEY: &[u8] = b"guardian_set"; -pub static SEQUENCE_KEY: &[u8] = b"sequence"; -pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset"; -pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address"; - -// Guardian set information -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigInfo { - // Current active guardian set - pub guardian_set_index: u32, - - // Period for which a guardian set stays active after it has been replaced - pub guardian_set_expirity: u64, - - // governance contract details - pub gov_chain: u16, - pub gov_address: Vec, - - // Message sending fee - pub fee: Coin, -} - -// Validator Action Approval(VAA) data -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ParsedVAA { - pub version: u8, - pub guardian_set_index: u32, - pub timestamp: u32, - pub nonce: u32, - pub len_signers: u8, - - pub emitter_chain: u16, - pub emitter_address: Vec, - pub sequence: u64, - pub consistency_level: u8, - pub payload: Vec, - - pub hash: Vec, -} - -impl ParsedVAA { - /* VAA format: - - header (length 6): - 0 uint8 version (0x01) - 1 uint32 guardian set index - 5 uint8 len signatures - - per signature (length 66): - 0 uint8 index of the signer (in guardian keys) - 1 [65]uint8 signature - - body: - 0 uint32 timestamp (unix in seconds) - 4 uint32 nonce - 8 uint16 emitter_chain - 10 [32]uint8 emitter_address - 42 uint64 sequence - 50 uint8 consistency_level - 51 []uint8 payload - */ - - pub const HEADER_LEN: usize = 6; - pub const SIGNATURE_LEN: usize = 66; - - pub const GUARDIAN_SET_INDEX_POS: usize = 1; - pub const LEN_SIGNER_POS: usize = 5; - - pub const VAA_NONCE_POS: usize = 4; - pub const VAA_EMITTER_CHAIN_POS: usize = 8; - pub const VAA_EMITTER_ADDRESS_POS: usize = 10; - pub const VAA_SEQUENCE_POS: usize = 42; - pub const VAA_CONSISTENCY_LEVEL_POS: usize = 50; - pub const VAA_PAYLOAD_POS: usize = 51; - - // Signature data offsets in the signature block - pub const SIG_DATA_POS: usize = 1; - // Signature length minus recovery id at the end - pub const SIG_DATA_LEN: usize = 64; - // Recovery byte is last after the main signature - pub const SIG_RECOVERY_POS: usize = Self::SIG_DATA_POS + Self::SIG_DATA_LEN; - - pub fn deserialize(data: &[u8]) -> StdResult { - let version = data.get_u8(0); - - // Load 4 bytes starting from index 1 - let guardian_set_index: u32 = data.get_u32(Self::GUARDIAN_SET_INDEX_POS); - let len_signers = data.get_u8(Self::LEN_SIGNER_POS) as usize; - let body_offset: usize = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers as usize; - - // Hash the body - if body_offset >= data.len() { - return ContractError::InvalidVAA.std_err(); - } - let body = &data[body_offset..]; - let mut hasher = Keccak256::new(); - hasher.update(body); - let hash = hasher.finalize().to_vec(); - - // Rehash the hash - let mut hasher = Keccak256::new(); - hasher.update(hash); - let hash = hasher.finalize().to_vec(); - - // Signatures valid, apply VAA - if body_offset + Self::VAA_PAYLOAD_POS > data.len() { - return ContractError::InvalidVAA.std_err(); - } - - let timestamp = data.get_u32(body_offset); - let nonce = data.get_u32(body_offset + Self::VAA_NONCE_POS); - let emitter_chain = data.get_u16(body_offset + Self::VAA_EMITTER_CHAIN_POS); - let emitter_address = data - .get_bytes32(body_offset + Self::VAA_EMITTER_ADDRESS_POS) - .to_vec(); - let sequence = data.get_u64(body_offset + Self::VAA_SEQUENCE_POS); - let consistency_level = data.get_u8(body_offset + Self::VAA_CONSISTENCY_LEVEL_POS); - let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec(); - - Ok(ParsedVAA { - version, - guardian_set_index, - timestamp, - nonce, - len_signers: len_signers as u8, - emitter_chain, - emitter_address, - sequence, - consistency_level, - payload, - hash, - }) - } -} - -// Guardian address -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct GuardianAddress { - pub bytes: Binary, // 20-byte addresses -} - -use crate::contract::FEE_DENOMINATION; -#[cfg(test)] -use hex; - -#[cfg(test)] -impl GuardianAddress { - pub fn from(string: &str) -> GuardianAddress { - GuardianAddress { - bytes: hex::decode(string).expect("Decoding failed").into(), - } - } -} - -// Guardian set information -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct GuardianSetInfo { - pub addresses: Vec, - // List of guardian addresses - pub expiration_time: u64, // Guardian set expiration time -} - -impl GuardianSetInfo { - pub fn quorum(&self) -> usize { - // allow quorum of 0 for testing purposes... - if self.addresses.len() == 0 { - return 0; - } - ((self.addresses.len() * 10 / 3) * 2) / 10 + 1 - } -} - -// Wormhole contract generic information -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct WormholeInfo { - // Period for which a guardian set stays active after it has been replaced - pub guardian_set_expirity: u64, -} - -pub fn config(storage: &mut dyn Storage) -> Singleton { - singleton(storage, CONFIG_KEY) -} - -pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, CONFIG_KEY) -} - -pub fn guardian_set_set( - storage: &mut dyn Storage, - index: u32, - data: &GuardianSetInfo, -) -> StdResult<()> { - bucket(storage, GUARDIAN_SET_KEY).save(&index.to_be_bytes(), data) -} - -pub fn guardian_set_get(storage: &dyn Storage, index: u32) -> StdResult { - bucket_read(storage, GUARDIAN_SET_KEY).load(&index.to_be_bytes()) -} - -pub fn sequence_set(storage: &mut dyn Storage, emitter: &[u8], sequence: u64) -> StdResult<()> { - bucket(storage, SEQUENCE_KEY).save(emitter, &sequence) -} - -pub fn sequence_read(storage: &dyn Storage, emitter: &[u8]) -> u64 { - bucket_read(storage, SEQUENCE_KEY) - .load(&emitter) - .or::(Ok(0)) - .unwrap() -} - -pub fn vaa_archive_add(storage: &mut dyn Storage, hash: &[u8]) -> StdResult<()> { - bucket(storage, GUARDIAN_SET_KEY).save(hash, &true) -} - -pub fn vaa_archive_check(storage: &dyn Storage, hash: &[u8]) -> bool { - bucket_read(storage, GUARDIAN_SET_KEY) - .load(&hash) - .or::(Ok(false)) - .unwrap() -} - -pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket { - bucket(storage, WRAPPED_ASSET_KEY) -} - -pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, WRAPPED_ASSET_KEY) -} - -pub fn wrapped_asset_address(storage: &mut dyn Storage) -> Bucket> { - bucket(storage, WRAPPED_ASSET_ADDRESS_KEY) -} - -pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket> { - bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY) -} - -pub struct GovernancePacket { - pub module: Vec, - pub action: u8, - pub chain: u16, - pub payload: Vec, -} - -impl GovernancePacket { - pub fn deserialize(data: &Vec) -> StdResult { - let data = data.as_slice(); - let module = data.get_bytes32(0).to_vec(); - let action = data.get_u8(32); - let chain = data.get_u16(33); - let payload = data[35..].to_vec(); - - Ok(GovernancePacket { - module, - action, - chain, - payload, - }) - } -} - -// action 2 -pub struct GuardianSetUpgrade { - pub new_guardian_set_index: u32, - pub new_guardian_set: GuardianSetInfo, -} - -impl GuardianSetUpgrade { - pub fn deserialize(data: &Vec) -> StdResult { - const ADDRESS_LEN: usize = 20; - - let data = data.as_slice(); - let new_guardian_set_index = data.get_u32(0); - - let n_guardians = data.get_u8(4); - - let mut addresses = vec![]; - - for i in 0..n_guardians { - let pos = 5 + (i as usize) * ADDRESS_LEN; - if pos + ADDRESS_LEN > data.len() { - return ContractError::InvalidVAA.std_err(); - } - - addresses.push(GuardianAddress { - bytes: data[pos..pos + ADDRESS_LEN].to_vec().into(), - }); - } - - let new_guardian_set = GuardianSetInfo { - addresses, - expiration_time: 0, - }; - - return Ok(GuardianSetUpgrade { - new_guardian_set_index, - new_guardian_set, - }); - } -} - -// action 3 -pub struct SetFee { - pub fee: Coin, -} - -impl SetFee { - pub fn deserialize(data: &Vec) -> StdResult { - let data = data.as_slice(); - - let (_, amount) = data.get_u256(0); - let fee = Coin { - denom: String::from(FEE_DENOMINATION), - amount: Uint128::new(amount), - }; - Ok(SetFee { fee }) - } -} - -// action 4 -pub struct TransferFee { - pub amount: Coin, - pub recipient: CanonicalAddr, -} - -impl TransferFee { - pub fn deserialize(data: &Vec) -> StdResult { - let data = data.as_slice(); - let recipient = data.get_address(0); - - let (_, amount) = data.get_u256(32); - let amount = Coin { - denom: String::from(FEE_DENOMINATION), - amount: Uint128::new(amount), - }; - Ok(TransferFee { amount, recipient }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn build_guardian_set(length: usize) -> GuardianSetInfo { - let mut addresses: Vec = Vec::with_capacity(length); - for _ in 0..length { - addresses.push(GuardianAddress { - bytes: vec![].into(), - }); - } - - GuardianSetInfo { - addresses, - expiration_time: 0, - } - } - - #[test] - fn quardian_set_quorum() { - assert_eq!(build_guardian_set(1).quorum(), 1); - assert_eq!(build_guardian_set(2).quorum(), 2); - assert_eq!(build_guardian_set(3).quorum(), 3); - assert_eq!(build_guardian_set(4).quorum(), 3); - assert_eq!(build_guardian_set(5).quorum(), 4); - assert_eq!(build_guardian_set(6).quorum(), 5); - assert_eq!(build_guardian_set(7).quorum(), 5); - assert_eq!(build_guardian_set(8).quorum(), 6); - assert_eq!(build_guardian_set(9).quorum(), 7); - assert_eq!(build_guardian_set(10).quorum(), 7); - assert_eq!(build_guardian_set(11).quorum(), 8); - assert_eq!(build_guardian_set(12).quorum(), 9); - assert_eq!(build_guardian_set(20).quorum(), 14); - assert_eq!(build_guardian_set(25).quorum(), 17); - assert_eq!(build_guardian_set(100).quorum(), 67); - } - - #[test] - fn test_deserialize() { - let x = hex::decode("080000000901007bfa71192f886ab6819fa4862e34b4d178962958d9b2e3d9437338c9e5fde1443b809d2886eaa69e0f0158ea517675d96243c9209c3fe1d94d5b19866654c6980000000b150000000500020001020304000000000000000000000000000000000000000000000000000000000000000000000a0261626364").unwrap(); - let v = ParsedVAA::deserialize(x.as_slice()).unwrap(); - assert_eq!( - v, - ParsedVAA { - version: 8, - guardian_set_index: 9, - timestamp: 2837, - nonce: 5, - len_signers: 1, - emitter_chain: 2, - emitter_address: vec![ - 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0 - ], - sequence: 10, - consistency_level: 2, - payload: vec![97, 98, 99, 100], - hash: vec![ - 195, 10, 19, 96, 8, 61, 218, 69, 160, 238, 165, 142, 105, 119, 139, 121, 212, - 73, 238, 179, 13, 80, 245, 224, 75, 110, 163, 8, 185, 132, 55, 34 - ] - } - ); - } -} diff --git a/terra/contracts/cw20-wrapped/Cargo.toml b/terra/contracts/cw20-wrapped/Cargo.toml index 6b153b06..f35f2042 100644 --- a/terra/contracts/cw20-wrapped/Cargo.toml +++ b/terra/contracts/cw20-wrapped/Cargo.toml @@ -14,13 +14,15 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { version = "0.10.0" } -cosmwasm-storage = { version = "0.10.0" } -schemars = "0.7" +cosmwasm-std = { version = "0.16.0" } +cosmwasm-storage = { version = "0.16.0" } +schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw20 = "0.2.0" -cw20-base = { version = "0.2.0", features = ["library"] } +cw2 = { version = "0.8.0" } +cw20 = { version = "0.8.0" } +cw20-legacy = { version = "0.2.0", features = ["library"]} +cw-storage-plus = { version = "0.8.0" } thiserror = { version = "1.0.20" } [dev-dependencies] -cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] } \ No newline at end of file +cosmwasm-vm = { version = "0.16.0", default-features = false } diff --git a/terra/contracts/cw20-wrapped/src/contract.rs b/terra/contracts/cw20-wrapped/src/contract.rs index 4c053b77..11ca72ae 100644 --- a/terra/contracts/cw20-wrapped/src/contract.rs +++ b/terra/contracts/cw20-wrapped/src/contract.rs @@ -1,48 +1,47 @@ use cosmwasm_std::{ + entry_point, to_binary, - Api, Binary, CosmosMsg, + Deps, + DepsMut, Env, - Extern, - HandleResponse, - HumanAddr, - InitResponse, - Querier, + MessageInfo, + Response, StdError, StdResult, - Storage, Uint128, WasmMsg, }; -use cw20_base::{ +use cw2::set_contract_version; +use cw20_legacy::{ allowances::{ - handle_burn_from, - handle_decrease_allowance, - handle_increase_allowance, - handle_send_from, - handle_transfer_from, + execute_burn_from, + execute_decrease_allowance, + execute_increase_allowance, + execute_send_from, + execute_transfer_from, query_allowance, }, contract::{ - handle_mint, - handle_send, - handle_transfer, + execute_mint, + execute_send, + execute_transfer, query_balance, }, state::{ - token_info, - token_info_read, MinterData, TokenInfo, + TOKEN_INFO, }, + ContractError, }; use crate::{ msg::{ - HandleMsg, - InitMsg, + ExecuteMsg, + InstantiateMsg, QueryMsg, WrappedAssetInfoResponse, }, @@ -55,116 +54,137 @@ use crate::{ use cw20::TokenInfoResponse; use std::string::String; -pub fn init( - deps: &mut Extern, +type HumanAddr = String; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw20-base"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, env: Env, - msg: InitMsg, -) -> StdResult { + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + // store token info using cw20-base format let data = TokenInfo { name: msg.name, symbol: msg.symbol, decimals: msg.decimals, - total_supply: Uint128(0), + total_supply: Uint128::new(0), // set creator as minter mint: Some(MinterData { - minter: deps.api.canonical_address(&env.message.sender)?, + minter: deps.api.addr_canonicalize(&info.sender.as_str())?, cap: None, }), }; - token_info(&mut deps.storage).save(&data)?; + TOKEN_INFO.save(deps.storage, &data)?; // save wrapped asset info let data = WrappedAssetInfo { asset_chain: msg.asset_chain, asset_address: msg.asset_address, - bridge: deps.api.canonical_address(&env.message.sender)?, + bridge: deps.api.addr_canonicalize(&info.sender.as_str())?, }; - wrapped_asset_info(&mut deps.storage).save(&data)?; + wrapped_asset_info(deps.storage).save(&data)?; if let Some(mint_info) = msg.mint { - handle_mint(deps, env, mint_info.recipient, mint_info.amount)?; + execute_mint(deps, env, info, mint_info.recipient, mint_info.amount) + .map_err(|e| StdError::generic_err(format!("{}", e)))?; } if let Some(hook) = msg.init_hook { - Ok(InitResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { + Ok( + Response::new().add_message(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: hook.contract_addr, msg: hook.msg, - send: vec![], - })], - log: vec![], - }) + funds: vec![], + })), + ) } else { - Ok(InitResponse::default()) + Ok(Response::default()) } } -pub fn handle( - deps: &mut Extern, +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, env: Env, - msg: HandleMsg, -) -> StdResult { + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { match msg { // these all come from cw20-base to implement the cw20 standard - HandleMsg::Transfer { recipient, amount } => { - Ok(handle_transfer(deps, env, recipient, amount)?) + ExecuteMsg::Transfer { recipient, amount } => { + Ok(execute_transfer(deps, env, info, recipient, amount)?) } - HandleMsg::Burn { account, amount } => Ok(handle_burn_from(deps, env, account, amount)?), - HandleMsg::Send { + ExecuteMsg::Burn { account, amount } => { + Ok(execute_burn_from(deps, env, info, account, amount)?) + } + ExecuteMsg::Send { contract, amount, msg, - } => Ok(handle_send(deps, env, contract, amount, msg)?), - HandleMsg::Mint { recipient, amount } => handle_mint_wrapped(deps, env, recipient, amount), - HandleMsg::IncreaseAllowance { + } => Ok(execute_send(deps, env, info, contract, amount, msg)?), + ExecuteMsg::Mint { recipient, amount } => { + execute_mint_wrapped(deps, env, info, recipient, amount) + } + ExecuteMsg::IncreaseAllowance { spender, amount, expires, - } => Ok(handle_increase_allowance( - deps, env, spender, amount, expires, + } => Ok(execute_increase_allowance( + deps, env, info, spender, amount, expires, )?), - HandleMsg::DecreaseAllowance { + ExecuteMsg::DecreaseAllowance { spender, amount, expires, - } => Ok(handle_decrease_allowance( - deps, env, spender, amount, expires, + } => Ok(execute_decrease_allowance( + deps, env, info, spender, amount, expires, )?), - HandleMsg::TransferFrom { + ExecuteMsg::TransferFrom { owner, recipient, amount, - } => Ok(handle_transfer_from(deps, env, owner, recipient, amount)?), - HandleMsg::BurnFrom { owner, amount } => Ok(handle_burn_from(deps, env, owner, amount)?), - HandleMsg::SendFrom { + } => Ok(execute_transfer_from( + deps, env, info, owner, recipient, amount, + )?), + ExecuteMsg::BurnFrom { owner, amount } => { + Ok(execute_burn_from(deps, env, info, owner, amount)?) + } + ExecuteMsg::SendFrom { owner, contract, amount, msg, - } => Ok(handle_send_from(deps, env, owner, contract, amount, msg)?), + } => Ok(execute_send_from( + deps, env, info, owner, contract, amount, msg, + )?), } } -fn handle_mint_wrapped( - deps: &mut Extern, +fn execute_mint_wrapped( + deps: DepsMut, env: Env, + info: MessageInfo, recipient: HumanAddr, amount: Uint128, -) -> StdResult { +) -> Result { // Only bridge can mint - let wrapped_info = wrapped_asset_info_read(&deps.storage).load()?; - if wrapped_info.bridge != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); + let wrapped_info = wrapped_asset_info_read(deps.storage).load()?; + if wrapped_info.bridge != deps.api.addr_canonicalize(&info.sender.as_str())? { + return Err(ContractError::Unauthorized {}); } - Ok(handle_mint(deps, env, recipient, amount)?) + Ok(execute_mint(deps, env, info, recipient, amount)?) } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::WrappedAssetInfo {} => to_binary(&query_wrapped_asset_info(deps)?), // inherited from cw20-base @@ -176,66 +196,58 @@ pub fn query( } } -pub fn query_token_info( - deps: &Extern, -) -> StdResult { - let info = token_info_read(&deps.storage).load()?; - let res = TokenInfoResponse { +pub fn query_token_info(deps: Deps) -> StdResult { + let info = TOKEN_INFO.load(deps.storage)?; + Ok(TokenInfoResponse { name: String::from("Wormhole:") + info.name.as_str(), symbol: String::from("wh") + info.symbol.as_str(), decimals: info.decimals, total_supply: info.total_supply, - }; - Ok(res) + }) } -pub fn query_wrapped_asset_info( - deps: &Extern, -) -> StdResult { - let info = wrapped_asset_info_read(&deps.storage).load()?; - let res = WrappedAssetInfoResponse { +pub fn query_wrapped_asset_info(deps: Deps) -> StdResult { + let info = wrapped_asset_info_read(deps.storage).load()?; + Ok(WrappedAssetInfoResponse { asset_chain: info.asset_chain, asset_address: info.asset_address, - bridge: deps.api.human_address(&info.bridge)?, - }; - Ok(res) + bridge: deps.api.addr_humanize(&info.bridge)?, + }) } #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::{ - testing::{ - mock_dependencies, - mock_env, - }, - HumanAddr, + use cosmwasm_std::testing::{ + mock_dependencies, + mock_env, + mock_info, }; use cw20::TokenInfoResponse; const CANONICAL_LENGTH: usize = 20; - fn get_balance>( - deps: &Extern, - address: T, - ) -> Uint128 { - query_balance(&deps, address.into()).unwrap().balance + fn get_balance(deps: Deps, address: HumanAddr) -> Uint128 { + query_balance(deps, address.into()).unwrap().balance } - fn do_init(deps: &mut Extern, creator: &HumanAddr) { - let init_msg = InitMsg { + fn do_init(mut deps: DepsMut, creator: &HumanAddr) { + let init_msg = InstantiateMsg { + name: "Integers".to_string(), + symbol: "INT".to_string(), asset_chain: 1, asset_address: vec![1; 32].into(), decimals: 10, mint: None, init_hook: None, }; - let env = mock_env(creator, &[]); - let res = init(deps, env, init_msg).unwrap(); + let env = mock_env(); + let info = mock_info(creator, &[]); + let res = instantiate(deps, env, info, init_msg).unwrap(); assert_eq!(0, res.messages.len()); assert_eq!( - query_token_info(&deps).unwrap(), + query_token_info(deps.as_ref()).unwrap(), TokenInfoResponse { name: "Wormhole Wrapped".to_string(), symbol: "WWT".to_string(), @@ -245,35 +257,36 @@ mod tests { ); assert_eq!( - query_wrapped_asset_info(&deps).unwrap(), + query_wrapped_asset_info(deps.as_ref()).unwrap(), WrappedAssetInfoResponse { asset_chain: 1, asset_address: vec![1; 32].into(), - bridge: creator.clone(), + bridge: deps.api.addr_validate(creator).unwrap(), } ); } - fn do_init_and_mint( - deps: &mut Extern, + fn do_init_and_mint( + mut deps: DepsMut, creator: &HumanAddr, mint_to: &HumanAddr, amount: Uint128, ) { do_init(deps, creator); - let msg = HandleMsg::Mint { + let msg = ExecuteMsg::Mint { recipient: mint_to.clone(), amount, }; - let env = mock_env(&creator, &[]); - let res = handle(deps, env, msg.clone()).unwrap(); + let env = mock_env(); + let info = mock_info(creator, &[]); + let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap(); assert_eq!(0, res.messages.len()); - assert_eq!(get_balance(deps, mint_to), amount); + assert_eq!(get_balance(deps.as_ref(), mint_to.clone(),), amount); assert_eq!( - query_token_info(&deps).unwrap(), + query_token_info(deps.as_ref()).unwrap(), TokenInfoResponse { name: "Wormhole Wrapped".to_string(), symbol: "WWT".to_string(), @@ -285,29 +298,30 @@ mod tests { #[test] fn can_mint_by_minter() { - let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]); + let mut deps = mock_dependencies(&[]); let minter = HumanAddr::from("minter"); let recipient = HumanAddr::from("recipient"); - let amount = Uint128(222_222_222); - do_init_and_mint(&mut deps, &minter, &recipient, amount); + let amount = Uint128::new(222_222_222); + do_init_and_mint(deps.as_mut(), &minter, &recipient, amount); } #[test] fn others_cannot_mint() { - let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]); + let mut deps = mock_dependencies(&[]); let minter = HumanAddr::from("minter"); let recipient = HumanAddr::from("recipient"); - do_init(&mut deps, &minter); + do_init(deps.as_mut(), &minter); - let amount = Uint128(222_222_222); - let msg = HandleMsg::Mint { + let amount = Uint128::new(222_222_222); + let msg = ExecuteMsg::Mint { recipient: recipient.clone(), amount, }; let other_address = HumanAddr::from("other"); - let env = mock_env(&other_address, &[]); - let res = handle(&mut deps, env, msg); + let env = mock_env(); + let info = mock_info(&other_address, &[]); + let res = execute(deps.as_mut(), env, info, msg); assert_eq!( format!("{}", res.unwrap_err()), format!("{}", crate::error::ContractError::Unauthorized {}) @@ -316,44 +330,46 @@ mod tests { #[test] fn transfer_balance_success() { - let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]); + let mut deps = mock_dependencies(&[]); let minter = HumanAddr::from("minter"); let owner = HumanAddr::from("owner"); - let amount_initial = Uint128(222_222_222); - do_init_and_mint(&mut deps, &minter, &owner, amount_initial); + let amount_initial = Uint128::new(222_222_222); + do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial); // Transfer let recipient = HumanAddr::from("recipient"); - let amount_transfer = Uint128(222_222); - let msg = HandleMsg::Transfer { + let amount_transfer = Uint128::new(222_222); + let msg = ExecuteMsg::Transfer { recipient: recipient.clone(), amount: amount_transfer, }; - let env = mock_env(&owner, &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap(); + let env = mock_env(); + let info = mock_info(&owner, &[]); + let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap(); assert_eq!(0, res.messages.len()); - assert_eq!(get_balance(&deps, owner), Uint128(222_000_000)); - assert_eq!(get_balance(&deps, recipient), amount_transfer); + assert_eq!(get_balance(deps.as_ref(), owner), Uint128::new(222_000_000)); + assert_eq!(get_balance(deps.as_ref(), recipient), amount_transfer); } #[test] fn transfer_balance_not_enough() { - let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]); + let mut deps = mock_dependencies(&[]); let minter = HumanAddr::from("minter"); let owner = HumanAddr::from("owner"); - let amount_initial = Uint128(222_221); - do_init_and_mint(&mut deps, &minter, &owner, amount_initial); + let amount_initial = Uint128::new(222_221); + do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial); // Transfer let recipient = HumanAddr::from("recipient"); - let amount_transfer = Uint128(222_222); - let msg = HandleMsg::Transfer { + let amount_transfer = Uint128::new(222_222); + let msg = ExecuteMsg::Transfer { recipient: recipient.clone(), amount: amount_transfer, }; - let env = mock_env(&owner, &[]); - let _ = handle(&mut deps, env, msg.clone()).unwrap_err(); // Will panic if no error + let env = mock_env(); + let info = mock_info(&owner, &[]); + let _ = execute(deps.as_mut(), env, info, msg.clone()).unwrap_err(); // Will panic if no error } } diff --git a/terra/contracts/cw20-wrapped/src/lib.rs b/terra/contracts/cw20-wrapped/src/lib.rs index e8a83276..6e2ebd88 100644 --- a/terra/contracts/cw20-wrapped/src/lib.rs +++ b/terra/contracts/cw20-wrapped/src/lib.rs @@ -1,9 +1,7 @@ -pub mod contract; mod error; + +pub mod contract; pub mod msg; pub mod state; pub use crate::error::ContractError; - -#[cfg(all(target_arch = "wasm32", not(feature = "library")))] -cosmwasm_std::create_entry_points!(contract); diff --git a/terra/contracts/cw20-wrapped/src/msg.rs b/terra/contracts/cw20-wrapped/src/msg.rs index 31c8cd6e..25ca6377 100644 --- a/terra/contracts/cw20-wrapped/src/msg.rs +++ b/terra/contracts/cw20-wrapped/src/msg.rs @@ -6,14 +6,16 @@ use serde::{ }; use cosmwasm_std::{ + Addr, Binary, - HumanAddr, Uint128, }; use cw20::Expiration; +type HumanAddr = String; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { +pub struct InstantiateMsg { pub name: String, pub symbol: String, pub asset_chain: u16, @@ -37,7 +39,7 @@ pub struct InitMint { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum HandleMsg { +pub enum ExecuteMsg { /// Implements CW20. Transfer is a base message to move tokens to another account without triggering actions Transfer { recipient: HumanAddr, @@ -50,7 +52,7 @@ pub enum HandleMsg { Send { contract: HumanAddr, amount: Uint128, - msg: Option, + msg: Binary, }, /// Implements CW20 "mintable" extension. If authorized, creates amount new tokens /// and adds to the recipient balance. @@ -87,7 +89,7 @@ pub enum HandleMsg { owner: HumanAddr, contract: HumanAddr, amount: Uint128, - msg: Option, + msg: Binary, }, /// Implements CW20 "approval" extension. Destroys tokens forever BurnFrom { owner: HumanAddr, amount: Uint128 }, @@ -116,5 +118,5 @@ pub enum QueryMsg { pub struct WrappedAssetInfoResponse { pub asset_chain: u16, // Asset chain id pub asset_address: Binary, // Asset smart contract address in the original chain - pub bridge: HumanAddr, // Bridge address, authorized to mint and burn wrapped tokens + pub bridge: Addr, // Bridge address, authorized to mint and burn wrapped tokens } diff --git a/terra/contracts/cw20-wrapped/src/state.rs b/terra/contracts/cw20-wrapped/src/state.rs index 382f7d83..2d9ba10f 100644 --- a/terra/contracts/cw20-wrapped/src/state.rs +++ b/terra/contracts/cw20-wrapped/src/state.rs @@ -7,7 +7,6 @@ use serde::{ use cosmwasm_std::{ Binary, CanonicalAddr, - ReadonlyStorage, Storage, }; use cosmwasm_storage::{ @@ -27,12 +26,12 @@ pub struct WrappedAssetInfo { pub bridge: CanonicalAddr, // Bridge address, authorized to mint and burn wrapped tokens } -pub fn wrapped_asset_info(storage: &mut S) -> Singleton { +pub fn wrapped_asset_info(storage: &mut dyn Storage) -> Singleton { singleton(storage, KEY_WRAPPED_ASSET) } -pub fn wrapped_asset_info_read( - storage: &S, -) -> ReadonlySingleton { +pub fn wrapped_asset_info_read( + storage: &dyn Storage, +) -> ReadonlySingleton { singleton_read(storage, KEY_WRAPPED_ASSET) } diff --git a/terra/contracts/token-bridge/Cargo.toml b/terra/contracts/token-bridge/Cargo.toml index a0df81ec..7465bd1d 100644 --- a/terra/contracts/token-bridge/Cargo.toml +++ b/terra/contracts/token-bridge/Cargo.toml @@ -14,18 +14,17 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { version = "0.10.0" } -cosmwasm-storage = { version = "0.10.0" } -schemars = "0.7" +cosmwasm-std = { version = "0.16.0" } +cosmwasm-storage = { version = "0.16.0" } +schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw20 = "0.2.2" -cw20-base = { version = "0.2.2", features = ["library"] } +cw20 = "0.8.0" +cw20-base = { version = "0.8.0", features = ["library"] } cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] } -terraswap = "1.1.0" +terraswap = "2.4.0" wormhole = { path = "../wormhole", features = ["library"] } - thiserror = { version = "1.0.20" } -k256 = { version = "0.5.9", default-features = false, features = ["ecdsa"] } +k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] } sha3 = { version = "0.9.1", default-features = false } generic-array = { version = "0.14.4" } hex = "0.4.2" @@ -33,5 +32,5 @@ lazy_static = "1.4.0" bigint = "4" [dev-dependencies] -cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] } -serde_json = "1.0" \ No newline at end of file +cosmwasm-vm = { version = "0.16.0", default-features = false } +serde_json = "1.0" diff --git a/terra/contracts/token-bridge/src/contract.rs b/terra/contracts/token-bridge/src/contract.rs index 3ee6de0e..298f3ba7 100644 --- a/terra/contracts/token-bridge/src/contract.rs +++ b/terra/contracts/token-bridge/src/contract.rs @@ -1,24 +1,22 @@ use crate::msg::WrappedRegistryResponse; use cosmwasm_std::{ coin, - log, + entry_point, to_binary, - Api, BankMsg, Binary, CanonicalAddr, Coin, CosmosMsg, + Deps, + DepsMut, + Empty, Env, - Extern, - HandleResponse, - HumanAddr, - InitResponse, - Querier, + MessageInfo, QueryRequest, + Response, StdError, StdResult, - Storage, Uint128, WasmMsg, WasmQuery, @@ -26,16 +24,16 @@ use cosmwasm_std::{ use crate::{ msg::{ - HandleMsg, - InitMsg, + ExecuteMsg, + InstantiateMsg, QueryMsg, }, state::{ bridge_contracts, bridge_contracts_read, + bridge_deposit, config, config_read, - bridge_deposit, receive_native, send_native, wrapped_asset, @@ -61,12 +59,12 @@ use wormhole::{ }; use cw20_base::msg::{ - HandleMsg as TokenMsg, + ExecuteMsg as TokenMsg, QueryMsg as TokenQuery, }; use wormhole::msg::{ - HandleMsg as WormholeHandleMsg, + ExecuteMsg as WormholeExecuteMsg, QueryMsg as WormholeQueryMsg, }; @@ -80,9 +78,9 @@ use wormhole::state::{ use cw20::TokenInfoResponse; use cw20_wrapped::msg::{ - HandleMsg as WrappedMsg, + ExecuteMsg as WrappedMsg, InitHook, - InitMsg as WrappedInit, + InstantiateMsg as WrappedInit, QueryMsg as WrappedQuery, WrappedAssetInfoResponse, }; @@ -100,16 +98,20 @@ use std::cmp::{ min, }; +type HumanAddr = String; + // Chain ID of Terra const CHAIN_ID: u16 = 3; const WRAPPED_ASSET_UPDATING: &str = "updating"; -pub fn init( - deps: &mut Extern, +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, _env: Env, - msg: InitMsg, -) -> StdResult { + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { // Save general wormhole info let state = ConfigInfo { gov_chain: msg.gov_chain, @@ -117,15 +119,12 @@ pub fn init( wormhole_contract: msg.wormhole_contract, wrapped_asset_code_id: msg.wrapped_asset_code_id, }; - config(&mut deps.storage).save(&state)?; + config(deps.storage).save(&state)?; - Ok(InitResponse::default()) + Ok(Response::default()) } -pub fn coins_after_tax( - deps: &mut Extern, - coins: Vec, -) -> StdResult> { +pub fn coins_after_tax(deps: DepsMut, coins: Vec) -> StdResult> { let mut res = vec![]; for coin in coins { let asset = Asset { @@ -134,17 +133,13 @@ pub fn coins_after_tax( denom: coin.denom.clone(), }, }; - res.push(asset.deduct_tax(&deps)?); + res.push(asset.deduct_tax(&deps.querier)?); } Ok(res) } -pub fn parse_vaa( - deps: &mut Extern, - block_time: u64, - data: &Binary, -) -> StdResult { - let cfg = config_read(&deps.storage).load()?; +pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult { + let cfg = config_read(deps.storage).load()?; let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: cfg.wormhole_contract.clone(), msg: to_binary(&WormholeQueryMsg::VerifyVAA { @@ -155,16 +150,13 @@ pub fn parse_vaa( Ok(vaa) } -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { match msg { - HandleMsg::RegisterAssetHook { asset_id } => { - handle_register_asset(deps, env, &asset_id.as_slice()) + ExecuteMsg::RegisterAssetHook { asset_id } => { + handle_register_asset(deps, env, info, &asset_id.as_slice()) } - HandleMsg::InitiateTransfer { + ExecuteMsg::InitiateTransfer { asset, recipient_chain, recipient, @@ -173,126 +165,110 @@ pub fn handle( } => handle_initiate_transfer( deps, env, + info, asset, recipient_chain, recipient.as_slice().to_vec(), fee, nonce, ), - HandleMsg::DepositTokens {} => deposit_tokens(deps, env), - HandleMsg::WithdrawTokens { asset } => withdraw_tokens(deps, env, asset), - HandleMsg::SubmitVaa { data } => submit_vaa(deps, env, &data), - HandleMsg::CreateAssetMeta { - asset_info, - nonce, - } => handle_create_asset_meta(deps, env, asset_info, nonce), + ExecuteMsg::DepositTokens {} => deposit_tokens(deps, env, info), + ExecuteMsg::WithdrawTokens { asset } => withdraw_tokens(deps, env, info, asset), + ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data), + ExecuteMsg::CreateAssetMeta { asset_info, nonce } => { + handle_create_asset_meta(deps, env, info, asset_info, nonce) + } } } -fn deposit_tokens( - deps: &mut Extern, - env: Env, -) -> StdResult { - for coin in env.message.sent_funds { +fn deposit_tokens(deps: DepsMut, _env: Env, info: MessageInfo) -> StdResult { + for coin in info.funds { let asset = Asset { amount: coin.amount.clone(), info: AssetInfo::NativeToken { denom: coin.denom.clone(), }, }; - let deducted_amount = asset.deduct_tax(&deps)?.amount; - let deposit_key = format!("{}:{}", env.message.sender, coin.denom); - bridge_deposit(&mut deps.storage).update(deposit_key.as_bytes(), |amount: Option| - Ok(amount.unwrap_or(Uint128(0)) + deducted_amount) + let deducted_amount = asset.deduct_tax(&deps.querier)?.amount; + let deposit_key = format!("{}:{}", info.sender, coin.denom); + bridge_deposit(deps.storage).update( + deposit_key.as_bytes(), + |amount: Option| -> StdResult { + Ok(amount.unwrap_or(Uint128::new(0)) + deducted_amount) + }, )?; } - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "deposit_tokens"), - ], - data: None, - }) + Ok(Response::new().add_attribute("action", "deposit_tokens")) } -fn withdraw_tokens( - deps: &mut Extern, - env: Env, +fn withdraw_tokens( + deps: DepsMut, + _env: Env, + info: MessageInfo, data: AssetInfo, -) -> StdResult { +) -> StdResult { let mut messages: Vec = vec![]; if let AssetInfo::NativeToken { denom } = data { - let deposit_key = format!("{}:{}", env.message.sender, denom); - bridge_deposit(&mut deps.storage).update(deposit_key.as_bytes(), |current: Option| { - match current { + let deposit_key = format!("{}:{}", info.sender, denom); + bridge_deposit(deps.storage).update( + deposit_key.as_bytes(), + |current: Option| match current { Some(v) => { messages.push(CosmosMsg::Bank(BankMsg::Send { - from_address: env.contract.address.clone(), - to_address: env.message.sender.clone(), + to_address: info.sender.to_string(), amount: vec![coin(v.u128(), &denom)], })); - Ok(Uint128(0)) + Ok(Uint128::new(0)) } - None => Err(StdError::generic_err("no deposit found to withdraw")) - } - })?; + None => Err(StdError::generic_err("no deposit found to withdraw")), + }, + )?; } - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "withdraw_tokens"), - ], - data: None, - }) + Ok(Response::new().add_attribute("action", "withdraw_tokens")) } /// Handle wrapped asset registration messages -fn handle_register_asset( - deps: &mut Extern, - env: Env, +fn handle_register_asset( + deps: DepsMut, + _env: Env, + info: MessageInfo, asset_id: &[u8], -) -> StdResult { - let mut bucket = wrapped_asset(&mut deps.storage); +) -> StdResult { + let mut bucket = wrapped_asset(deps.storage); let result = bucket.load(asset_id); let result = result.map_err(|_| ContractError::RegistrationForbidden.std())?; if result != HumanAddr::from(WRAPPED_ASSET_UPDATING) { return ContractError::AssetAlreadyRegistered.std_err(); } - bucket.save(asset_id, &env.message.sender)?; + bucket.save(asset_id, &info.sender.to_string())?; - let contract_address: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - wrapped_asset_address(&mut deps.storage) - .save(contract_address.as_slice(), &asset_id.to_vec())?; + let contract_address: CanonicalAddr = deps.api.addr_canonicalize(&info.sender.as_str())?; + wrapped_asset_address(deps.storage).save(contract_address.as_slice(), &asset_id.to_vec())?; - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "register_asset"), - log("asset_id", format!("{:?}", asset_id)), - log("contract_addr", env.message.sender), - ], - data: None, - }) + Ok(Response::new() + .add_attribute("action", "register_asset") + .add_attribute("asset_id", format!("{:?}", asset_id)) + .add_attribute("contract_addr", info.sender)) } -fn handle_attest_meta( - deps: &mut Extern, +fn handle_attest_meta( + deps: DepsMut, env: Env, emitter_chain: u16, emitter_address: Vec, data: &Vec, -) -> StdResult { +) -> StdResult { let meta = AssetMeta::deserialize(data)?; let expected_contract = - bridge_contracts_read(&deps.storage).load(&emitter_chain.to_be_bytes())?; + bridge_contracts_read(deps.storage).load(&emitter_chain.to_be_bytes())?; // must be sent by a registered token bridge contract if expected_contract != emitter_address { - return Err(StdError::unauthorized()); + return Err(StdError::generic_err("invalid emitter")); } if CHAIN_ID == meta.token_chain { @@ -301,22 +277,20 @@ fn handle_attest_meta( )); } - let cfg = config_read(&deps.storage).load()?; + let cfg = config_read(deps.storage).load()?; let asset_id = build_asset_id(meta.token_chain, &meta.token_address.as_slice()); - if wrapped_asset_read(&mut deps.storage) - .load(&asset_id) - .is_ok() - { + if wrapped_asset_read(deps.storage).load(&asset_id).is_ok() { return Err(StdError::generic_err( "this asset has already been attested", )); } - wrapped_asset(&mut deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?; + wrapped_asset(deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?; - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Instantiate { + Ok( + Response::new().add_message(CosmosMsg::Wasm(WasmMsg::Instantiate { + admin: None, code_id: cfg.wrapped_asset_code_id, msg: to_binary(&WrappedInit { name: get_string_from_32(&meta.name)?, @@ -326,56 +300,50 @@ fn handle_attest_meta( decimals: min(meta.decimals, 8u8), mint: None, init_hook: Some(InitHook { - contract_addr: env.contract.address, - msg: to_binary(&HandleMsg::RegisterAssetHook { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&ExecuteMsg::RegisterAssetHook { asset_id: asset_id.to_vec().into(), })?, }), })?, - send: vec![], - label: None, - })], - log: vec![], - data: None, - }) + funds: vec![], + label: String::new(), + })), + ) } -fn handle_create_asset_meta( - deps: &mut Extern, +fn handle_create_asset_meta( + deps: DepsMut, env: Env, + info: MessageInfo, asset_info: AssetInfo, nonce: u32, -) -> StdResult { +) -> StdResult { match asset_info { - AssetInfo::Token { contract_addr } => handle_create_asset_meta_token( - deps, - env, - contract_addr, - nonce, - ), - AssetInfo::NativeToken { ref denom } => handle_create_asset_meta_native_token( - deps, - env, - denom.clone(), - nonce, - ) + AssetInfo::Token { contract_addr } => { + handle_create_asset_meta_token(deps, env, info, contract_addr, nonce) + } + AssetInfo::NativeToken { ref denom } => { + handle_create_asset_meta_native_token(deps, env, info, denom.clone(), nonce) + } } } -fn handle_create_asset_meta_token( - deps: &mut Extern, +fn handle_create_asset_meta_token( + deps: DepsMut, env: Env, + info: MessageInfo, asset_address: HumanAddr, nonce: u32, -) -> StdResult { - let cfg = config_read(&deps.storage).load()?; +) -> StdResult { + let cfg = config_read(deps.storage).load()?; let request = QueryRequest::Wasm(WasmQuery::Smart { contract_addr: asset_address.clone(), msg: to_binary(&TokenQuery::TokenInfo {})?, }); - let asset_canonical = deps.api.canonical_address(&asset_address)?; + let asset_canonical = deps.api.addr_canonicalize(&asset_address)?; let token_info: TokenInfoResponse = deps.querier.query(&request)?; let meta: AssetMeta = AssetMeta { @@ -391,49 +359,33 @@ fn handle_create_asset_meta_token( payload: meta.serialize().to_vec(), }; - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { + Ok(Response::new() + .add_message(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: cfg.wormhole_contract, - msg: to_binary(&WormholeHandleMsg::PostMessage { + msg: to_binary(&WormholeExecuteMsg::PostMessage { message: Binary::from(token_bridge_message.serialize()), nonce, })?, // forward coins sent to this message - send: coins_after_tax(deps, env.message.sent_funds.clone())?, - })], - log: vec![ - log("meta.token_chain", CHAIN_ID), - log("meta.token", asset_address), - log("meta.nonce", nonce), - log("meta.block_time", env.block.time), - ], - data: None, - }) + funds: coins_after_tax(deps, info.funds.clone())?, + })) + .add_attribute("meta.token_chain", CHAIN_ID.to_string()) + .add_attribute("meta.token", asset_address) + .add_attribute("meta.nonce", nonce.to_string()) + .add_attribute("meta.block_time", env.block.time.seconds().to_string())) } -/// All ISO-4217 currency codes are 3 letters, so we can safely slice anything that is not ULUNA. -/// https://www.xe.com/iso4217.php -fn format_native_denom_symbol(denom: &str) -> String { - if denom == "uluna" { - return "LUNA".to_string(); - } - // UUSD -> US -> UST - denom.to_uppercase()[1..3].to_string() + "T" -} - -fn handle_create_asset_meta_native_token( - deps: &mut Extern, +fn handle_create_asset_meta_native_token( + deps: DepsMut, env: Env, + info: MessageInfo, denom: String, nonce: u32, -) -> StdResult { - let cfg = config_read(&deps.storage).load()?; - +) -> StdResult { + let cfg = config_read(deps.storage).load()?; let mut asset_id = extend_address_to_32(&build_native_id(&denom).into()); asset_id[0] = 1; - let symbol = format_native_denom_symbol(&denom); - let meta: AssetMeta = AssetMeta { token_chain: CHAIN_ID, token_address: asset_id.clone(), @@ -441,47 +393,42 @@ fn handle_create_asset_meta_native_token( symbol: extend_string_to_32(&symbol)?, name: extend_string_to_32(&symbol)?, }; - let token_bridge_message = TokenBridgeMessage { action: Action::ATTEST_META, payload: meta.serialize().to_vec(), }; - - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { + Ok(Response::new() + .add_message(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: cfg.wormhole_contract, - msg: to_binary(&WormholeHandleMsg::PostMessage { + msg: to_binary(&WormholeExecuteMsg::PostMessage { message: Binary::from(token_bridge_message.serialize()), nonce, })?, // forward coins sent to this message - send: coins_after_tax(deps, env.message.sent_funds.clone())?, - })], - log: vec![ - log("meta.token_chain", CHAIN_ID), - log("meta.symbol", symbol), - log("meta.asset_id", hex::encode(asset_id)), - log("meta.nonce", nonce), - log("meta.block_time", env.block.time), - ], - data: None, - }) + funds: coins_after_tax(deps, info.funds.clone())?, + })) + .add_attribute("meta.token_chain", CHAIN_ID.to_string()) + .add_attribute("meta.symbol", symbol) + .add_attribute("meta.asset_id", hex::encode(asset_id)) + .add_attribute("meta.nonce", nonce.to_string()) + .add_attribute("meta.block_time", env.block.time.seconds().to_string())) } -fn submit_vaa( - deps: &mut Extern, +fn submit_vaa( + mut deps: DepsMut, env: Env, + info: MessageInfo, data: &Binary, -) -> StdResult { - let state = config_read(&deps.storage).load()?; +) -> StdResult { + let state = config_read(deps.storage).load()?; - let vaa = parse_vaa(deps, env.block.time, data)?; + let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?; let data = vaa.payload; - if vaa_archive_check(&deps.storage, vaa.hash.as_slice()) { + if vaa_archive_check(deps.storage, vaa.hash.as_slice()) { return ContractError::VaaAlreadyExecuted.std_err(); } - vaa_archive_add(&mut deps.storage, vaa.hash.as_slice())?; + vaa_archive_add(deps.storage, vaa.hash.as_slice())?; // check if vaa is from governance if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address { @@ -490,10 +437,11 @@ fn submit_vaa( let message = TokenBridgeMessage::deserialize(&data)?; - let result = match message.action { + match message.action { Action::TRANSFER => handle_complete_transfer( deps, env, + info, vaa.emitter_chain, vaa.emitter_address, &message.payload, @@ -506,15 +454,10 @@ fn submit_vaa( &message.payload, ), _ => ContractError::InvalidVAAAction.std_err(), - }; - return result; + } } -fn handle_governance_payload( - deps: &mut Extern, - env: Env, - data: &Vec, -) -> StdResult { +fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec) -> StdResult { let gov_packet = GovernancePacket::deserialize(&data)?; let module = get_string_from_32(&gov_packet.module)?; @@ -534,77 +477,67 @@ fn handle_governance_payload( } } -fn handle_register_chain( - deps: &mut Extern, - _env: Env, - data: &Vec, -) -> StdResult { +fn handle_register_chain(deps: DepsMut, _env: Env, data: &Vec) -> StdResult { let RegisterChain { chain_id, chain_address, } = RegisterChain::deserialize(&data)?; - let existing = bridge_contracts_read(&deps.storage).load(&chain_id.to_be_bytes()); + let existing = bridge_contracts_read(deps.storage).load(&chain_id.to_be_bytes()); if existing.is_ok() { return Err(StdError::generic_err( "bridge contract already exists for this chain", )); } - let mut bucket = bridge_contracts(&mut deps.storage); + let mut bucket = bridge_contracts(deps.storage); bucket.save(&chain_id.to_be_bytes(), &chain_address)?; - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("chain_id", chain_id), - log("chain_address", hex::encode(chain_address)), - ], - data: None, - }) + Ok(Response::new() + .add_attribute("chain_id", chain_id.to_string()) + .add_attribute("chain_address", hex::encode(chain_address))) } -fn handle_complete_transfer( - deps: &mut Extern, +fn handle_complete_transfer( + deps: DepsMut, env: Env, + info: MessageInfo, emitter_chain: u16, emitter_address: Vec, data: &Vec, -) -> StdResult { +) -> StdResult { let transfer_info = TransferInfo::deserialize(&data)?; - - // All terra token addresses are 20 bytes, and so start with 12 0's, if the address begins with - // a 1 we can identify it as a fully native token. match transfer_info.token_address.as_slice()[0] { - 1 => handle_complete_transfer_token_native(deps, env, emitter_chain, emitter_address, data), - _ => handle_complete_transfer_token(deps, env, emitter_chain, emitter_address, data), + 1 => handle_complete_transfer_token_native(deps, env, info, emitter_chain, emitter_address, data), + _ => handle_complete_transfer_token(deps, env, info, emitter_chain, emitter_address, data), } } -fn handle_complete_transfer_token( - deps: &mut Extern, - env: Env, +fn handle_complete_transfer_token( + deps: DepsMut, + _env: Env, + info: MessageInfo, emitter_chain: u16, emitter_address: Vec, data: &Vec, -) -> StdResult { +) -> StdResult { let transfer_info = TransferInfo::deserialize(&data)?; - let expected_contract = - bridge_contracts_read(&deps.storage).load(&emitter_chain.to_be_bytes())?; + bridge_contracts_read(deps.storage).load(&emitter_chain.to_be_bytes())?; // must be sent by a registered token bridge contract if expected_contract != emitter_address { - return Err(StdError::unauthorized()); + return Err(StdError::generic_err("invalid emitter")); } + if transfer_info.recipient_chain != CHAIN_ID { return Err(StdError::generic_err( "this transfer is not directed at this chain", )); } - let target_address = (&transfer_info.recipient.as_slice()).get_address(0); let token_chain = transfer_info.token_chain; + let target_address = (&transfer_info.recipient.as_slice()).get_address(0); let (not_supported_amount, mut amount) = transfer_info.amount; let (not_supported_fee, mut fee) = transfer_info.fee; @@ -621,62 +554,58 @@ fn handle_complete_transfer_token( let asset_id = build_asset_id(token_chain, &asset_address); // Check if this asset is already deployed - let contract_addr = wrapped_asset_read(&deps.storage).load(&asset_id).ok(); + let contract_addr = wrapped_asset_read(deps.storage).load(&asset_id).ok(); return if let Some(contract_addr) = contract_addr { // Asset already deployed, just mint let recipient = deps .api - .human_address(&target_address) + .addr_humanize(&target_address) .or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?; let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: contract_addr.clone(), msg: to_binary(&WrappedMsg::Mint { - recipient: recipient.clone(), + recipient: recipient.to_string(), amount: Uint128::from(amount), })?, - send: vec![], + funds: vec![], })]; if fee != 0 { messages.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: contract_addr.clone(), msg: to_binary(&WrappedMsg::Mint { - recipient: env.message.sender.clone(), + recipient: info.sender.to_string(), amount: Uint128::from(fee), })?, - send: vec![], + funds: vec![], })) } - Ok(HandleResponse { - messages, - log: vec![ - log("action", "complete_transfer_wrapped"), - log("contract", contract_addr), - log("recipient", recipient), - log("amount", amount), - ], - data: None, - }) + Ok(Response::new() + .add_messages(messages) + .add_attribute("action", "complete_transfer_wrapped") + .add_attribute("contract", contract_addr) + .add_attribute("recipient", recipient) + .add_attribute("amount", amount.to_string())) } else { Err(StdError::generic_err("Wrapped asset not deployed. To deploy, invoke CreateWrapped with the associated AssetMeta")) }; } else { let token_address = transfer_info.token_address.as_slice().get_address(0); - let recipient = deps.api.human_address(&target_address)?; - let contract_addr = deps.api.human_address(&token_address)?; + let recipient = deps.api.addr_humanize(&target_address)?; + let contract_addr = deps.api.addr_humanize(&token_address)?; // note -- here the amount is the amount the recipient will receive; // amount + fee is the total sent - receive_native(&mut deps.storage, &token_address, Uint128(amount + fee))?; + receive_native(deps.storage, &token_address, Uint128::new(amount + fee))?; // undo normalization to 8 decimals let token_info: TokenInfoResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: contract_addr.clone(), + contract_addr: contract_addr.to_string(), msg: to_binary(&TokenQuery::TokenInfo {})?, }))?; @@ -686,54 +615,55 @@ fn handle_complete_transfer_token( fee = fee.checked_mul(multiplier).unwrap(); let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.clone(), + contract_addr: contract_addr.to_string(), msg: to_binary(&TokenMsg::Transfer { - recipient: recipient.clone(), + recipient: recipient.to_string(), amount: Uint128::from(amount), })?, - send: vec![], + funds: vec![], })]; if fee != 0 { messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.clone(), + contract_addr: contract_addr.to_string(), msg: to_binary(&TokenMsg::Transfer { - recipient: env.message.sender.clone(), + recipient: info.sender.to_string(), amount: Uint128::from(fee), })?, - send: vec![], + funds: vec![], })) } - Ok(HandleResponse { - messages, - log: vec![ - log("action", "complete_transfer_token"), - log("recipient", recipient), - log("contract", contract_addr), - log("amount", amount), - ], - data: None, - }) + Ok(Response::new() + .add_messages(messages) + .add_attribute("action", "complete_transfer_native") + .add_attribute("recipient", recipient) + .add_attribute("contract", contract_addr) + .add_attribute("amount", amount.to_string())) } } -fn handle_complete_transfer_token_native( - deps: &mut Extern, - env: Env, + +fn handle_complete_transfer_token_native( + deps: DepsMut, + _env: Env, + info: MessageInfo, emitter_chain: u16, emitter_address: Vec, data: &Vec, -) -> StdResult { +) -> StdResult { let transfer_info = TransferInfo::deserialize(&data)?; let expected_contract = - bridge_contracts_read(&deps.storage).load(&emitter_chain.to_be_bytes())?; + bridge_contracts_read(deps.storage).load(&emitter_chain.to_be_bytes())?; // must be sent by a registered token bridge contract if expected_contract != emitter_address { - return Err(StdError::unauthorized()); + return Err(StdError::generic_err( + "invalid emitter", + )); } + if transfer_info.recipient_chain != CHAIN_ID { return Err(StdError::generic_err( "this transfer is not directed at this chain", @@ -763,49 +693,46 @@ fn handle_complete_transfer_token_native( // note -- here the amount is the amount the recipient will receive; // amount + fee is the total sent - let recipient = deps.api.human_address(&target_address)?; + let recipient = deps.api.addr_humanize(&target_address)?; let token_address = (&*token_address).get_address(0); - receive_native(&mut deps.storage, &token_address, Uint128(amount + fee))?; + receive_native(deps.storage, &token_address, Uint128::new(amount + fee))?; let mut messages = vec![CosmosMsg::Bank(BankMsg::Send { - from_address: env.contract.address.clone(), - to_address: recipient.clone(), + to_address: recipient.to_string(), amount: vec![coin(amount, &denom)], })]; if fee != 0 { messages.push(CosmosMsg::Bank(BankMsg::Send { - from_address: env.contract.address.clone(), - to_address: recipient.clone(), + to_address: recipient.to_string(), amount: vec![coin(fee, &denom)], })); } - Ok(HandleResponse { - messages, - log: vec![ - log("action", "complete_transfer_token_native"), - log("recipient", recipient), - log("denom", denom), - log("amount", amount), - ], - data: None, - }) + Ok(Response::new() + .add_messages(messages) + .add_attribute("action", "complete_transfer_terra_native") + .add_attribute("recipient", recipient) + .add_attribute("denom", denom) + .add_attribute("amount", amount.to_string())) } -fn handle_initiate_transfer( - deps: &mut Extern, + +fn handle_initiate_transfer( + deps: DepsMut, env: Env, + info: MessageInfo, asset: Asset, recipient_chain: u16, recipient: Vec, fee: Uint128, nonce: u32, -) -> StdResult { +) -> StdResult { match asset.info { AssetInfo::Token { contract_addr } => handle_initiate_transfer_token( deps, env, + info, contract_addr, asset.amount, recipient_chain, @@ -813,31 +740,31 @@ fn handle_initiate_transfer( fee, nonce, ), - AssetInfo::NativeToken { ref denom } => { - handle_initiate_transfer_native_token( - deps, - env, - denom.clone(), - asset.amount, - recipient_chain, - recipient, - fee, - nonce, - ) - } + AssetInfo::NativeToken { ref denom } => handle_initiate_transfer_native_token( + deps, + env, + info, + denom.clone(), + asset.amount, + recipient_chain, + recipient, + fee, + nonce, + ), } } -fn handle_initiate_transfer_token( - deps: &mut Extern, +fn handle_initiate_transfer_token( + mut deps: DepsMut, env: Env, + info: MessageInfo, asset: HumanAddr, mut amount: Uint128, recipient_chain: u16, recipient: Vec, mut fee: Uint128, nonce: u32, -) -> StdResult { +) -> StdResult { if recipient_chain == CHAIN_ID { return ContractError::SameSourceAndTarget.std_err(); } @@ -851,23 +778,23 @@ fn handle_initiate_transfer_token( let asset_chain: u16; let asset_address: Vec; - let cfg: ConfigInfo = config_read(&deps.storage).load()?; - let asset_canonical: CanonicalAddr = deps.api.canonical_address(&asset)?; + let cfg: ConfigInfo = config_read(deps.storage).load()?; + let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&asset)?; let mut messages: Vec = vec![]; - match wrapped_asset_address_read(&deps.storage).load(asset_canonical.as_slice()) { + match wrapped_asset_address_read(deps.storage).load(asset_canonical.as_slice()) { Ok(_) => { // This is a deployed wrapped asset, burn it messages.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: asset.clone(), msg: to_binary(&WrappedMsg::Burn { - account: env.message.sender.clone(), + account: info.sender.to_string(), amount, })?, - send: vec![], + funds: vec![], })); - let request = QueryRequest::<()>::Wasm(WasmQuery::Smart { + let request = QueryRequest::::Wasm(WasmQuery::Smart { contract_addr: asset, msg: to_binary(&WrappedQuery::WrappedAssetInfo {})?, }); @@ -887,13 +814,13 @@ fn handle_initiate_transfer_token( let decimals = token_info.decimals; let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32); // chop off dust - amount = Uint128( + amount = Uint128::new( amount .u128() .checked_sub(amount.u128().checked_rem(multiplier).unwrap()) .unwrap(), ); - fee = Uint128( + fee = Uint128::new( fee.u128() .checked_sub(fee.u128().checked_rem(multiplier).unwrap()) .unwrap(), @@ -903,20 +830,20 @@ fn handle_initiate_transfer_token( messages.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: asset, msg: to_binary(&TokenMsg::TransferFrom { - owner: env.message.sender.clone(), - recipient: env.contract.address.clone(), + owner: info.sender.to_string(), + recipient: env.contract.address.to_string(), amount, })?, - send: vec![], + funds: vec![], })); asset_address = extend_address_to_32(&asset_canonical); asset_chain = CHAIN_ID; // convert to normalized amounts before recording & posting vaa - amount = Uint128(amount.u128().checked_div(multiplier).unwrap()); - fee = Uint128(fee.u128().checked_div(multiplier).unwrap()); + amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap()); + fee = Uint128::new(fee.u128().checked_div(multiplier).unwrap()); - send_native(&mut deps.storage, &asset_canonical, amount)?; + send_native(deps.storage, &asset_canonical, amount)?; } }; @@ -936,45 +863,52 @@ fn handle_initiate_transfer_token( messages.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: cfg.wormhole_contract, - msg: to_binary(&WormholeHandleMsg::PostMessage { + msg: to_binary(&WormholeExecuteMsg::PostMessage { message: Binary::from(token_bridge_message.serialize()), nonce, })?, // forward coins sent to this message - send: coins_after_tax(deps, env.message.sent_funds.clone())?, + funds: coins_after_tax(deps.branch(), info.funds.clone())?, })); - Ok(HandleResponse { - messages, - log: vec![ - log("transfer.token_chain", asset_chain), - log("transfer.token", hex::encode(asset_address)), - log( - "transfer.sender", - hex::encode(extend_address_to_32( - &deps.api.canonical_address(&env.message.sender)?, - )), - ), - log("transfer.recipient_chain", recipient_chain), - log("transfer.recipient", hex::encode(recipient)), - log("transfer.amount", amount), - log("transfer.nonce", nonce), - log("transfer.block_time", env.block.time), - ], - data: None, - }) + Ok(Response::new() + .add_messages(messages) + .add_attribute("transfer.token_chain", asset_chain.to_string()) + .add_attribute("transfer.token", hex::encode(asset_address)) + .add_attribute( + "transfer.sender", + hex::encode(extend_address_to_32( + &deps.api.addr_canonicalize(&info.sender.as_str())?, + )), + ) + .add_attribute("transfer.recipient_chain", recipient_chain.to_string()) + .add_attribute("transfer.recipient", hex::encode(recipient)) + .add_attribute("transfer.amount", amount.to_string()) + .add_attribute("transfer.nonce", nonce.to_string()) + .add_attribute("transfer.block_time", env.block.time.seconds().to_string())) } -fn handle_initiate_transfer_native_token( - deps: &mut Extern, +/// All ISO-4217 currency codes are 3 letters, so we can safely slice anything that is not ULUNA. +/// https://www.xe.com/iso4217.php +fn format_native_denom_symbol(denom: &str) -> String { + if denom == "uluna" { + return "LUNA".to_string(); + } + // UUSD -> US -> UST + denom.to_uppercase()[1..3].to_string() + "T" +} + +fn handle_initiate_transfer_native_token( + deps: DepsMut, env: Env, + info: MessageInfo, denom: String, amount: Uint128, recipient_chain: u16, recipient: Vec, fee: Uint128, nonce: u32, -) -> StdResult { +) -> StdResult { if recipient_chain == CHAIN_ID { return ContractError::SameSourceAndTarget.std_err(); } @@ -985,21 +919,21 @@ fn handle_initiate_transfer_native_token( return Err(StdError::generic_err("fee greater than sent amount")); } - let deposit_key = format!("{}:{}", env.message.sender, denom); - bridge_deposit(&mut deps.storage).update(deposit_key.as_bytes(), |current: Option| { + let deposit_key = format!("{}:{}", info.sender, denom); + bridge_deposit(deps.storage).update(deposit_key.as_bytes(), |current: Option| { match current { - Some(v) => Ok((v - amount)?), - None => Err(StdError::generic_err("no deposit found to transfer")) + Some(v) => Ok(v.checked_sub(amount)?), + None => Err(StdError::generic_err("no deposit found to transfer")), } })?; - let cfg: ConfigInfo = config_read(&deps.storage).load()?; + let cfg: ConfigInfo = config_read(deps.storage).load()?; let mut messages: Vec = vec![]; let asset_chain: u16 = CHAIN_ID; let mut asset_address: Vec = build_native_id(&denom); - send_native(&mut deps.storage, &asset_address[..].into(), amount)?; + send_native(deps.storage, &asset_address[..].into(), amount)?; // Mark the first byte of the address to distinguish it as native. asset_address = extend_address_to_32(&asset_address.into()); @@ -1019,40 +953,33 @@ fn handle_initiate_transfer_native_token( payload: transfer_info.serialize(), }; + let sender = deps.api.addr_canonicalize(&info.sender.as_str())?; messages.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: cfg.wormhole_contract, - msg: to_binary(&WormholeHandleMsg::PostMessage { + msg: to_binary(&WormholeExecuteMsg::PostMessage { message: Binary::from(token_bridge_message.serialize()), nonce, })?, - send: coins_after_tax(deps, env.message.sent_funds.clone())?, + funds: coins_after_tax(deps, info.funds.clone())?, })); - Ok(HandleResponse { - messages, - log: vec![ - log("transfer.token_chain", asset_chain), - log("transfer.token", hex::encode(asset_address)), - log( - "transfer.sender", - hex::encode(extend_address_to_32( - &deps.api.canonical_address(&env.message.sender)?, - )), - ), - log("transfer.recipient_chain", recipient_chain), - log("transfer.recipient", hex::encode(recipient)), - log("transfer.amount", amount), - log("transfer.nonce", nonce), - log("transfer.block_time", env.block.time), - ], - data: None, - }) + Ok(Response::new() + .add_messages(messages) + .add_attribute("transfer.token_chain", asset_chain.to_string()) + .add_attribute("transfer.token", hex::encode(asset_address)) + .add_attribute( + "transfer.sender", + hex::encode(extend_address_to_32(&sender)), + ) + .add_attribute("transfer.recipient_chain", recipient_chain.to_string()) + .add_attribute("transfer.recipient", hex::encode(recipient)) + .add_attribute("transfer.amount", amount.to_string()) + .add_attribute("transfer.nonce", nonce.to_string()) + .add_attribute("transfer.block_time", env.block.time.seconds().to_string())) } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::WrappedRegistry { chain, address } => { to_binary(&query_wrapped_registry(deps, chain, address.as_slice())?) @@ -1060,14 +987,14 @@ pub fn query( } } -pub fn query_wrapped_registry( - deps: &Extern, +pub fn query_wrapped_registry( + deps: Deps, chain: u16, address: &[u8], ) -> StdResult { let asset_id = build_asset_id(chain, address); // Check if this asset is already deployed - match wrapped_asset_read(&deps.storage).load(&asset_id) { + match wrapped_asset_read(deps.storage).load(&asset_id) { Ok(address) => Ok(WrappedRegistryResponse { address }), Err(_) => ContractError::AssetNotFound.std_err(), } diff --git a/terra/contracts/token-bridge/src/lib.rs b/terra/contracts/token-bridge/src/lib.rs index 1d2cc8b3..f67a6e5e 100644 --- a/terra/contracts/token-bridge/src/lib.rs +++ b/terra/contracts/token-bridge/src/lib.rs @@ -1,10 +1,6 @@ #[cfg(test)] -#[macro_use] extern crate lazy_static; pub mod contract; pub mod msg; pub mod state; - -#[cfg(all(target_arch = "wasm32", not(feature = "library")))] -cosmwasm_std::create_entry_points!(contract); diff --git a/terra/contracts/token-bridge/src/msg.rs b/terra/contracts/token-bridge/src/msg.rs index 06b4f44f..fb2b0bd6 100644 --- a/terra/contracts/token-bridge/src/msg.rs +++ b/terra/contracts/token-bridge/src/msg.rs @@ -1,13 +1,18 @@ -use cosmwasm_std::{Binary, HumanAddr, Uint128}; -use schemars::JsonSchema; +use cosmwasm_std::{ + Binary, + Uint128, +}; use terraswap::asset::{Asset, AssetInfo}; +use schemars::JsonSchema; use serde::{ Deserialize, Serialize, }; +type HumanAddr = String; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { +pub struct InstantiateMsg { // governance contract details pub gov_chain: u16, pub gov_address: Binary, @@ -18,7 +23,7 @@ pub struct InitMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum HandleMsg { +pub enum ExecuteMsg { RegisterAssetHook { asset_id: Binary, }, @@ -28,6 +33,7 @@ pub enum HandleMsg { asset: AssetInfo, }, + InitiateTransfer { asset: Asset, recipient_chain: u16, diff --git a/terra/contracts/token-bridge/src/state.rs b/terra/contracts/token-bridge/src/state.rs index bbab0ee5..ef3d9dae 100644 --- a/terra/contracts/token-bridge/src/state.rs +++ b/terra/contracts/token-bridge/src/state.rs @@ -6,7 +6,6 @@ use serde::{ use cosmwasm_std::{ CanonicalAddr, - HumanAddr, StdError, StdResult, Storage, @@ -25,6 +24,8 @@ use cosmwasm_storage::{ use wormhole::byte_utils::ByteUtils; +type HumanAddr = String; + pub static CONFIG_KEY: &[u8] = b"config"; pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset"; pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address"; @@ -43,57 +44,57 @@ pub struct ConfigInfo { pub wrapped_asset_code_id: u64, } -pub fn config(storage: &mut S) -> Singleton { +pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } -pub fn config_read(storage: &S) -> ReadonlySingleton { +pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } -pub fn bridge_deposit(storage: &mut S) -> Bucket { - bucket(BRIDGE_DEPOSITS, storage) +pub fn bridge_deposit(storage: &mut dyn Storage) -> Bucket { + bucket(storage, BRIDGE_DEPOSITS) } -pub fn bridge_deposit_read(storage: &S) -> ReadonlyBucket { - bucket_read(BRIDGE_DEPOSITS, storage) +pub fn bridge_deposit_read(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, BRIDGE_DEPOSITS) } -pub fn bridge_contracts(storage: &mut S) -> Bucket> { - bucket(BRIDGE_CONTRACTS, storage) +pub fn bridge_contracts(storage: &mut dyn Storage) -> Bucket> { + bucket(storage, BRIDGE_CONTRACTS) } -pub fn bridge_contracts_read(storage: &S) -> ReadonlyBucket> { - bucket_read(BRIDGE_CONTRACTS, storage) +pub fn bridge_contracts_read(storage: &dyn Storage) -> ReadonlyBucket> { + bucket_read(storage, BRIDGE_CONTRACTS) } -pub fn wrapped_asset(storage: &mut S) -> Bucket { - bucket(WRAPPED_ASSET_KEY, storage) +pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket { + bucket(storage, WRAPPED_ASSET_KEY) } -pub fn wrapped_asset_read(storage: &S) -> ReadonlyBucket { - bucket_read(WRAPPED_ASSET_KEY, storage) +pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, WRAPPED_ASSET_KEY) } -pub fn wrapped_asset_address(storage: &mut S) -> Bucket> { - bucket(WRAPPED_ASSET_ADDRESS_KEY, storage) +pub fn wrapped_asset_address(storage: &mut dyn Storage) -> Bucket> { + bucket(storage, WRAPPED_ASSET_ADDRESS_KEY) } -pub fn wrapped_asset_address_read(storage: &S) -> ReadonlyBucket> { - bucket_read(WRAPPED_ASSET_ADDRESS_KEY, storage) +pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket> { + bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY) } -pub fn send_native( - storage: &mut S, +pub fn send_native( + storage: &mut dyn Storage, asset_address: &CanonicalAddr, amount: Uint128, ) -> StdResult<()> { - let mut counter_bucket = bucket(NATIVE_COUNTER, storage); + let mut counter_bucket = bucket(storage, NATIVE_COUNTER); let new_total = amount + counter_bucket .load(asset_address.as_slice()) .unwrap_or(Uint128::zero()); - if new_total > Uint128(u64::MAX as u128) { + if new_total > Uint128::new(u64::MAX as u128) { return Err(StdError::generic_err( "transfer exceeds max outstanding bridged token amount", )); @@ -101,14 +102,15 @@ pub fn send_native( counter_bucket.save(asset_address.as_slice(), &new_total) } -pub fn receive_native( - storage: &mut S, +pub fn receive_native( + storage: &mut dyn Storage, asset_address: &CanonicalAddr, amount: Uint128, ) -> StdResult<()> { - let mut counter_bucket = bucket(NATIVE_COUNTER, storage); + let mut counter_bucket = bucket(storage, NATIVE_COUNTER); let total: Uint128 = counter_bucket.load(asset_address.as_slice())?; - counter_bucket.save(asset_address.as_slice(), &(total - amount)?) + let result = total.checked_sub(amount)?; + counter_bucket.save(asset_address.as_slice(), &result) } pub struct Action; diff --git a/terra/contracts/wormhole/Cargo.toml b/terra/contracts/wormhole/Cargo.toml index 391fdd4c..c8c9996e 100644 --- a/terra/contracts/wormhole/Cargo.toml +++ b/terra/contracts/wormhole/Cargo.toml @@ -14,20 +14,21 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { version = "0.10.0" } -cosmwasm-storage = { version = "0.10.0" } -schemars = "0.7" +cosmwasm-std = { version = "0.16.0" } +cosmwasm-storage = { version = "0.16.0" } +schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw20 = "0.2.2" -cw20-base = { version = "0.2.2", features = ["library"] } +cw20 = "0.8.0" +cw20-base = { version = "0.8.0", features = ["library"] } cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] } thiserror = { version = "1.0.20" } -k256 = { version = "0.5.9", default-features = false, features = ["ecdsa"] } +k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] } +getrandom = { version = "0.2", features = ["custom"] } sha3 = { version = "0.9.1", default-features = false } generic-array = { version = "0.14.4" } hex = "0.4.2" lazy_static = "1.4.0" [dev-dependencies] -cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] } -serde_json = "1.0" \ No newline at end of file +cosmwasm-vm = { version = "0.16.0", default-features = false } +serde_json = "1.0" diff --git a/terra/contracts/wormhole/src/contract.rs b/terra/contracts/wormhole/src/contract.rs index c1859594..f7a53bf4 100644 --- a/terra/contracts/wormhole/src/contract.rs +++ b/terra/contracts/wormhole/src/contract.rs @@ -1,21 +1,20 @@ use cosmwasm_std::{ + entry_point, has_coins, - log, to_binary, - Api, BankMsg, Binary, Coin, CosmosMsg, + Deps, + DepsMut, Env, - Extern, - HandleResponse, - HumanAddr, - InitResponse, - Querier, + MessageInfo, + Response, StdError, StdResult, Storage, + WasmMsg, }; use crate::{ @@ -25,11 +24,12 @@ use crate::{ }, error::ContractError, msg::{ + ExecuteMsg, GetAddressHexResponse, GetStateResponse, GuardianSetInfoResponse, - HandleMsg, - InitMsg, + InstantiateMsg, + MigrateMsg, QueryMsg, }, state::{ @@ -42,6 +42,7 @@ use crate::{ vaa_archive_add, vaa_archive_check, ConfigInfo, + ContractUpgrade, GovernancePacket, GuardianAddress, GuardianSetInfo, @@ -59,7 +60,7 @@ use k256::{ Signature as RecoverableSignature, }, Signature, - VerifyKey, + VerifyingKey, }, EncodedPoint, }; @@ -71,6 +72,8 @@ use sha3::{ use generic_array::GenericArray; use std::convert::TryFrom; +type HumanAddr = String; + // Chain ID of Terra const CHAIN_ID: u16 = 3; @@ -78,11 +81,18 @@ const CHAIN_ID: u16 = 3; const FEE_AMOUNT: u128 = 10000; pub const FEE_DENOMINATION: &str = "uluna"; -pub fn init( - deps: &mut Extern, +#[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 instantiate( + deps: DepsMut, _env: Env, - msg: InitMsg, -) -> StdResult { + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { // Save general wormhole info let state = ConfigInfo { gov_chain: msg.gov_chain, @@ -91,41 +101,39 @@ pub fn init( guardian_set_expirity: msg.guardian_set_expirity, fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default }; - config(&mut deps.storage).save(&state)?; + config(deps.storage).save(&state)?; // Add initial guardian set to storage guardian_set_set( - &mut deps.storage, + deps.storage, state.guardian_set_index, &msg.initial_guardian_set, )?; - Ok(InitResponse::default()) + Ok(Response::default()) } -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { match msg { - HandleMsg::PostMessage { message, nonce } => { - handle_post_message(deps, env, &message.as_slice(), nonce) + ExecuteMsg::PostMessage { message, nonce } => { + handle_post_message(deps, env, info, &message.as_slice(), nonce) } - HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, vaa.as_slice()), + ExecuteMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, info, vaa.as_slice()), } } /// Process VAA message signed by quardians -fn handle_submit_vaa( - deps: &mut Extern, +fn handle_submit_vaa( + deps: DepsMut, env: Env, + _info: MessageInfo, data: &[u8], -) -> StdResult { - let state = config_read(&deps.storage).load()?; +) -> StdResult { + let state = config_read(deps.storage).load()?; - let vaa = parse_and_verify_vaa(&deps.storage, data, env.block.time)?; - vaa_archive_add(&mut deps.storage, vaa.hash.as_slice())?; + let vaa = parse_and_verify_vaa(deps.storage, data, env.block.time.seconds())?; + vaa_archive_add(deps.storage, vaa.hash.as_slice())?; if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address { if state.guardian_set_index != vaa.guardian_set_index { @@ -139,11 +147,7 @@ fn handle_submit_vaa( ContractError::InvalidVAAAction.std_err() } -fn handle_governance_payload( - deps: &mut Extern, - env: Env, - data: &Vec, -) -> StdResult { +fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec) -> StdResult { let gov_packet = GovernancePacket::deserialize(&data)?; let module = String::from_utf8(gov_packet.module).unwrap(); @@ -160,7 +164,7 @@ fn handle_governance_payload( } match gov_packet.action { - // 1 is reserved for upgrade / migration + 1u8 => vaa_update_contract(deps, env, &gov_packet.payload), 2u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload), 3u8 => handle_set_fee(deps, env, &gov_packet.payload), 4u8 => handle_transfer_fee(deps, env, &gov_packet.payload), @@ -170,8 +174,8 @@ fn handle_governance_payload( /// Parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an /// active guardian set i.e. is valid according to Wormhole consensus rules -fn parse_and_verify_vaa( - storage: &S, +fn parse_and_verify_vaa( + storage: &dyn Storage, data: &[u8], block_time: u64, ) -> StdResult { @@ -239,18 +243,14 @@ fn parse_and_verify_vaa( Ok(vaa) } -fn vaa_update_guardian_set( - deps: &mut Extern, - env: Env, - data: &Vec, -) -> StdResult { +fn vaa_update_guardian_set(deps: DepsMut, env: Env, data: &Vec) -> StdResult { /* Payload format 0 uint32 new_index 4 uint8 len(keys) 5 [][20]uint8 guardian addresses */ - let mut state = config_read(&deps.storage).load()?; + let mut state = config_read(deps.storage).load()?; let GuardianSetUpgrade { new_guardian_set_index, @@ -265,107 +265,89 @@ fn vaa_update_guardian_set( state.guardian_set_index = new_guardian_set_index; - guardian_set_set( - &mut deps.storage, - state.guardian_set_index, - &new_guardian_set, - )?; + guardian_set_set(deps.storage, state.guardian_set_index, &new_guardian_set)?; - config(&mut deps.storage).save(&state)?; + config(deps.storage).save(&state)?; - let mut old_guardian_set = guardian_set_get(&deps.storage, old_guardian_set_index)?; - old_guardian_set.expiration_time = env.block.time + state.guardian_set_expirity; - guardian_set_set(&mut deps.storage, old_guardian_set_index, &old_guardian_set)?; + let mut old_guardian_set = guardian_set_get(deps.storage, old_guardian_set_index)?; + old_guardian_set.expiration_time = env.block.time.seconds() + state.guardian_set_expirity; + guardian_set_set(deps.storage, old_guardian_set_index, &old_guardian_set)?; - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "guardian_set_change"), - log("old", old_guardian_set_index), - log("new", state.guardian_set_index), - ], - data: None, - }) + Ok(Response::new() + .add_attribute("action", "guardian_set_change") + .add_attribute("old", old_guardian_set_index.to_string()) + .add_attribute("new", state.guardian_set_index.to_string())) } -pub fn handle_set_fee( - deps: &mut Extern, - env: Env, - data: &Vec, -) -> StdResult { +fn vaa_update_contract(_deps: DepsMut, env: Env, data: &Vec) -> StdResult { + /* Payload format + 0 [][32]uint8 new_contract + */ + + let ContractUpgrade { new_contract } = ContractUpgrade::deserialize(&data)?; + + 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")) +} + +pub fn handle_set_fee(deps: DepsMut, _env: Env, data: &Vec) -> StdResult { let set_fee_msg = SetFee::deserialize(&data)?; // Save new fees - let mut state = config_read(&mut deps.storage).load()?; + let mut state = config_read(deps.storage).load()?; state.fee = set_fee_msg.fee; - config(&mut deps.storage).save(&state)?; + config(deps.storage).save(&state)?; - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "fee_change"), - log("new_fee.amount", state.fee.amount), - log("new_fee.denom", state.fee.denom), - ], - data: None, - }) + Ok(Response::new() + .add_attribute("action", "fee_change") + .add_attribute("new_fee.amount", state.fee.amount.to_string()) + .add_attribute("new_fee.denom", state.fee.denom.to_string())) } -pub fn handle_transfer_fee( - deps: &mut Extern, - env: Env, - data: &Vec, -) -> StdResult { +pub fn handle_transfer_fee(deps: DepsMut, _env: Env, data: &Vec) -> StdResult { let transfer_msg = TransferFee::deserialize(&data)?; - Ok(HandleResponse { - messages: vec![CosmosMsg::Bank(BankMsg::Send { - from_address: env.contract.address, - to_address: deps.api.human_address(&transfer_msg.recipient)?, - amount: vec![transfer_msg.amount], - })], - log: vec![], - data: None, - }) + Ok(Response::new().add_message(CosmosMsg::Bank(BankMsg::Send { + to_address: deps.api.addr_humanize(&transfer_msg.recipient)?.to_string(), + amount: vec![transfer_msg.amount], + }))) } -fn handle_post_message( - deps: &mut Extern, +fn handle_post_message( + deps: DepsMut, env: Env, + info: MessageInfo, message: &[u8], nonce: u32, -) -> StdResult { - let state = config_read(&deps.storage).load()?; +) -> StdResult { + let state = config_read(deps.storage).load()?; let fee = state.fee; // Check fee - if !has_coins(env.message.sent_funds.as_ref(), &fee) { + if !has_coins(info.funds.as_ref(), &fee) { return ContractError::FeeTooLow.std_err(); } - let emitter = extend_address_to_32(&deps.api.canonical_address(&env.message.sender)?); + let emitter = extend_address_to_32(&deps.api.addr_canonicalize(&info.sender.as_str())?); + let sequence = sequence_read(deps.storage, emitter.as_slice()); + sequence_set(deps.storage, emitter.as_slice(), sequence + 1)?; - let sequence = sequence_read(&deps.storage, emitter.as_slice()); - sequence_set(&mut deps.storage, emitter.as_slice(), sequence + 1)?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("message.message", hex::encode(message)), - log("message.sender", hex::encode(emitter)), - log("message.chain_id", CHAIN_ID), - log("message.nonce", nonce), - log("message.sequence", sequence), - log("message.block_time", env.block.time), - ], - data: None, - }) + Ok(Response::new() + .add_attribute("message.message", hex::encode(message)) + .add_attribute("message.sender", hex::encode(emitter)) + .add_attribute("message.chain_id", CHAIN_ID.to_string()) + .add_attribute("message.nonce", nonce.to_string()) + .add_attribute("message.sequence", sequence.to_string()) + .add_attribute("message.block_time", env.block.time.seconds().to_string())) } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GuardianSetInfo {} => to_binary(&query_guardian_set_info(deps)?), QueryMsg::VerifyVAA { vaa, block_time } => to_binary(&query_parse_and_verify_vaa( @@ -378,11 +360,9 @@ pub fn query( } } -pub fn query_guardian_set_info( - deps: &Extern, -) -> StdResult { - let state = config_read(&deps.storage).load()?; - let guardian_set = guardian_set_get(&deps.storage, state.guardian_set_index)?; +pub fn query_guardian_set_info(deps: Deps) -> StdResult { + let state = config_read(deps.storage).load()?; + let guardian_set = guardian_set_get(deps.storage, state.guardian_set_index)?; let res = GuardianSetInfoResponse { guardian_set_index: state.guardian_set_index, addresses: guardian_set.addresses, @@ -390,33 +370,28 @@ pub fn query_guardian_set_info( Ok(res) } -pub fn query_parse_and_verify_vaa( - deps: &Extern, +pub fn query_parse_and_verify_vaa( + deps: Deps, data: &[u8], block_time: u64, ) -> StdResult { - parse_and_verify_vaa(&deps.storage, data, block_time) + parse_and_verify_vaa(deps.storage, data, block_time) } // returns the hex of the 32 byte address we use for some address on this chain -pub fn query_address_hex( - deps: &Extern, - address: &HumanAddr, -) -> StdResult { +pub fn query_address_hex(deps: Deps, address: &HumanAddr) -> StdResult { Ok(GetAddressHexResponse { - hex: hex::encode(extend_address_to_32(&deps.api.canonical_address(&address)?)), + hex: hex::encode(extend_address_to_32(&deps.api.addr_canonicalize(&address)?)), }) } -pub fn query_state( - deps: &Extern, -) -> StdResult { - let state = config_read(&deps.storage).load()?; +pub fn query_state(deps: Deps) -> StdResult { + let state = config_read(deps.storage).load()?; let res = GetStateResponse { fee: state.fee }; Ok(res) } -fn keys_equal(a: &VerifyKey, b: &GuardianAddress) -> bool { +fn keys_equal(a: &VerifyingKey, b: &GuardianAddress) -> bool { let mut hasher = Keccak256::new(); let point: EncodedPoint = EncodedPoint::from(a); diff --git a/terra/contracts/wormhole/src/error.rs b/terra/contracts/wormhole/src/error.rs index 2975eafc..67603f5d 100644 --- a/terra/contracts/wormhole/src/error.rs +++ b/terra/contracts/wormhole/src/error.rs @@ -104,7 +104,6 @@ impl ContractError { pub fn std(&self) -> StdError { StdError::GenericErr { msg: format!("{}", self), - backtrace: None, } } diff --git a/terra/contracts/wormhole/src/lib.rs b/terra/contracts/wormhole/src/lib.rs index 9117b3f1..610d16ed 100644 --- a/terra/contracts/wormhole/src/lib.rs +++ b/terra/contracts/wormhole/src/lib.rs @@ -5,6 +5,3 @@ pub mod msg; pub mod state; pub use crate::error::ContractError; - -#[cfg(all(target_arch = "wasm32", not(feature = "library")))] -cosmwasm_std::create_entry_points!(contract); diff --git a/terra/contracts/wormhole/src/msg.rs b/terra/contracts/wormhole/src/msg.rs index 0bfc5c13..9653c052 100644 --- a/terra/contracts/wormhole/src/msg.rs +++ b/terra/contracts/wormhole/src/msg.rs @@ -1,7 +1,6 @@ use cosmwasm_std::{ Binary, Coin, - HumanAddr, }; use schemars::JsonSchema; use serde::{ @@ -14,8 +13,10 @@ use crate::state::{ GuardianSetInfo, }; +type HumanAddr = String; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { +pub struct InstantiateMsg { pub gov_chain: u16, pub gov_address: Binary, @@ -25,11 +26,16 @@ pub struct InitMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum HandleMsg { +pub enum ExecuteMsg { SubmitVAA { vaa: Binary }, PostMessage { message: Binary, nonce: u32 }, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct MigrateMsg { +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { diff --git a/terra/contracts/wormhole/src/state.rs b/terra/contracts/wormhole/src/state.rs index e8abe94e..43cacbff 100644 --- a/terra/contracts/wormhole/src/state.rs +++ b/terra/contracts/wormhole/src/state.rs @@ -1,6 +1,5 @@ use schemars::{ JsonSchema, - Set, }; use serde::{ Deserialize, @@ -11,7 +10,6 @@ use cosmwasm_std::{ Binary, CanonicalAddr, Coin, - HumanAddr, StdResult, Storage, Uint128, @@ -37,6 +35,8 @@ use sha3::{ Keccak256, }; +type HumanAddr = String; + pub static CONFIG_KEY: &[u8] = b"config"; pub static GUARDIAN_SET_KEY: &[u8] = b"guardian_set"; pub static SEQUENCE_KEY: &[u8] = b"sequence"; @@ -217,62 +217,62 @@ pub struct WormholeInfo { pub guardian_set_expirity: u64, } -pub fn config(storage: &mut S) -> Singleton { +pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } -pub fn config_read(storage: &S) -> ReadonlySingleton { +pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } -pub fn guardian_set_set( - storage: &mut S, +pub fn guardian_set_set( + storage: &mut dyn Storage, index: u32, data: &GuardianSetInfo, ) -> StdResult<()> { - bucket(GUARDIAN_SET_KEY, storage).save(&index.to_be_bytes(), data) + bucket(storage, GUARDIAN_SET_KEY).save(&index.to_be_bytes(), data) } -pub fn guardian_set_get(storage: &S, index: u32) -> StdResult { - bucket_read(GUARDIAN_SET_KEY, storage).load(&index.to_be_bytes()) +pub fn guardian_set_get(storage: &dyn Storage, index: u32) -> StdResult { + bucket_read(storage, GUARDIAN_SET_KEY).load(&index.to_be_bytes()) } -pub fn sequence_set(storage: &mut S, emitter: &[u8], sequence: u64) -> StdResult<()> { - bucket(SEQUENCE_KEY, storage).save(emitter, &sequence) +pub fn sequence_set(storage: &mut dyn Storage, emitter: &[u8], sequence: u64) -> StdResult<()> { + bucket(storage, SEQUENCE_KEY).save(emitter, &sequence) } -pub fn sequence_read(storage: &S, emitter: &[u8]) -> u64 { - bucket_read(SEQUENCE_KEY, storage) +pub fn sequence_read(storage: &dyn Storage, emitter: &[u8]) -> u64 { + bucket_read(storage, SEQUENCE_KEY) .load(&emitter) .or::(Ok(0)) .unwrap() } -pub fn vaa_archive_add(storage: &mut S, hash: &[u8]) -> StdResult<()> { - bucket(GUARDIAN_SET_KEY, storage).save(hash, &true) +pub fn vaa_archive_add(storage: &mut dyn Storage, hash: &[u8]) -> StdResult<()> { + bucket(storage, GUARDIAN_SET_KEY).save(hash, &true) } -pub fn vaa_archive_check(storage: &S, hash: &[u8]) -> bool { - bucket_read(GUARDIAN_SET_KEY, storage) +pub fn vaa_archive_check(storage: &dyn Storage, hash: &[u8]) -> bool { + bucket_read(storage, GUARDIAN_SET_KEY) .load(&hash) .or::(Ok(false)) .unwrap() } -pub fn wrapped_asset(storage: &mut S) -> Bucket { - bucket(WRAPPED_ASSET_KEY, storage) +pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket { + bucket(storage, WRAPPED_ASSET_KEY) } -pub fn wrapped_asset_read(storage: &S) -> ReadonlyBucket { - bucket_read(WRAPPED_ASSET_KEY, storage) +pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, WRAPPED_ASSET_KEY) } -pub fn wrapped_asset_address(storage: &mut S) -> Bucket> { - bucket(WRAPPED_ASSET_ADDRESS_KEY, storage) +pub fn wrapped_asset_address(storage: &mut dyn Storage) -> Bucket> { + bucket(storage, WRAPPED_ASSET_ADDRESS_KEY) } -pub fn wrapped_asset_address_read(storage: &S) -> ReadonlyBucket> { - bucket_read(WRAPPED_ASSET_ADDRESS_KEY, storage) +pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket> { + bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY) } pub struct GovernancePacket { @@ -299,12 +299,27 @@ impl GovernancePacket { } } +// action 1 +pub struct ContractUpgrade { + pub new_contract: u64, +} + // action 2 pub struct GuardianSetUpgrade { pub new_guardian_set_index: u32, pub new_guardian_set: GuardianSetInfo, } +impl ContractUpgrade { + pub fn deserialize(data: &Vec) -> StdResult { + let data = data.as_slice(); + let new_contract = data.get_u64(24); + Ok(ContractUpgrade { + new_contract, + }) + } +} + impl GuardianSetUpgrade { pub fn deserialize(data: &Vec) -> StdResult { const ADDRESS_LEN: usize = 20; @@ -351,7 +366,7 @@ impl SetFee { let (_, amount) = data.get_u256(0); let fee = Coin { denom: String::from(FEE_DENOMINATION), - amount: Uint128(amount), + amount: Uint128::new(amount), }; Ok(SetFee { fee }) } @@ -371,7 +386,7 @@ impl TransferFee { let (_, amount) = data.get_u256(32); let amount = Coin { denom: String::from(FEE_DENOMINATION), - amount: Uint128(amount), + amount: Uint128::new(amount), }; Ok(TransferFee { amount, recipient }) } diff --git a/terra/tools/deploy.js b/terra/tools/deploy.js index 92f66a51..95a2f315 100644 --- a/terra/tools/deploy.js +++ b/terra/tools/deploy.js @@ -1,5 +1,10 @@ import { Wallet, LCDClient, MnemonicKey } from "@terra-money/terra.js"; -import { StdFee, MsgInstantiateContract, MsgExecuteContract, MsgStoreCode } from "@terra-money/terra.js"; +import { + StdFee, + MsgInstantiateContract, + MsgExecuteContract, + MsgStoreCode, +} from "@terra-money/terra.js"; import { readFileSync, readdirSync } from "fs"; // TODO: Workaround /tx/estimate_fee errors. @@ -37,139 +42,191 @@ async function main() { await wallet.sequence(); // Deploy WASM blobs. - const artifacts = readdirSync('../artifacts/'); + // Read a list of files from directory containing compiled contracts. + const artifacts = readdirSync("../artifacts/"); + + // Sort them to get a determinstic list for consecutive code ids. artifacts.sort(); + artifacts.reverse(); + + const hardcodedGas = { + "cw20_base.wasm": 4000000, + "cw20_wrapped.wasm": 4000000, + "wormhole.wasm": 5000000, + "token_bridge.wasm": 6000000, + }; + + // Deploy all found WASM files and assign Code IDs. + const codeIds = {}; for (const artifact in artifacts) { - console.log(artifact); - console.log(artifacts.hasOwnProperty(artifact)); - if(artifacts.hasOwnProperty(artifact) && artifacts[artifact].includes('.wasm')) { - const file = artifacts[artifact]; - const contract_bytes = readFileSync(`../artifacts/${file}`); - console.log(`Storing Bytes, ${contract_bytes.length}, for ${file}`); - const store_code = new MsgStoreCode( - wallet.key.accAddress, - contract_bytes.toString('base64'), - ); + if ( + artifacts.hasOwnProperty(artifact) && + artifacts[artifact].includes(".wasm") + ) { + const file = artifacts[artifact]; + const contract_bytes = readFileSync(`../artifacts/${file}`); - try { - const tx = await wallet.createAndSignTx({ - msgs: [store_code], - memo: '', - fee: new StdFee( - 3000000, - { uluna: "100000" } - ) - }); + console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`); - const rs = await terra.tx.broadcast(tx); + const store_code = new MsgStoreCode( + wallet.key.accAddress, + contract_bytes.toString("base64") + ); - console.log(JSON.stringify(rs, null, 2)); - await wallet.sequence(); - } catch (e) { - console.log('Failed to Execute'); - } + try { + const tx = await wallet.createAndSignTx({ + msgs: [store_code], + memo: "", + fee: new StdFee(hardcodedGas[artifacts[artifact]], { + uluna: "100000", + }), + }); + + const rs = await terra.tx.broadcast(tx); + const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1]; + codeIds[file] = parseInt(ci); + } catch (e) { + console.log("Failed to Execute"); + } } } + console.log(codeIds); + + // Governance constants defined by the Wormhole spec. const govChain = 1; - const govAddress = "0000000000000000000000000000000000000000000000000000000000000004"; + const govAddress = + "0000000000000000000000000000000000000000000000000000000000000004"; + const addresses = {}; - //Instantiate Contracts - wallet.createAndSignTx({ + // Instantiate Wormhole + console.log("Instantiating Wormhole"); + await wallet + .createAndSignTx({ msgs: [ new MsgInstantiateContract( - wallet.key.accAddress, - undefined, - 2, - { - gov_chain: govChain, - gov_address: Buffer.from(govAddress, 'hex').toString('base64'), - guardian_set_expirity: 86400, - initial_guardian_set: { - addresses: [ - { - bytes: Buffer.from('beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe', 'hex').toString('base64'), - } - ], - expiration_time: 0 + wallet.key.accAddress, + wallet.key.accAddress, + codeIds["wormhole.wasm"], + { + gov_chain: govChain, + gov_address: Buffer.from(govAddress, "hex").toString("base64"), + guardian_set_expirity: 86400, + initial_guardian_set: { + addresses: [ + { + bytes: Buffer.from( + "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe", + "hex" + ).toString("base64"), }, + ], + expiration_time: 0, }, - ) - ], - memo:'', - }) - .then(tx => terra.tx.broadcast(tx)) - .then(rs => console.log(rs)); + } + ), + ], + memo: "", + }) + .then((tx) => terra.tx.broadcast(tx)) + .then((rs) => { + const address = /"contract_address","value":"([^"]+)/gm.exec( + rs.raw_log + )[1]; + addresses["wormhole.wasm"] = address; + }); - wallet.createAndSignTx({ + console.log("Instantiating Token Bridge"); + await wallet + .createAndSignTx({ msgs: [ new MsgInstantiateContract( - wallet.key.accAddress, - undefined, - 4, - { - owner: deployer.key.accAddress, - gov_chain: govChain, - gov_address: Buffer.from(govAddress, 'hex').toString('base64'), - wormhole_contract: "", - wrapped_asset_code_id: 2, - }, - ) - ], - memo:'', - }) - .then(tx => terra.tx.broadcast(tx)) - .then(rs => console.log(rs)); + wallet.key.accAddress, + wallet.key.accAddress, + codeIds["token_bridge.wasm"], + { + owner: wallet.key.accAddress, + gov_chain: govChain, + gov_address: Buffer.from(govAddress, "hex").toString("base64"), + wormhole_contract: addresses["wormhole.wasm"], + wrapped_asset_code_id: 2, + } + ), + ], + memo: "", + }) + .then((tx) => terra.tx.broadcast(tx)) + .then((rs) => { + const address = /"contract_address","value":"([^"]+)/gm.exec( + rs.raw_log + )[1]; + addresses["token_bridge.wasm"] = address; + }); - wallet.createAndSignTx({ + await wallet + .createAndSignTx({ msgs: [ new MsgInstantiateContract( - wallet.key.accAddress, - undefined, - 3, - { - name: "MOCK", - symbol: "MCK", - decimals: 6, - initial_balances: [ - { - "address": deployer.key.acc_address, - "amount": "100000000" - } - ], - mint: null, - }, - ) - ], - memo:'', - }) - .then(tx => terra.tx.broadcast(tx)) - .then(rs => console.log(rs)); + wallet.key.accAddress, + undefined, + codeIds["cw20_base.wasm"], + { + name: "MOCK", + symbol: "MCK", + decimals: 6, + initial_balances: [ + { + address: wallet.key.accAddress, + amount: "100000000", + }, + ], + mint: null, + } + ), + ], + memo: "", + }) + .then((tx) => terra.tx.broadcast(tx)) + .then((rs) => { + const address = /"contract_address","value":"([^"]+)/gm.exec( + rs.raw_log + )[1]; + addresses["mock.wasm"] = address; + }); + + console.log(addresses); const registrations = [ - '01000000000100c9f4230109e378f7efc0605fb40f0e1869f2d82fda5b1dfad8a5a2dafee85e033d155c18641165a77a2db6a7afbf2745b458616cb59347e89ae0c7aa3e7cc2d400000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e4272696467650100000001c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f', - '01000000000100e2e1975d14734206e7a23d90db48a6b5b6696df72675443293c6057dcb936bf224b5df67d32967adeb220d4fe3cb28be515be5608c74aab6adb31099a478db5c01000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e42726964676501000000020000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16' + "01000000000100c9f4230109e378f7efc0605fb40f0e1869f2d82fda5b1dfad8a5a2dafee85e033d155c18641165a77a2db6a7afbf2745b458616cb59347e89ae0c7aa3e7cc2d400000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e4272696467650100000001c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f", + "01000000000100e2e1975d14734206e7a23d90db48a6b5b6696df72675443293c6057dcb936bf224b5df67d32967adeb220d4fe3cb28be515be5608c74aab6adb31099a478db5c01000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e42726964676501000000020000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16", ]; - registrations.forEach(registration => { - wallet.createAndSignTx({ - msgs: [ - new MsgExecuteContract( + for (const registration in registrations) { + if (registrations.hasOwnProperty(registration)) { + console.log('Registering'); + await wallet + .createAndSignTx({ + msgs: [ + new MsgExecuteContract( wallet.key.accAddress, - "", + addresses["token_bridge.wasm"], { - submit_vaa: { - data: Buffer.from(registration, 'hex'), - }, + submit_vaa: { + data: Buffer.from(registrations[registration], "hex").toString('base64'), + }, }, { uluna: 1000 } - ), - ], - memo: '', - }) - .then(tx => terra.tx.broadcast(tx)) - .then(rs => console.log(rs)); - }); + ), + ], + memo: "", + fee: new StdFee(2000000, { + uluna: "100000", + }), + }) + .then((tx) => terra.tx.broadcast(tx)) + .then((rs) => console.log(rs)); + } + } } -main() +main(); diff --git a/terra/tools/deploy.sh b/terra/tools/deploy.sh index c125d49d..10307978 100644 --- a/terra/tools/deploy.sh +++ b/terra/tools/deploy.sh @@ -11,5 +11,6 @@ done sleep 2 npm ci && node deploy.js + echo "Going to sleep, interrupt if running manually" sleep infinity diff --git a/terra/tools/migrate.js b/terra/tools/migrate.js new file mode 100644 index 00000000..575b7c69 --- /dev/null +++ b/terra/tools/migrate.js @@ -0,0 +1,133 @@ +import { Wallet, LCDClient, MnemonicKey } from "@terra-money/terra.js"; +import { + StdFee, + MsgExecuteContract, + MsgInstantiateContract, + MsgMigrateContract, + MsgStoreCode, + MsgUpdateContractAdmin, +} from "@terra-money/terra.js"; +import { readFileSync, readdirSync } from "fs"; + +async function main() { + const terra = new LCDClient({ + URL: "http://localhost:1317", + chainID: "localterra", + }); + + const wallet = terra.wallet( + new MnemonicKey({ + mnemonic: + "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius", + }) + ); + + const hardcodedGas = { + "wormhole.wasm": 5000000, + }; + + // Deploy Wormhole alone. + const file = "wormhole.wasm"; + const contract_bytes = readFileSync(`../artifacts/${file}`); + console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`); + + // Get new code id. + const store_code = new MsgStoreCode( + wallet.key.accAddress, + contract_bytes.toString("base64") + ); + + const codeIds = {}; + try { + const tx = await wallet.createAndSignTx({ + msgs: [store_code], + memo: "", + fee: new StdFee(hardcodedGas["wormhole.wasm"], { + uluna: "100000", + }), + }); + + const rs = await terra.tx.broadcast(tx); + const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1]; + codeIds[file] = parseInt(ci); + } catch (e) { + console.log("Failed to Execute"); + } + + // Perform a Centralised update. + await wallet + .createAndSignTx({ + msgs: [ + new MsgMigrateContract( + wallet.key.accAddress, + "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5", + codeIds["wormhole.wasm"], + { + "action": "" + }, + { uluna: 1000 } + ), + ], + memo: "", + }) + .then((tx) => terra.tx.broadcast(tx)) + .then((rs) => console.log(rs)); + + // Set the Admin to the contract. + await wallet + .createAndSignTx({ + msgs: [ + new MsgUpdateContractAdmin( + wallet.key.accAddress, + "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5", + "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5" + ), + ], + memo: "", + }) + .then((tx) => terra.tx.broadcast(tx)) + .then((rs) => console.log(rs)); + + // Deploy a new CodeID. + try { + const tx = await wallet.createAndSignTx({ + msgs: [store_code], + memo: "", + fee: new StdFee(hardcodedGas["wormhole.wasm"], { + uluna: "100000", + }), + }); + + const rs = await terra.tx.broadcast(tx); + const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1]; + codeIds[file] = parseInt(ci); + } catch (e) { + console.log("Failed to Execute"); + } + + const upgradeVAA = '010000000001008928c70a029a924d334a24587e9d2ddbcfa7250d7ba61200e86b16966ef2bbd675fb759aa7a47c6392482ef073e9a6d7c4980dc53ed6f90fc84331486e284912000000000100000001000100000000000000000000000000000000000000000000000000000000000000040000000004e78c580000000000000000000000000000000000000000000000000000000000436f72650100030000000000000000000000000000000000000000000000000000000000000005'; + + // Perform a decentralised update with a signed VAA. + await wallet + .createAndSignTx({ + msgs: [ + new MsgExecuteContract( + wallet.key.accAddress, + "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5", + { + submit_v_a_a: { + vaa: Buffer.from(upgradeVAA, "hex").toString( + "base64" + ), + }, + }, + { uluna: 1000 } + ), + ], + memo: "", + }) + .then((tx) => terra.tx.broadcast(tx)) + .then((rs) => console.log(rs)); +} + +main();