diff --git a/.circleci/config.yml b/.circleci/config.yml
index 09dbda2c7f..b80f18560a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -11,6 +11,7 @@ workflows:
- package_vm_cranelift
- contract_burner
- contract_hackatom
+ - contract_ibc_reflect
- contract_queue
- contract_reflect
- contract_staking
@@ -333,6 +334,60 @@ jobs:
- target/wasm32-unknown-unknown/release/deps
key: cargocache-v2-contract_hackatom-rust:1.47.0-{{ checksum "Cargo.lock" }}
+ contract_ibc_reflect:
+ docker:
+ - image: rust:1.47.0
+ environment:
+ RUST_BACKTRACE: 1
+ working_directory: ~/cosmwasm/contracts/ibc-reflect
+ steps:
+ - checkout:
+ path: ~/cosmwasm
+ - run:
+ name: Version information
+ command: rustc --version; cargo --version; rustup --version
+ - restore_cache:
+ keys:
+ - cargocache-v2-contract_ibc_reflect-rust:1.47.0-{{ checksum "Cargo.lock" }}
+ - run:
+ name: Add wasm32 target
+ command: rustup target add wasm32-unknown-unknown && rustup target list --installed
+ - run:
+ name: Build wasm binary
+ command: cargo wasm --locked
+ - run:
+ name: Unit tests
+ command: cargo unit-test --locked
+ - run:
+ name: Integration tests (cranelift backend)
+ command: cargo integration-test --locked
+ - run:
+ name: Integration tests (singlepass backend)
+ command: cargo integration-test --locked --no-default-features
+ - run:
+ name: Build and run schema generator
+ command: cargo schema --locked
+ - run:
+ name: Ensure schemas are up-to-date
+ command: |
+ CHANGES_IN_REPO=$(git status --porcelain)
+ if [[ -n "$CHANGES_IN_REPO" ]]; then
+ echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:"
+ git status && git --no-pager diff
+ exit 1
+ fi
+ - save_cache:
+ paths:
+ - /usr/local/cargo/registry
+ - target/debug/.fingerprint
+ - target/debug/build
+ - target/debug/deps
+ - target/wasm32-unknown-unknown/release/.fingerprint
+ - target/wasm32-unknown-unknown/release/build
+ - target/wasm32-unknown-unknown/release/deps
+ key: cargocache-v2-contract_ibc_reflect-rust:1.47.0-{{ checksum "Cargo.lock" }}
+
+
contract_queue:
docker:
- image: rust:1.47.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b849a2b8dd..4ba1c3d441 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to
### Added
- cosmwasm-vm: Add PinnedMemoryCache. ([#696])
+- cosmwasm-vm: The new `Instance::has_ibc_entry_points` tells the caller if the
+ contract exposes IBC entry points.
### Changed
diff --git a/contracts/README.md b/contracts/README.md
index 0960cf6e4d..71c8d0d64c 100644
--- a/contracts/README.md
+++ b/contracts/README.md
@@ -19,27 +19,32 @@ reason, use the following commands:
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_burner",target=/code/contracts/burner/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
- cosmwasm/rust-optimizer:0.10.5 ./contracts/burner
+ cosmwasm/rust-optimizer:0.10.7 ./contracts/burner
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_hackatom",target=/code/contracts/hackatom/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
- cosmwasm/rust-optimizer:0.10.5 ./contracts/hackatom
+ cosmwasm/rust-optimizer:0.10.7 ./contracts/hackatom
+
+docker run --rm -v "$(pwd)":/code \
+ --mount type=volume,source="devcontract_cache_ibc_reflect",target=/code/contracts/ibc-reflect/target \
+ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
+ cosmwasm/rust-optimizer:0.10.7 ./contracts/ibc-reflect
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_queue",target=/code/contracts/queue/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
- cosmwasm/rust-optimizer:0.10.5 ./contracts/queue
+ cosmwasm/rust-optimizer:0.10.7 ./contracts/queue
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_reflect",target=/code/contracts/reflect/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
- cosmwasm/rust-optimizer:0.10.5 ./contracts/reflect
+ cosmwasm/rust-optimizer:0.10.7 ./contracts/reflect
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_staking",target=/code/contracts/staking/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
- cosmwasm/rust-optimizer:0.10.5 ./contracts/staking
+ cosmwasm/rust-optimizer:0.10.7 ./contracts/staking
```
## Entry points
@@ -47,13 +52,14 @@ docker run --rm -v "$(pwd)":/code \
The development contracts in this folder contain a variety of different entry
points in order to demonstrate and test the flexibility we have.
-| Contract | Macro | Has `query` | Has `migrate` |
-| -------- | --------------------------------------------- | ----------- | ------------- |
-| burner | `#[entry_point]` | no | yes |
-| hackatom | [`create_entry_points_with_migration!`][cepm] | yes | yes |
-| queue | mixed1 | yes | yes |
-| reflect | [`create_entry_points!`][cep] | yes | no |
-| staking | `#[entry_point]` | yes | no |
+| Contract | Macro | Has `query` | Has `migrate` |
+| ----------- | --------------------------------------------- | ----------- | ------------- |
+| burner | `#[entry_point]` | no | yes |
+| hackatom | [`create_entry_points_with_migration!`][cepm] | yes | yes |
+| ibc-reflect | `#[entry_point]` | yes | no |
+| queue | mixed1 | yes | yes |
+| reflect | [`create_entry_points!`][cep] | yes | no |
+| staking | `#[entry_point]` | yes | no |
1 Because we can. Don't try this at home.
diff --git a/contracts/ibc-reflect/.cargo/config b/contracts/ibc-reflect/.cargo/config
new file mode 100644
index 0000000000..8d4bc738b1
--- /dev/null
+++ b/contracts/ibc-reflect/.cargo/config
@@ -0,0 +1,6 @@
+[alias]
+wasm = "build --release --target wasm32-unknown-unknown"
+wasm-debug = "build --target wasm32-unknown-unknown"
+unit-test = "test --lib"
+integration-test = "test --test integration"
+schema = "run --example schema"
diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock
new file mode 100644
index 0000000000..eb6cfdab3e
--- /dev/null
+++ b/contracts/ibc-reflect/Cargo.lock
@@ -0,0 +1,1316 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "addr2line"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
+dependencies = [
+ "gimli 0.23.0",
+]
+
+[[package]]
+name = "adler"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "backtrace"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
+dependencies = [
+ "addr2line",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bincode"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
+dependencies = [
+ "byteorder",
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+
+[[package]]
+name = "cc"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clru"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44e10ee132778350b0390b347b47e4bbb098e23f0ee34ded4a03b078cae19024"
+
+[[package]]
+name = "const_fn"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
+
+[[package]]
+name = "cosmwasm-derive"
+version = "0.13.2"
+dependencies = [
+ "syn",
+]
+
+[[package]]
+name = "cosmwasm-schema"
+version = "0.13.2"
+dependencies = [
+ "schemars",
+ "serde_json",
+]
+
+[[package]]
+name = "cosmwasm-std"
+version = "0.13.2"
+dependencies = [
+ "base64",
+ "cosmwasm-derive",
+ "schemars",
+ "serde",
+ "serde-json-wasm",
+ "thiserror",
+]
+
+[[package]]
+name = "cosmwasm-storage"
+version = "0.13.2"
+dependencies = [
+ "cosmwasm-std",
+ "serde",
+]
+
+[[package]]
+name = "cosmwasm-vm"
+version = "0.13.2"
+dependencies = [
+ "clru",
+ "cosmwasm-std",
+ "hex",
+ "parity-wasm",
+ "schemars",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror",
+ "wasmer",
+ "wasmer-middlewares",
+]
+
+[[package]]
+name = "cpuid-bool"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
+
+[[package]]
+name = "cranelift-bforest"
+version = "0.68.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9221545c0507dc08a62b2d8b5ffe8e17ac580b0a74d1813b496b8d70b070fbd0"
+dependencies = [
+ "cranelift-entity",
+]
+
+[[package]]
+name = "cranelift-codegen"
+version = "0.68.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e9936ea608b6cd176f107037f6adbb4deac933466fc7231154f96598b2d3ab1"
+dependencies = [
+ "byteorder",
+ "cranelift-bforest",
+ "cranelift-codegen-meta",
+ "cranelift-codegen-shared",
+ "cranelift-entity",
+ "gimli 0.22.0",
+ "log",
+ "regalloc",
+ "smallvec",
+ "target-lexicon",
+ "thiserror",
+]
+
+[[package]]
+name = "cranelift-codegen-meta"
+version = "0.68.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef2b2768568306540f4c8db3acce9105534d34c4a1e440529c1e702d7f8c8d7"
+dependencies = [
+ "cranelift-codegen-shared",
+ "cranelift-entity",
+]
+
+[[package]]
+name = "cranelift-codegen-shared"
+version = "0.68.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6759012d6d19c4caec95793f052613e9d4113e925e7f14154defbac0f1d4c938"
+
+[[package]]
+name = "cranelift-entity"
+version = "0.68.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86badbce14e15f52a45b666b38abe47b204969dd7f8fb7488cb55dd46b361fa6"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cranelift-frontend"
+version = "0.68.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b608bb7656c554d0a4cf8f50c7a10b857e80306f6ff829ad6d468a7e2323c8d8"
+dependencies = [
+ "cranelift-codegen",
+ "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 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
+dependencies = [
+ "cfg-if 1.0.0",
+ "const_fn",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+dependencies = [
+ "autocfg",
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "darling"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "dynasm"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62a59fbab09460c1569eeea9b5e4cf62f13f5198b1c2ba0e5196dd7fdd17cd42"
+dependencies = [
+ "bitflags",
+ "byteorder",
+ "lazy_static",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dynasmrt"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85bec3edae2841d37b1c3dc7f3fd403c9061f26e9ffeeee97a3ea909b1bb2ef1"
+dependencies = [
+ "byteorder",
+ "dynasm",
+ "memmap",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "fallible-iterator"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
+dependencies = [
+ "fallible-iterator",
+ "indexmap",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "gimli"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
+
+[[package]]
+name = "ibc-reflect"
+version = "0.0.0"
+dependencies = [
+ "cosmwasm-schema",
+ "cosmwasm-std",
+ "cosmwasm-storage",
+ "cosmwasm-vm",
+ "schemars",
+ "serde",
+]
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+ "serde",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[package]]
+name = "lazy_static"
+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.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
+
+[[package]]
+name = "libloading"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi",
+]
+
+[[package]]
+name = "log"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+dependencies = [
+ "cfg-if 0.1.10",
+]
+
+[[package]]
+name = "mach"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memmap"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "memmap2"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e73be3b7d04a0123e933fea1d50d126cc7196bbc0362c0ce426694f777194eee"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "more-asserts"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
+dependencies = [
+ "crc32fast",
+ "indexmap",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "parity-wasm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "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]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "raw-cpuid"
+version = "7.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beb71f708fe39b2c5e98076204c3cc094ee5a4c12c4cdb119a2b72dc34164f41"
+dependencies = [
+ "bitflags",
+ "cc",
+ "rustc_version",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[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 = "rustc-demangle"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "schemars"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61"
+dependencies = [
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "0.9.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"
+
+[[package]]
+name = "serde"
+version = "1.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-json-wasm"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ebf73928683472b82f01683968f4f3e8979fb9443e28f0f4d5e5f308901123e"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
+dependencies = [
+ "block-buffer",
+ "cfg-if 1.0.0",
+ "cpuid-bool",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "strsim"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
+
+[[package]]
+name = "syn"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee5a98e506fb7231a304c3a1bd7c132a55016cf65001e0282480665870dfcb9"
+
+[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
+dependencies = [
+ "cfg-if 1.0.0",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "typenum"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasmer"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15a44e0148d07d4ed0a73c098755c04425ba3cbabbc32786e1d2349991b222f6"
+dependencies = [
+ "cfg-if 0.1.10",
+ "indexmap",
+ "more-asserts",
+ "target-lexicon",
+ "thiserror",
+ "wasmer-compiler",
+ "wasmer-compiler-cranelift",
+ "wasmer-compiler-singlepass",
+ "wasmer-derive",
+ "wasmer-engine",
+ "wasmer-engine-jit",
+ "wasmer-engine-native",
+ "wasmer-types",
+ "wasmer-vm",
+ "wat",
+ "winapi",
+]
+
+[[package]]
+name = "wasmer-compiler"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a93d98980bf219a2d1c7500f44f3c93e5c1da798c114b4840eb5b23dda4453c"
+dependencies = [
+ "raw-cpuid",
+ "serde",
+ "serde_bytes",
+ "smallvec",
+ "target-lexicon",
+ "thiserror",
+ "wasmer-types",
+ "wasmer-vm",
+ "wasmer_enumset",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmer-compiler-cranelift"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "359245ae98b40b24e7e835dc7a80db17f1ce79046efba6e330acca62c3464c5f"
+dependencies = [
+ "cranelift-codegen",
+ "cranelift-frontend",
+ "gimli 0.22.0",
+ "more-asserts",
+ "rayon",
+ "serde",
+ "smallvec",
+ "tracing",
+ "wasmer-compiler",
+ "wasmer-types",
+ "wasmer-vm",
+]
+
+[[package]]
+name = "wasmer-compiler-singlepass"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "684e25d8896ea77aa9576fb05a72a43c069015385de46d06b5da653121ecab54"
+dependencies = [
+ "byteorder",
+ "dynasm",
+ "dynasmrt",
+ "lazy_static",
+ "more-asserts",
+ "rayon",
+ "serde",
+ "smallvec",
+ "wasmer-compiler",
+ "wasmer-types",
+ "wasmer-vm",
+]
+
+[[package]]
+name = "wasmer-derive"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f37f10c2d364015aae2c2ff2b10fdce6a8d657a73020731c4d8a466cad9b318a"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "wasmer-engine"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb719043832d09d29d5679804d0a42ddec20671050471da73b2a967ae8b9c39"
+dependencies = [
+ "backtrace",
+ "bincode",
+ "lazy_static",
+ "memmap2",
+ "more-asserts",
+ "rustc-demangle",
+ "serde",
+ "serde_bytes",
+ "target-lexicon",
+ "thiserror",
+ "wasmer-compiler",
+ "wasmer-types",
+ "wasmer-vm",
+]
+
+[[package]]
+name = "wasmer-engine-jit"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3681be41579cec3e19b78667ea78ec11985cbbe2b3fd7775cde74350afb35aa"
+dependencies = [
+ "bincode",
+ "cfg-if 0.1.10",
+ "region",
+ "serde",
+ "serde_bytes",
+ "wasmer-compiler",
+ "wasmer-engine",
+ "wasmer-types",
+ "wasmer-vm",
+ "winapi",
+]
+
+[[package]]
+name = "wasmer-engine-native"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "251e2ede2cc14988c0c061f56593656d01e9eff0463800512e2386cc92379bf1"
+dependencies = [
+ "bincode",
+ "cfg-if 0.1.10",
+ "leb128",
+ "libloading",
+ "serde",
+ "tempfile",
+ "tracing",
+ "wasmer-compiler",
+ "wasmer-engine",
+ "wasmer-object",
+ "wasmer-types",
+ "wasmer-vm",
+ "which",
+]
+
+[[package]]
+name = "wasmer-middlewares"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f9347bb3f5ae57beccdb46edd13b8d89beb9c145bd46b9b78d42709f6d5200c"
+dependencies = [
+ "wasmer",
+ "wasmer-types",
+ "wasmer-vm",
+]
+
+[[package]]
+name = "wasmer-object"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b991a03a4309950fe8662460c11af646a44319b64f85e54dffe4c929f3315f8a"
+dependencies = [
+ "object",
+ "thiserror",
+ "wasmer-compiler",
+ "wasmer-types",
+]
+
+[[package]]
+name = "wasmer-types"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf17a50d109b26155d0d13cbc333e1a3f2c7f2e67951f7736cbbd88c67bca414"
+dependencies = [
+ "cranelift-entity",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "wasmer-vm"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7156e41a590030e8d9de473fad2d2f8d094e7d838471295ffa51138dffde201"
+dependencies = [
+ "backtrace",
+ "cc",
+ "cfg-if 0.1.10",
+ "indexmap",
+ "libc",
+ "memoffset",
+ "more-asserts",
+ "region",
+ "serde",
+ "thiserror",
+ "wasmer-types",
+ "winapi",
+]
+
+[[package]]
+name = "wasmer_enumset"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf088cc1f7d247fd96dff0df46fb1bbb747d8a69ae1ecd71aed55c55e354b2d8"
+dependencies = [
+ "num-traits",
+ "wasmer_enumset_derive",
+]
+
+[[package]]
+name = "wasmer_enumset_derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d1b32d98e11194200baf6d3f85eb2d6cfe56f6d9af0dd617f90ca48f958a88"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.65.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc2fe6350834b4e528ba0901e7aa405d78b89dc1fa3145359eb4de0e323fcf"
+
+[[package]]
+name = "wast"
+version = "30.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b79907b22f740634810e882d8d1d9d0f9563095a8ab94e786e370242bff5cd2"
+dependencies = [
+ "leb128",
+]
+
+[[package]]
+name = "wat"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8279a02835bf12e61ed2b3c3cbc6ecf9918762fd97e036917c11a09ec20ca44"
+dependencies = [
+ "wast",
+]
+
+[[package]]
+name = "which"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef"
+dependencies = [
+ "libc",
+ "thiserror",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/contracts/ibc-reflect/Cargo.toml b/contracts/ibc-reflect/Cargo.toml
new file mode 100644
index 0000000000..5e23b41e2d
--- /dev/null
+++ b/contracts/ibc-reflect/Cargo.toml
@@ -0,0 +1,41 @@
+[package]
+name = "ibc-reflect"
+version = "0.0.0"
+authors = ["Ethan Frey "]
+edition = "2018"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[profile.release]
+opt-level = 3
+debug = false
+rpath = false
+lto = true
+debug-assertions = false
+codegen-units = 1
+panic = 'abort'
+incremental = false
+overflow-checks = true
+
+[features]
+# Change this to [] if you don't need Windows support and want faster integration tests.
+default = ["cranelift"]
+# Use cranelift backend instead of singlepass. This is required for development on Windows.
+cranelift = ["cosmwasm-vm/cranelift"]
+# for quicker tests, cargo test --lib
+# for more explicit tests, cargo test --features=backtraces
+backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"]
+
+[dependencies]
+cosmwasm-std = { path = "../../packages/std", features = ["iterator", "stargate"] }
+cosmwasm-storage = { path = "../../packages/storage", features = ["iterator"] }
+schemars = "0.7"
+serde = { version = "1.0.103", default-features = false, features = ["derive"] }
+
+[dev-dependencies]
+cosmwasm-vm = { path = "../../packages/vm", default-features = false, features = ["iterator", "stargate"] }
+cosmwasm-schema = { path = "../../packages/schema" }
diff --git a/contracts/ibc-reflect/README.md b/contracts/ibc-reflect/README.md
new file mode 100644
index 0000000000..0a9aad788d
--- /dev/null
+++ b/contracts/ibc-reflect/README.md
@@ -0,0 +1,100 @@
+# Ibc Reflect Contract
+
+This is a simple contract to demonstrate using contracts using IBC messages. The
+first case we build is to simulate the `reflect` contract on another chain. That
+is, you can send a message over IBC to the reflect contract and it will
+"reflect" that message on the remote chain as if it sent it.
+
+This is inspired by
+[ICS27](https://github.com/chainapsis/cosmos-sdk-interchain-account/tree/master/x/ibc-account/spec)
+and uses a similar workflow, but we use different messages to make it easier for
+building with cosmwasm. In the future we could try to implement the ICS27 spec
+byte-for-byte compatible inside a CosmWasm contract, but that is not the
+intention here.
+
+## Workflow
+
+This requires 2 contracts on the remote chain. The first is this contract, which
+is essentially a factory. The second is the default [`reflect`](../reflect)
+contract, which allows the factory to control multiple independent accounts.
+
+The factory will handshake and accept connections from any attempt that uses the
+`ibc-reflect` "version" for the protocol negotiation. This will create a new
+channel. Once the connection is established (in the `ibc_channel_connect` entry
+point), it will create a new `reflect` contract instance. The reflect `code_id`
+must be set when initializing the factory. This `reflect` contract address will
+be saved and connected to the channel.
+
+Once the channel is fully established and the reflect contract instantiated it
+will expect a `RunTx` message, which contains `Vec`. When this
+message is received, it will execute it on the `reflect` contract, performing
+the requested action on behalf of the remote user.
+
+## Issues
+
+- How to set the return value from the execution properly? We return them async
+- How to handle errors properly?
+- How to send packets in the proper format. I guess we need an ibc-reflect-send
+ contract on the origin chain?
+
+## Protocol
+
+We require version `ibc-reflect-v1` when making the ibc handshake.
+
+The packets sent look like:
+
+```rust
+pub enum PacketMsg {
+ Dispatch { msgs: Vec },
+ WhoAmI {},
+ Balances {},
+}
+```
+
+That is, one of the following:
+
+```json
+[
+ { "dispatch": ["large struct here.."] },
+ { "who_am_i": {} },
+ { "balances": {} }
+]
+```
+
+The success responses look like one of the following:
+
+Dispatch:
+
+```json
+{
+ "ok": null
+}
+```
+
+WhoAmI:
+
+```json
+{
+ "account": "wasm12skc92jiowf8hwfhofqfh225ss"
+}
+```
+
+Balances:
+
+```json
+{
+ "account": "wasm12skc92jiowf8hwfhofqfh225ss",
+ "balances": [
+ { "amount": "12345678", "denom": "uatom" },
+ { "amount": "777777", "denom": "tgrd" }
+ ]
+}
+```
+
+The error ack packet always looks like this:
+
+```json
+{
+ "error": "invalid packet: "
+}
+```
diff --git a/contracts/ibc-reflect/examples/schema.rs b/contracts/ibc-reflect/examples/schema.rs
new file mode 100644
index 0000000000..46a9ef0d58
--- /dev/null
+++ b/contracts/ibc-reflect/examples/schema.rs
@@ -0,0 +1,36 @@
+use std::env::current_dir;
+use std::fs::create_dir_all;
+
+use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for};
+
+use ibc_reflect::msg::{
+ AcknowledgementMsg, BalancesResponse, DispatchResponse, HandleMsg, InitMsg, PacketMsg,
+ QueryMsg, WhoAmIResponse,
+};
+
+fn main() {
+ let mut out_dir = current_dir().unwrap();
+ out_dir.push("schema");
+ create_dir_all(&out_dir).unwrap();
+ remove_schemas(&out_dir).unwrap();
+
+ export_schema(&schema_for!(HandleMsg), &out_dir);
+ export_schema(&schema_for!(InitMsg), &out_dir);
+ export_schema(&schema_for!(QueryMsg), &out_dir);
+ export_schema(&schema_for!(PacketMsg), &out_dir);
+ export_schema_with_title(
+ &mut schema_for!(AcknowledgementMsg),
+ &out_dir,
+ "AcknowledgementMsgBalances",
+ );
+ export_schema_with_title(
+ &mut schema_for!(AcknowledgementMsg),
+ &out_dir,
+ "AcknowledgementMsgDispatch",
+ );
+ export_schema_with_title(
+ &mut schema_for!(AcknowledgementMsg),
+ &out_dir,
+ "AcknowledgementMsgWhoAmI",
+ );
+}
diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json
new file mode 100644
index 0000000000..b4b7e3c16e
--- /dev/null
+++ b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json
@@ -0,0 +1,72 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "AcknowledgementMsgBalances",
+ "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, HandleResponse}; let response: HandleResponse = HandleResponse::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, HandleResponse}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok"
+ ],
+ "properties": {
+ "ok": {
+ "$ref": "#/definitions/BalancesResponse"
+ }
+ }
+ },
+ {
+ "description": "An error type that every custom error created by contract developers can be converted to. This could potientially have more structure, but String is the easiest.",
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ],
+ "definitions": {
+ "BalancesResponse": {
+ "description": "This is the success response we send on ack for PacketMsg::Balance. Just acknowledge success or error",
+ "type": "object",
+ "required": [
+ "account",
+ "balances"
+ ],
+ "properties": {
+ "account": {
+ "$ref": "#/definitions/HumanAddr"
+ },
+ "balances": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Coin"
+ }
+ }
+ }
+ },
+ "Coin": {
+ "type": "object",
+ "required": [
+ "amount",
+ "denom"
+ ],
+ "properties": {
+ "amount": {
+ "$ref": "#/definitions/Uint128"
+ },
+ "denom": {
+ "type": "string"
+ }
+ }
+ },
+ "HumanAddr": {
+ "type": "string"
+ },
+ "Uint128": {
+ "type": "string"
+ }
+ }
+}
diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_dispatch.json b/contracts/ibc-reflect/schema/acknowledgement_msg_dispatch.json
new file mode 100644
index 0000000000..21093421eb
--- /dev/null
+++ b/contracts/ibc-reflect/schema/acknowledgement_msg_dispatch.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "AcknowledgementMsgDispatch",
+ "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, HandleResponse}; let response: HandleResponse = HandleResponse::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, HandleResponse}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok"
+ ],
+ "properties": {
+ "ok": {
+ "type": "null"
+ }
+ }
+ },
+ {
+ "description": "An error type that every custom error created by contract developers can be converted to. This could potientially have more structure, but String is the easiest.",
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+}
diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json b/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json
new file mode 100644
index 0000000000..7b11ba2ae7
--- /dev/null
+++ b/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json
@@ -0,0 +1,47 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "AcknowledgementMsgWhoAmI",
+ "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, HandleResponse}; let response: HandleResponse = HandleResponse::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, HandleResponse}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok"
+ ],
+ "properties": {
+ "ok": {
+ "$ref": "#/definitions/WhoAmIResponse"
+ }
+ }
+ },
+ {
+ "description": "An error type that every custom error created by contract developers can be converted to. This could potientially have more structure, but String is the easiest.",
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ],
+ "definitions": {
+ "HumanAddr": {
+ "type": "string"
+ },
+ "WhoAmIResponse": {
+ "description": "This is the success response we send on ack for PacketMsg::WhoAmI. Return the caller's account address on the remote chain",
+ "type": "object",
+ "required": [
+ "account"
+ ],
+ "properties": {
+ "account": {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ }
+ }
+ }
+}
diff --git a/contracts/ibc-reflect/schema/handle_msg.json b/contracts/ibc-reflect/schema/handle_msg.json
new file mode 100644
index 0000000000..bd41e9cea7
--- /dev/null
+++ b/contracts/ibc-reflect/schema/handle_msg.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "HandleMsg",
+ "anyOf": [
+ {
+ "description": "InitCallback is returned from reflect contract after a new contract is set up",
+ "type": "object",
+ "required": [
+ "init_callback"
+ ],
+ "properties": {
+ "init_callback": {
+ "type": "object",
+ "required": [
+ "contract_addr",
+ "id"
+ ],
+ "properties": {
+ "contract_addr": {
+ "description": "contract_addr is the address of this contract",
+ "allOf": [
+ {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ ]
+ },
+ "id": {
+ "description": "id was provided in the InitMsg",
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "definitions": {
+ "HumanAddr": {
+ "type": "string"
+ }
+ }
+}
diff --git a/contracts/ibc-reflect/schema/init_msg.json b/contracts/ibc-reflect/schema/init_msg.json
new file mode 100644
index 0000000000..35a99523b7
--- /dev/null
+++ b/contracts/ibc-reflect/schema/init_msg.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "InitMsg",
+ "description": "InitMsg just needs to know the code_id of a reflect contract to spawn sub-accounts",
+ "type": "object",
+ "required": [
+ "reflect_code_id"
+ ],
+ "properties": {
+ "reflect_code_id": {
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ }
+ }
+}
diff --git a/contracts/ibc-reflect/schema/packet_msg.json b/contracts/ibc-reflect/schema/packet_msg.json
new file mode 100644
index 0000000000..4b046b8124
--- /dev/null
+++ b/contracts/ibc-reflect/schema/packet_msg.json
@@ -0,0 +1,544 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "PacketMsg",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "dispatch"
+ ],
+ "properties": {
+ "dispatch": {
+ "type": "object",
+ "required": [
+ "msgs"
+ ],
+ "properties": {
+ "msgs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/CosmosMsg_for_Empty"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "who_am_i"
+ ],
+ "properties": {
+ "who_am_i": {
+ "type": "object"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "balances"
+ ],
+ "properties": {
+ "balances": {
+ "type": "object"
+ }
+ }
+ }
+ ],
+ "definitions": {
+ "BankMsg": {
+ "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto",
+ "anyOf": [
+ {
+ "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.",
+ "type": "object",
+ "required": [
+ "send"
+ ],
+ "properties": {
+ "send": {
+ "type": "object",
+ "required": [
+ "amount",
+ "to_address"
+ ],
+ "properties": {
+ "amount": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Coin"
+ }
+ },
+ "to_address": {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "Binary": {
+ "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec",
+ "type": "string"
+ },
+ "Coin": {
+ "type": "object",
+ "required": [
+ "amount",
+ "denom"
+ ],
+ "properties": {
+ "amount": {
+ "$ref": "#/definitions/Uint128"
+ },
+ "denom": {
+ "type": "string"
+ }
+ }
+ },
+ "CosmosMsg_for_Empty": {
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "bank"
+ ],
+ "properties": {
+ "bank": {
+ "$ref": "#/definitions/BankMsg"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "custom"
+ ],
+ "properties": {
+ "custom": {
+ "$ref": "#/definitions/Empty"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "staking"
+ ],
+ "properties": {
+ "staking": {
+ "$ref": "#/definitions/StakingMsg"
+ }
+ }
+ },
+ {
+ "description": "A Stargate message encoded the same way as a protobof [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)",
+ "type": "object",
+ "required": [
+ "stargate"
+ ],
+ "properties": {
+ "stargate": {
+ "type": "object",
+ "required": [
+ "type_url",
+ "value"
+ ],
+ "properties": {
+ "type_url": {
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/definitions/Binary"
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ibc"
+ ],
+ "properties": {
+ "ibc": {
+ "$ref": "#/definitions/IbcMsg"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "wasm"
+ ],
+ "properties": {
+ "wasm": {
+ "$ref": "#/definitions/WasmMsg"
+ }
+ }
+ }
+ ]
+ },
+ "Empty": {
+ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)",
+ "type": "object"
+ },
+ "HumanAddr": {
+ "type": "string"
+ },
+ "IbcMsg": {
+ "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)",
+ "anyOf": [
+ {
+ "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.",
+ "type": "object",
+ "required": [
+ "transfer"
+ ],
+ "properties": {
+ "transfer": {
+ "type": "object",
+ "required": [
+ "amount",
+ "channel_id",
+ "to_address"
+ ],
+ "properties": {
+ "amount": {
+ "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Coin"
+ }
+ ]
+ },
+ "channel_id": {
+ "description": "exisiting channel to send the tokens over",
+ "type": "string"
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/IbcTimeoutHeight"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timeout_timestamp": {
+ "description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "type": [
+ "integer",
+ "null"
+ ],
+ "format": "uint64",
+ "minimum": 0.0
+ },
+ "to_address": {
+ "description": "address on the remote chain to receive these tokens",
+ "allOf": [
+ {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.",
+ "type": "object",
+ "required": [
+ "send_packet"
+ ],
+ "properties": {
+ "send_packet": {
+ "type": "object",
+ "required": [
+ "channel_id",
+ "data"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ },
+ "data": {
+ "$ref": "#/definitions/Binary"
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/IbcTimeoutHeight"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timeout_timestamp": {
+ "description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "type": [
+ "integer",
+ "null"
+ ],
+ "format": "uint64",
+ "minimum": 0.0
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port",
+ "type": "object",
+ "required": [
+ "close_channel"
+ ],
+ "properties": {
+ "close_channel": {
+ "type": "object",
+ "required": [
+ "channel_id"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "IbcTimeoutHeight": {
+ "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
+ "type": "object",
+ "required": [
+ "revision_number",
+ "timeout_height"
+ ],
+ "properties": {
+ "revision_number": {
+ "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. the height within the given revision",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ }
+ }
+ },
+ "StakingMsg": {
+ "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
+ "anyOf": [
+ {
+ "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.",
+ "type": "object",
+ "required": [
+ "delegate"
+ ],
+ "properties": {
+ "delegate": {
+ "type": "object",
+ "required": [
+ "amount",
+ "validator"
+ ],
+ "properties": {
+ "amount": {
+ "$ref": "#/definitions/Coin"
+ },
+ "validator": {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.",
+ "type": "object",
+ "required": [
+ "undelegate"
+ ],
+ "properties": {
+ "undelegate": {
+ "type": "object",
+ "required": [
+ "amount",
+ "validator"
+ ],
+ "properties": {
+ "amount": {
+ "$ref": "#/definitions/Coin"
+ },
+ "validator": {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37) followed by a [MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.",
+ "type": "object",
+ "required": [
+ "withdraw"
+ ],
+ "properties": {
+ "withdraw": {
+ "type": "object",
+ "required": [
+ "validator"
+ ],
+ "properties": {
+ "recipient": {
+ "description": "this is the \"withdraw address\", the one that should receive the rewards if None, then use delegator address",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/HumanAddr"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "validator": {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.",
+ "type": "object",
+ "required": [
+ "redelegate"
+ ],
+ "properties": {
+ "redelegate": {
+ "type": "object",
+ "required": [
+ "amount",
+ "dst_validator",
+ "src_validator"
+ ],
+ "properties": {
+ "amount": {
+ "$ref": "#/definitions/Coin"
+ },
+ "dst_validator": {
+ "$ref": "#/definitions/HumanAddr"
+ },
+ "src_validator": {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "Uint128": {
+ "type": "string"
+ },
+ "WasmMsg": {
+ "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto",
+ "anyOf": [
+ {
+ "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.",
+ "type": "object",
+ "required": [
+ "execute"
+ ],
+ "properties": {
+ "execute": {
+ "type": "object",
+ "required": [
+ "contract_addr",
+ "msg",
+ "send"
+ ],
+ "properties": {
+ "contract_addr": {
+ "$ref": "#/definitions/HumanAddr"
+ },
+ "msg": {
+ "description": "msg is the json-encoded HandleMsg struct (as raw Binary)",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Binary"
+ }
+ ]
+ },
+ "send": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Coin"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.",
+ "type": "object",
+ "required": [
+ "instantiate"
+ ],
+ "properties": {
+ "instantiate": {
+ "type": "object",
+ "required": [
+ "code_id",
+ "msg",
+ "send"
+ ],
+ "properties": {
+ "code_id": {
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ },
+ "label": {
+ "description": "optional human-readbale label for the contract",
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "msg": {
+ "description": "msg is the json-encoded InitMsg struct (as raw Binary)",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Binary"
+ }
+ ]
+ },
+ "send": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Coin"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/contracts/ibc-reflect/schema/query_msg.json b/contracts/ibc-reflect/schema/query_msg.json
new file mode 100644
index 0000000000..c0b3bc5fb8
--- /dev/null
+++ b/contracts/ibc-reflect/schema/query_msg.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "QueryMsg",
+ "anyOf": [
+ {
+ "description": "Returns (reflect) account that is attached to this channel, or none.",
+ "type": "object",
+ "required": [
+ "account"
+ ],
+ "properties": {
+ "account": {
+ "type": "object",
+ "required": [
+ "channel_id"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "Returns all (channel, reflect_account) pairs. No pagination - this is a test contract",
+ "type": "object",
+ "required": [
+ "list_accounts"
+ ],
+ "properties": {
+ "list_accounts": {
+ "type": "object"
+ }
+ }
+ }
+ ]
+}
diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs
new file mode 100644
index 0000000000..fd37ab8f07
--- /dev/null
+++ b/contracts/ibc-reflect/src/contract.rs
@@ -0,0 +1,482 @@
+use cosmwasm_std::{
+ entry_point, from_slice, to_binary, wasm_execute, wasm_instantiate, CosmosMsg, Deps, DepsMut,
+ Env, HandleResponse, HumanAddr, IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcOrder,
+ IbcPacket, IbcReceiveResponse, InitResponse, MessageInfo, Order, QueryResponse, StdError,
+ StdResult,
+};
+
+use crate::msg::{
+ AccountInfo, AccountResponse, AcknowledgementMsg, BalancesResponse, DispatchResponse,
+ HandleMsg, InitMsg, ListAccountsResponse, PacketMsg, QueryMsg, ReflectHandleMsg,
+ ReflectInitMsg, WhoAmIResponse,
+};
+use crate::state::{accounts, accounts_read, config, Config};
+
+pub const IBC_VERSION: &str = "ibc-reflect";
+
+#[entry_point]
+pub fn init(deps: DepsMut, _env: Env, _info: MessageInfo, msg: InitMsg) -> StdResult {
+ // we store the reflect_id for creating accounts later
+ let cfg = Config {
+ reflect_code_id: msg.reflect_code_id,
+ };
+ config(deps.storage).save(&cfg)?;
+
+ Ok(InitResponse::default())
+}
+
+#[entry_point]
+pub fn handle(
+ deps: DepsMut,
+ _env: Env,
+ info: MessageInfo,
+ msg: HandleMsg,
+) -> StdResult {
+ match msg {
+ HandleMsg::InitCallback { id, contract_addr } => {
+ handle_init_callback(deps, info, id, contract_addr)
+ }
+ }
+}
+
+pub fn handle_init_callback(
+ deps: DepsMut,
+ info: MessageInfo,
+ id: String,
+ contract_addr: HumanAddr,
+) -> StdResult {
+ // sanity check - the caller is registering itself
+ if info.sender != contract_addr {
+ return Err(StdError::generic_err("Must register self on callback"));
+ }
+
+ // store id -> contract_addr if it is empty
+ // id comes from: `let chan_id = msg.endpoint.channel_id;` in `ibc_channel_connect`
+ accounts(deps.storage).update(id.as_bytes(), |val| -> StdResult<_> {
+ match val {
+ Some(_) => Err(StdError::generic_err(
+ "Cannot register over an existing channel",
+ )),
+ None => Ok(contract_addr),
+ }
+ })?;
+
+ Ok(HandleResponse::default())
+}
+
+#[entry_point]
+pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
+ match msg {
+ QueryMsg::Account { channel_id } => to_binary(&query_account(deps, channel_id)?),
+ QueryMsg::ListAccounts {} => to_binary(&query_list_accounts(deps)?),
+ }
+}
+
+pub fn query_account(deps: Deps, channel_id: String) -> StdResult {
+ let account = accounts_read(deps.storage).load(channel_id.as_bytes())?;
+ Ok(AccountResponse {
+ account: Some(account),
+ })
+}
+
+pub fn query_list_accounts(deps: Deps) -> StdResult {
+ let accounts: StdResult> = accounts_read(deps.storage)
+ .range(None, None, Order::Ascending)
+ .map(|r| {
+ let (k, account) = r?;
+ Ok(AccountInfo {
+ account,
+ channel_id: String::from_utf8(k)?,
+ })
+ })
+ .collect();
+ Ok(ListAccountsResponse {
+ accounts: accounts?,
+ })
+}
+
+#[entry_point]
+/// enforces ordering and versioing constraints
+pub fn ibc_channel_open(_deps: DepsMut, _env: Env, channel: IbcChannel) -> StdResult<()> {
+ if channel.order != IbcOrder::Ordered {
+ return Err(StdError::generic_err("Only supports ordered channels"));
+ }
+ if channel.version.as_str() != IBC_VERSION {
+ return Err(StdError::generic_err(format!(
+ "Must set version to `{}`",
+ IBC_VERSION
+ )));
+ }
+ // TODO: do we need to check counterparty version as well?
+ // This flow needs to be well documented
+ if let Some(counter_version) = channel.counterparty_version {
+ if counter_version.as_str() != IBC_VERSION {
+ return Err(StdError::generic_err(format!(
+ "Counterparty version must be `{}`",
+ IBC_VERSION
+ )));
+ }
+ }
+
+ Ok(())
+}
+
+#[entry_point]
+/// once it's established, we create the reflect contract
+pub fn ibc_channel_connect(
+ deps: DepsMut,
+ _env: Env,
+ channel: IbcChannel,
+) -> StdResult {
+ let cfg = config(deps.storage).load()?;
+ let chan_id = channel.endpoint.channel_id;
+
+ let label = format!("ibc-reflect-{}", &chan_id);
+ let payload = ReflectInitMsg {
+ callback_id: Some(chan_id),
+ };
+ let msg = wasm_instantiate(cfg.reflect_code_id, &payload, vec![], Some(label))?;
+ Ok(IbcBasicResponse {
+ messages: vec![msg.into()],
+ attributes: vec![],
+ })
+}
+
+#[entry_point]
+/// we do nothing
+/// TODO: remove the account from the lookup?
+pub fn ibc_channel_close(
+ _deps: DepsMut,
+ _env: Env,
+ _channel: IbcChannel,
+) -> StdResult {
+ Ok(IbcBasicResponse::default())
+}
+
+#[entry_point]
+/// we look for a the proper reflect contract to relay to and send the message
+/// We cannot return any meaningful response value as we do not know the response value
+/// of execution. We just return ok if we dispatched, error if we failed to dispatch
+pub fn ibc_packet_receive(
+ deps: DepsMut,
+ _env: Env,
+ packet: IbcPacket,
+) -> StdResult {
+ // put this in a closure so we can convert all error responses into acknowledgements
+ (|| {
+ // which local channel did this packet come on
+ let caller = packet.dest.channel_id;
+ let msg: PacketMsg = from_slice(&packet.data)?;
+ match msg {
+ PacketMsg::Dispatch { msgs } => receive_dispatch(deps, caller, msgs),
+ PacketMsg::WhoAmI {} => receive_who_am_i(deps, caller),
+ PacketMsg::Balances {} => receive_balances(deps, caller),
+ }
+ })()
+ .or_else(|e| {
+ // we try to capture all app-level errors and convert them into
+ // acknowledgement packets that contain an error code.
+ let msg = format!("invalid packet: {}", e);
+ // we only use the error variant here, so we can use any T
+ let acknowledgement = to_binary(&AcknowledgementMsg::<()>::Err(msg))?;
+ Ok(IbcReceiveResponse {
+ acknowledgement,
+ messages: vec![],
+ attributes: vec![],
+ })
+ })
+}
+
+// processes PacketMsg::Dispatch variant
+fn receive_dispatch(
+ deps: DepsMut,
+ caller: String,
+ msgs: Vec,
+) -> StdResult {
+ // what is the reflect contract here
+ let reflect_addr = accounts(deps.storage).load(caller.as_bytes())?;
+
+ // let them know we're fine
+ let acknowledgement = to_binary(&AcknowledgementMsg::::Ok(()))?;
+ // create the message to re-dispatch to the reflect contract
+ let reflect_msg = ReflectHandleMsg::ReflectMsg { msgs };
+ let wasm_msg = wasm_execute(reflect_addr, &reflect_msg, vec![])?;
+ // and we are golden
+ Ok(IbcReceiveResponse {
+ acknowledgement,
+ messages: vec![wasm_msg.into()],
+ attributes: vec![],
+ })
+}
+
+// processes PacketMsg::WhoAmI variant
+fn receive_who_am_i(deps: DepsMut, caller: String) -> StdResult {
+ let account = accounts(deps.storage).load(caller.as_bytes())?;
+ let response = WhoAmIResponse { account };
+ let acknowledgement = to_binary(&AcknowledgementMsg::Ok(response))?;
+ // and we are golden
+ Ok(IbcReceiveResponse {
+ acknowledgement,
+ messages: vec![],
+ attributes: vec![],
+ })
+}
+
+// processes PacketMsg::Balances variant
+fn receive_balances(deps: DepsMut, caller: String) -> StdResult {
+ let account = accounts(deps.storage).load(caller.as_bytes())?;
+ let balances = deps.querier.query_all_balances(&account)?;
+ let response = BalancesResponse { account, balances };
+ let acknowledgement = to_binary(&AcknowledgementMsg::Ok(response))?;
+ // and we are golden
+ Ok(IbcReceiveResponse {
+ acknowledgement,
+ messages: vec![],
+ attributes: vec![],
+ })
+}
+
+#[entry_point]
+/// we do nothing
+pub fn ibc_packet_ack(
+ _deps: DepsMut,
+ _env: Env,
+ _ack: IbcAcknowledgement,
+) -> StdResult {
+ Ok(IbcBasicResponse::default())
+}
+
+#[entry_point]
+/// we do nothing
+pub fn ibc_packet_timeout(
+ _deps: DepsMut,
+ _env: Env,
+ _packet: IbcPacket,
+) -> StdResult {
+ Ok(IbcBasicResponse::default())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use cosmwasm_std::testing::{
+ mock_dependencies, mock_env, mock_ibc_channel, mock_ibc_packet_recv, mock_info, MockApi,
+ MockQuerier, MockStorage,
+ };
+ use cosmwasm_std::{coins, from_slice, BankMsg, OwnedDeps, WasmMsg};
+
+ const CREATOR: &str = "creator";
+ // code id of the reflect contract
+ const REFLECT_ID: u64 = 101;
+ // address of first reflect contract instance that we created
+ const REFLECT_ADDR: &str = "reflect-acct-1";
+
+ fn setup() -> OwnedDeps {
+ let mut deps = mock_dependencies(&[]);
+ let msg = InitMsg {
+ reflect_code_id: REFLECT_ID,
+ };
+ let info = mock_info(CREATOR, &[]);
+ let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
+ assert_eq!(0, res.messages.len());
+ deps
+ }
+
+ // connect will run through the entire handshake to set up a proper connect and
+ // save the account (tested in detail in `proper_handshake_flow`)
+ fn connect>(mut deps: DepsMut, channel_id: &str, account: T) {
+ let account = account.into();
+
+ // open packet has no counterparty versin, connect does
+ // TODO: validate this with alpe
+ let mut handshake_open = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ handshake_open.counterparty_version = None;
+ // first we try to open with a valid handshake
+ ibc_channel_open(deps.branch(), mock_env(), handshake_open).unwrap();
+
+ // then we connect (with counter-party version set)
+ let handshake_connect = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ ibc_channel_connect(deps.branch(), mock_env(), handshake_connect).unwrap();
+
+ // which creates a reflect account. here we get the callback
+ let handle_msg = HandleMsg::InitCallback {
+ id: channel_id.into(),
+ contract_addr: account.clone(),
+ };
+ let info = mock_info(account, &[]);
+ handle(deps.branch(), mock_env(), info, handle_msg).unwrap();
+ }
+
+ #[test]
+ fn init_works() {
+ let mut deps = mock_dependencies(&[]);
+
+ let msg = InitMsg {
+ reflect_code_id: 17,
+ };
+ let info = mock_info("creator", &[]);
+ let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
+ assert_eq!(0, res.messages.len())
+ }
+
+ #[test]
+ fn enforce_version_in_handshake() {
+ let mut deps = setup();
+
+ let wrong_order = mock_ibc_channel("channel-12", IbcOrder::Unordered, IBC_VERSION);
+ ibc_channel_open(deps.as_mut(), mock_env(), wrong_order).unwrap_err();
+
+ let wrong_version = mock_ibc_channel("channel-12", IbcOrder::Ordered, "reflect");
+ ibc_channel_open(deps.as_mut(), mock_env(), wrong_version).unwrap_err();
+
+ let valid_handshake = mock_ibc_channel("channel-12", IbcOrder::Ordered, IBC_VERSION);
+ ibc_channel_open(deps.as_mut(), mock_env(), valid_handshake).unwrap();
+ }
+
+ #[test]
+ fn proper_handshake_flow() {
+ let mut deps = setup();
+ let channel_id = "channel-1234";
+
+ // first we try to open with a valid handshake
+ let mut handshake_open = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ handshake_open.counterparty_version = None;
+ ibc_channel_open(deps.as_mut(), mock_env(), handshake_open).unwrap();
+
+ // then we connect (with counter-party version set)
+ let handshake_connect = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ let res = ibc_channel_connect(deps.as_mut(), mock_env(), handshake_connect).unwrap();
+ // and set up a reflect account
+ assert_eq!(1, res.messages.len());
+ if let CosmosMsg::Wasm(WasmMsg::Instantiate {
+ code_id,
+ msg,
+ send,
+ label,
+ }) = &res.messages[0]
+ {
+ assert_eq!(&REFLECT_ID, code_id);
+ assert_eq!(0, send.len());
+ assert!(label.as_ref().unwrap().contains(channel_id));
+ // parse the message - should callback with proper channel_id
+ let rmsg: ReflectInitMsg = from_slice(&msg).unwrap();
+ assert_eq!(rmsg.callback_id, Some(channel_id.to_string()));
+ } else {
+ panic!("invalid return message: {:?}", res.messages[0]);
+ }
+
+ // no accounts set yet
+ let raw = query(deps.as_ref(), mock_env(), QueryMsg::ListAccounts {}).unwrap();
+ let res: ListAccountsResponse = from_slice(&raw).unwrap();
+ assert_eq!(0, res.accounts.len());
+
+ // we get the callback from reflect
+ let handle_msg = HandleMsg::InitCallback {
+ id: channel_id.to_string(),
+ contract_addr: REFLECT_ADDR.into(),
+ };
+ let info = mock_info(REFLECT_ADDR, &[]);
+ let res = handle(deps.as_mut(), mock_env(), info, handle_msg).unwrap();
+ assert_eq!(0, res.messages.len());
+
+ // ensure this is now registered
+ let raw = query(deps.as_ref(), mock_env(), QueryMsg::ListAccounts {}).unwrap();
+ let res: ListAccountsResponse = from_slice(&raw).unwrap();
+ assert_eq!(1, res.accounts.len());
+ assert_eq!(
+ &res.accounts[0],
+ &AccountInfo {
+ account: REFLECT_ADDR.into(),
+ channel_id: channel_id.to_string(),
+ }
+ );
+
+ // and the account query also works
+ let raw = query(
+ deps.as_ref(),
+ mock_env(),
+ QueryMsg::Account {
+ channel_id: channel_id.to_string(),
+ },
+ )
+ .unwrap();
+ let res: AccountResponse = from_slice(&raw).unwrap();
+ assert_eq!(res.account.unwrap(), HumanAddr::from(REFLECT_ADDR));
+ }
+
+ #[test]
+ fn handle_dispatch_packet() {
+ let mut deps = setup();
+
+ let channel_id: &str = "channel-123";
+ let account: &str = "acct-123";
+
+ // receive a packet for an unregistered channel returns app-level error (not Result::Err)
+ let msgs_to_dispatch = vec![BankMsg::Send {
+ to_address: "my-friend".into(),
+ amount: coins(123456789, "uatom"),
+ }
+ .into()];
+ let ibc_msg = PacketMsg::Dispatch {
+ msgs: msgs_to_dispatch.clone(),
+ };
+ let packet = mock_ibc_packet_recv(channel_id, &ibc_msg).unwrap();
+ let res = ibc_packet_receive(deps.as_mut(), mock_env(), packet.clone()).unwrap();
+ // we didn't dispatch anything
+ assert_eq!(0, res.messages.len());
+ // acknowledgement is an error
+ let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap();
+ assert_eq!(
+ ack.unwrap_err(),
+ "invalid packet: cosmwasm_std::addresses::HumanAddr not found"
+ );
+
+ // register the channel
+ connect(deps.as_mut(), channel_id, account);
+
+ // receive a packet for an unregistered channel returns app-level error (not Result::Err)
+ let packet = mock_ibc_packet_recv(channel_id, &ibc_msg).unwrap();
+ let res = ibc_packet_receive(deps.as_mut(), mock_env(), packet.clone()).unwrap();
+
+ // TODO: blocked on serde-json-wasm fix
+ // see: https://github.com/CosmWasm/serde-json-wasm/issues/27
+ // assert app-level success
+ // let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap();
+ // ack.unwrap();
+
+ // and we dispatch the BankMsg
+ assert_eq!(1, res.messages.len());
+ // parse the output, ensuring it matches
+ if let CosmosMsg::Wasm(WasmMsg::Execute {
+ contract_addr,
+ msg,
+ send,
+ }) = &res.messages[0]
+ {
+ assert_eq!(account, contract_addr.as_str());
+ assert_eq!(0, send.len());
+ // parse the message - should callback with proper channel_id
+ let rmsg: ReflectHandleMsg = from_slice(&msg).unwrap();
+ assert_eq!(
+ rmsg,
+ ReflectHandleMsg::ReflectMsg {
+ msgs: msgs_to_dispatch
+ }
+ );
+ } else {
+ panic!("invalid return message: {:?}", res.messages[0]);
+ }
+
+ // invalid packet format on registered channel also returns app-level error
+ let bad_data = InitMsg {
+ reflect_code_id: 12345,
+ };
+ let packet = mock_ibc_packet_recv(channel_id, &bad_data).unwrap();
+ let res = ibc_packet_receive(deps.as_mut(), mock_env(), packet.clone()).unwrap();
+ // we didn't dispatch anything
+ assert_eq!(0, res.messages.len());
+ // acknowledgement is an error
+ let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap();
+ assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balances`");
+ }
+}
diff --git a/contracts/ibc-reflect/src/lib.rs b/contracts/ibc-reflect/src/lib.rs
new file mode 100644
index 0000000000..4934c19d5b
--- /dev/null
+++ b/contracts/ibc-reflect/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod contract;
+pub mod msg;
+pub mod state;
diff --git a/contracts/ibc-reflect/src/msg.rs b/contracts/ibc-reflect/src/msg.rs
new file mode 100644
index 0000000000..ac9794a28a
--- /dev/null
+++ b/contracts/ibc-reflect/src/msg.rs
@@ -0,0 +1,93 @@
+#![allow(clippy::field_reassign_with_default)] // see https://github.com/CosmWasm/cosmwasm/issues/685
+
+use cosmwasm_std::{Coin, ContractResult, CosmosMsg, HumanAddr};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+/// InitMsg just needs to know the code_id of a reflect contract to spawn sub-accounts
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct InitMsg {
+ pub reflect_code_id: u64,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum HandleMsg {
+ /// InitCallback is returned from reflect contract after a new contract is set up
+ InitCallback {
+ /// id was provided in the InitMsg
+ id: String,
+ /// contract_addr is the address of this contract
+ contract_addr: HumanAddr,
+ },
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum QueryMsg {
+ /// Returns (reflect) account that is attached to this channel,
+ /// or none.
+ Account { channel_id: String },
+ /// Returns all (channel, reflect_account) pairs.
+ /// No pagination - this is a test contract
+ ListAccounts {},
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct AccountResponse {
+ pub account: Option,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct ListAccountsResponse {
+ pub accounts: Vec,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct AccountInfo {
+ pub account: HumanAddr,
+ pub channel_id: String,
+}
+
+/// This is the message we send to the reflect contract to initialize it
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct ReflectInitMsg {
+ pub callback_id: Option,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ReflectHandleMsg {
+ ReflectMsg { msgs: Vec },
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketMsg {
+ Dispatch { msgs: Vec },
+ WhoAmI {},
+ Balances {},
+}
+
+/// All acknowledgements are wrapped in `ContractResult`.
+/// The success value depends on the PacketMsg variant.
+pub type AcknowledgementMsg = ContractResult;
+
+/// This is the success response we send on ack for PacketMsg::Dispatch.
+/// Just acknowledge success or error
+pub type DispatchResponse = ();
+
+/// This is the success response we send on ack for PacketMsg::WhoAmI.
+/// Return the caller's account address on the remote chain
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct WhoAmIResponse {
+ pub account: HumanAddr,
+}
+
+/// This is the success response we send on ack for PacketMsg::Balance.
+/// Just acknowledge success or error
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct BalancesResponse {
+ pub account: HumanAddr,
+ pub balances: Vec,
+}
diff --git a/contracts/ibc-reflect/src/state.rs b/contracts/ibc-reflect/src/state.rs
new file mode 100644
index 0000000000..44685a3e3e
--- /dev/null
+++ b/contracts/ibc-reflect/src/state.rs
@@ -0,0 +1,35 @@
+#![allow(clippy::field_reassign_with_default)] // see https://github.com/CosmWasm/cosmwasm/issues/685
+
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+use cosmwasm_std::{HumanAddr, Storage};
+use cosmwasm_storage::{
+ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
+ Singleton,
+};
+
+pub const KEY_CONFIG: &[u8] = b"config";
+pub const PREFIX_ACCOUNTS: &[u8] = b"accounts";
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct Config {
+ pub reflect_code_id: u64,
+}
+
+/// accounts is lookup of channel_id to reflect contract
+pub fn accounts(storage: &mut dyn Storage) -> Bucket {
+ bucket(storage, PREFIX_ACCOUNTS)
+}
+
+pub fn accounts_read(storage: &dyn Storage) -> ReadonlyBucket {
+ bucket_read(storage, PREFIX_ACCOUNTS)
+}
+
+pub fn config(storage: &mut dyn Storage) -> Singleton {
+ singleton(storage, KEY_CONFIG)
+}
+
+pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton {
+ singleton_read(storage, KEY_CONFIG)
+}
diff --git a/contracts/ibc-reflect/tests/integration.rs b/contracts/ibc-reflect/tests/integration.rs
new file mode 100644
index 0000000000..bdc30e2c76
--- /dev/null
+++ b/contracts/ibc-reflect/tests/integration.rs
@@ -0,0 +1,267 @@
+//! This integration test tries to run and call the generated wasm.
+//! It depends on a Wasm build being available, which you can create with `cargo wasm`.
+//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm.
+//!
+//! You can easily convert unit tests to integration tests.
+//! 1. First copy them over verbatum,
+//! 2. Then change
+//! let mut deps = mock_dependencies(20, &[]);
+//! to
+//! let mut deps = mock_instance(WASM, &[]);
+//! 3. If you access raw storage, where ever you see something like:
+//! deps.storage.get(CONFIG_KEY).expect("no data stored");
+//! replace it with:
+//! deps.with_storage(|store| {
+//! let data = store.get(CONFIG_KEY).expect("no data stored");
+//! //...
+//! });
+//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...)
+
+use cosmwasm_std::testing::{mock_ibc_channel, mock_ibc_packet_recv};
+use cosmwasm_std::{
+ coins, BankMsg, ContractResult, CosmosMsg, HandleResponse, HumanAddr, IbcBasicResponse,
+ IbcOrder, IbcReceiveResponse, InitResponse, WasmMsg,
+};
+use cosmwasm_vm::testing::{
+ handle, ibc_channel_connect, ibc_channel_open, ibc_packet_receive, init, mock_env, mock_info,
+ mock_instance, query, MockApi, MockQuerier, MockStorage,
+};
+use cosmwasm_vm::{from_slice, Instance};
+
+use ibc_reflect::contract::IBC_VERSION;
+use ibc_reflect::msg::{
+ AccountInfo, AccountResponse, AcknowledgementMsg, DispatchResponse, HandleMsg, InitMsg,
+ ListAccountsResponse, PacketMsg, QueryMsg, ReflectHandleMsg, ReflectInitMsg,
+};
+
+// This line will test the output of cargo wasm
+static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/ibc_reflect.wasm");
+
+const CREATOR: &str = "creator";
+// code id of the reflect contract
+const REFLECT_ID: u64 = 101;
+// address of first reflect contract instance that we created
+const REFLECT_ADDR: &str = "reflect-acct-1";
+
+fn setup() -> Instance {
+ let mut deps = mock_instance(WASM, &[]);
+ let msg = InitMsg {
+ reflect_code_id: REFLECT_ID,
+ };
+ let info = mock_info(CREATOR, &[]);
+ let res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap();
+ assert_eq!(0, res.messages.len());
+ deps
+}
+
+// connect will run through the entire handshake to set up a proper connect and
+// save the account (tested in detail in `proper_handshake_flow`)
+fn connect>(
+ deps: &mut Instance,
+ channel_id: &str,
+ account: T,
+) {
+ let account = account.into();
+ // first we try to open with a valid handshake
+ let mut handshake_open = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ handshake_open.counterparty_version = None;
+ ibc_channel_open(deps, mock_env(), handshake_open).unwrap();
+
+ // then we connect (with counter-party version set)
+ let handshake_connect = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ let _: IbcBasicResponse = ibc_channel_connect(deps, mock_env(), handshake_connect).unwrap();
+
+ // which creates a reflect account. here we get the callback
+ let handle_msg = HandleMsg::InitCallback {
+ id: channel_id.into(),
+ contract_addr: account.clone(),
+ };
+ let info = mock_info(account, &[]);
+ let _: HandleResponse = handle(deps, mock_env(), info, handle_msg).unwrap();
+}
+
+#[test]
+fn ibc_entry_points_are_detected() {
+ let deps = mock_instance(WASM, &[]);
+ assert_eq!(deps.has_ibc_entry_points(), true);
+}
+
+#[test]
+fn init_works() {
+ let mut deps = mock_instance(WASM, &[]);
+
+ let msg = InitMsg {
+ reflect_code_id: 17,
+ };
+ let info = mock_info("creator", &[]);
+ let res: ContractResult = init(&mut deps, mock_env(), info, msg);
+ let msgs = res.unwrap().messages;
+ assert_eq!(0, msgs.len());
+}
+
+#[test]
+fn enforce_version_in_handshake() {
+ let mut deps = setup();
+
+ let wrong_order = mock_ibc_channel("channel-1234", IbcOrder::Unordered, IBC_VERSION);
+ ibc_channel_open(&mut deps, mock_env(), wrong_order).unwrap_err();
+
+ let wrong_version = mock_ibc_channel("channel-1234", IbcOrder::Ordered, "reflect");
+ ibc_channel_open(&mut deps, mock_env(), wrong_version).unwrap_err();
+
+ let valid_handshake = mock_ibc_channel("channel-1234", IbcOrder::Ordered, IBC_VERSION);
+ ibc_channel_open(&mut deps, mock_env(), valid_handshake).unwrap();
+}
+
+#[test]
+fn proper_handshake_flow() {
+ let mut deps = setup();
+ let channel_id = "channel-432";
+
+ // first we try to open with a valid handshake
+ let mut handshake_open = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ handshake_open.counterparty_version = None;
+ ibc_channel_open(&mut deps, mock_env(), handshake_open).unwrap();
+
+ // then we connect (with counter-party version set)
+ let handshake_connect = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION);
+ let res: IbcBasicResponse =
+ ibc_channel_connect(&mut deps, mock_env(), handshake_connect).unwrap();
+ // and set up a reflect account
+ assert_eq!(1, res.messages.len());
+ if let CosmosMsg::Wasm(WasmMsg::Instantiate {
+ code_id,
+ msg,
+ send,
+ label,
+ }) = &res.messages[0]
+ {
+ assert_eq!(&REFLECT_ID, code_id);
+ assert_eq!(0, send.len());
+ assert!(label.as_ref().unwrap().contains(channel_id));
+ // parse the message - should callback with proper channel_id
+ let rmsg: ReflectInitMsg = from_slice(&msg).unwrap();
+ assert_eq!(rmsg.callback_id, Some(channel_id.to_string()));
+ } else {
+ panic!("invalid return message: {:?}", res.messages[0]);
+ }
+
+ // no accounts set yet
+ let raw = query(&mut deps, mock_env(), QueryMsg::ListAccounts {}).unwrap();
+ let res: ListAccountsResponse = from_slice(&raw).unwrap();
+ assert_eq!(0, res.accounts.len());
+
+ // we get the callback from reflect
+ let handle_msg = HandleMsg::InitCallback {
+ id: channel_id.to_string(),
+ contract_addr: REFLECT_ADDR.into(),
+ };
+ let info = mock_info(REFLECT_ADDR, &[]);
+ let res: HandleResponse = handle(&mut deps, mock_env(), info, handle_msg).unwrap();
+ assert_eq!(0, res.messages.len());
+
+ // ensure this is now registered
+ let raw = query(&mut deps, mock_env(), QueryMsg::ListAccounts {}).unwrap();
+ let res: ListAccountsResponse = from_slice(&raw).unwrap();
+ assert_eq!(1, res.accounts.len());
+ assert_eq!(
+ &res.accounts[0],
+ &AccountInfo {
+ account: REFLECT_ADDR.into(),
+ channel_id: channel_id.to_string(),
+ }
+ );
+
+ // and the account query also works
+ let raw = query(
+ &mut deps,
+ mock_env(),
+ QueryMsg::Account {
+ channel_id: channel_id.to_string(),
+ },
+ )
+ .unwrap();
+ let res: AccountResponse = from_slice(&raw).unwrap();
+ assert_eq!(res.account.unwrap(), HumanAddr::from(REFLECT_ADDR));
+}
+
+#[test]
+fn handle_dispatch_packet() {
+ let mut deps = setup();
+
+ let channel_id: &str = "channel-123";
+ let account: &str = "acct-123";
+
+ // receive a packet for an unregistered channel returns app-level error (not Result::Err)
+ let msgs_to_dispatch = vec![BankMsg::Send {
+ to_address: "my-friend".into(),
+ amount: coins(123456789, "uatom"),
+ }
+ .into()];
+ let ibc_msg = PacketMsg::Dispatch {
+ msgs: msgs_to_dispatch.clone(),
+ };
+ let packet = mock_ibc_packet_recv(channel_id, &ibc_msg).unwrap();
+ let res: IbcReceiveResponse =
+ ibc_packet_receive(&mut deps, mock_env(), packet.clone()).unwrap();
+ // we didn't dispatch anything
+ assert_eq!(0, res.messages.len());
+ // acknowledgement is an error
+ let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap();
+ assert_eq!(
+ ack.unwrap_err(),
+ "invalid packet: cosmwasm_std::addresses::HumanAddr not found"
+ );
+
+ // register the channel
+ connect(&mut deps, channel_id, account);
+
+ // receive a packet for an unregistered channel returns app-level error (not Result::Err)
+ let packet = mock_ibc_packet_recv(channel_id, &ibc_msg).unwrap();
+ let res: IbcReceiveResponse =
+ ibc_packet_receive(&mut deps, mock_env(), packet.clone()).unwrap();
+ println!(
+ "{}",
+ String::from_utf8(res.acknowledgement.0.clone()).unwrap()
+ );
+
+ // assert app-level success
+ let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap();
+ ack.unwrap();
+
+ // and we dispatch the BankMsg
+ assert_eq!(1, res.messages.len());
+ // parse the output, ensuring it matches
+ if let CosmosMsg::Wasm(WasmMsg::Execute {
+ contract_addr,
+ msg,
+ send,
+ }) = &res.messages[0]
+ {
+ assert_eq!(account, contract_addr.as_str());
+ assert_eq!(0, send.len());
+ // parse the message - should callback with proper channel_id
+ let rmsg: ReflectHandleMsg = from_slice(&msg).unwrap();
+ assert_eq!(
+ rmsg,
+ ReflectHandleMsg::ReflectMsg {
+ msgs: msgs_to_dispatch
+ }
+ );
+ } else {
+ panic!("invalid return message: {:?}", res.messages[0]);
+ }
+
+ // invalid packet format on registered channel also returns app-level error
+ let bad_data = InitMsg {
+ reflect_code_id: 12345,
+ };
+ let packet = mock_ibc_packet_recv(channel_id, &bad_data).unwrap();
+ let res: IbcReceiveResponse =
+ ibc_packet_receive(&mut deps, mock_env(), packet.clone()).unwrap();
+ // we didn't dispatch anything
+ assert_eq!(0, res.messages.len());
+ // acknowledgement is an error
+ let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap();
+ assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balances`");
+}
diff --git a/contracts/reflect/Cargo.toml b/contracts/reflect/Cargo.toml
index 40b374771d..811e980e52 100644
--- a/contracts/reflect/Cargo.toml
+++ b/contracts/reflect/Cargo.toml
@@ -33,12 +33,12 @@ cranelift = ["cosmwasm-vm/cranelift"]
backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"]
[dependencies]
-cosmwasm-std = { path = "../../packages/std", features = ["staking"] }
+cosmwasm-std = { path = "../../packages/std", features = ["staking", "stargate"] }
cosmwasm-storage = { path = "../../packages/storage" }
schemars = "0.7"
serde = { version = "=1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0"
[dev-dependencies]
-cosmwasm-vm = { path = "../../packages/vm", default-features = false }
+cosmwasm-vm = { path = "../../packages/vm", default-features = false, features = ["stargate"] }
cosmwasm-schema = { path = "../../packages/schema" }
diff --git a/contracts/reflect/schema/handle_msg.json b/contracts/reflect/schema/handle_msg.json
index 70c2242e9b..7694ac46d8 100644
--- a/contracts/reflect/schema/handle_msg.json
+++ b/contracts/reflect/schema/handle_msg.json
@@ -131,6 +131,41 @@
}
}
},
+ {
+ "description": "A Stargate message encoded the same way as a protobof [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)",
+ "type": "object",
+ "required": [
+ "stargate"
+ ],
+ "properties": {
+ "stargate": {
+ "type": "object",
+ "required": [
+ "type_url",
+ "value"
+ ],
+ "properties": {
+ "type_url": {
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/definitions/Binary"
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ibc"
+ ],
+ "properties": {
+ "ibc": {
+ "$ref": "#/definitions/IbcMsg"
+ }
+ }
+ },
{
"type": "object",
"required": [
@@ -174,6 +209,156 @@
"HumanAddr": {
"type": "string"
},
+ "IbcMsg": {
+ "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)",
+ "anyOf": [
+ {
+ "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.",
+ "type": "object",
+ "required": [
+ "transfer"
+ ],
+ "properties": {
+ "transfer": {
+ "type": "object",
+ "required": [
+ "amount",
+ "channel_id",
+ "to_address"
+ ],
+ "properties": {
+ "amount": {
+ "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Coin"
+ }
+ ]
+ },
+ "channel_id": {
+ "description": "exisiting channel to send the tokens over",
+ "type": "string"
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/IbcTimeoutHeight"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timeout_timestamp": {
+ "description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "type": [
+ "integer",
+ "null"
+ ],
+ "format": "uint64",
+ "minimum": 0.0
+ },
+ "to_address": {
+ "description": "address on the remote chain to receive these tokens",
+ "allOf": [
+ {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.",
+ "type": "object",
+ "required": [
+ "send_packet"
+ ],
+ "properties": {
+ "send_packet": {
+ "type": "object",
+ "required": [
+ "channel_id",
+ "data"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ },
+ "data": {
+ "$ref": "#/definitions/Binary"
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/IbcTimeoutHeight"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timeout_timestamp": {
+ "description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "type": [
+ "integer",
+ "null"
+ ],
+ "format": "uint64",
+ "minimum": 0.0
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port",
+ "type": "object",
+ "required": [
+ "close_channel"
+ ],
+ "properties": {
+ "close_channel": {
+ "type": "object",
+ "required": [
+ "channel_id"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "IbcTimeoutHeight": {
+ "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
+ "type": "object",
+ "required": [
+ "revision_number",
+ "timeout_height"
+ ],
+ "properties": {
+ "revision_number": {
+ "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. the height within the given revision",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ }
+ }
+ },
"StakingMsg": {
"description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
"anyOf": [
diff --git a/contracts/reflect/schema/handle_response_for__custom_msg.json b/contracts/reflect/schema/handle_response_for__custom_msg.json
index 038c49ddab..40c8ae534d 100644
--- a/contracts/reflect/schema/handle_response_for__custom_msg.json
+++ b/contracts/reflect/schema/handle_response_for__custom_msg.json
@@ -134,6 +134,41 @@
}
}
},
+ {
+ "description": "A Stargate message encoded the same way as a protobof [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)",
+ "type": "object",
+ "required": [
+ "stargate"
+ ],
+ "properties": {
+ "stargate": {
+ "type": "object",
+ "required": [
+ "type_url",
+ "value"
+ ],
+ "properties": {
+ "type_url": {
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/definitions/Binary"
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ibc"
+ ],
+ "properties": {
+ "ibc": {
+ "$ref": "#/definitions/IbcMsg"
+ }
+ }
+ },
{
"type": "object",
"required": [
@@ -177,6 +212,156 @@
"HumanAddr": {
"type": "string"
},
+ "IbcMsg": {
+ "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)",
+ "anyOf": [
+ {
+ "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.",
+ "type": "object",
+ "required": [
+ "transfer"
+ ],
+ "properties": {
+ "transfer": {
+ "type": "object",
+ "required": [
+ "amount",
+ "channel_id",
+ "to_address"
+ ],
+ "properties": {
+ "amount": {
+ "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Coin"
+ }
+ ]
+ },
+ "channel_id": {
+ "description": "exisiting channel to send the tokens over",
+ "type": "string"
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/IbcTimeoutHeight"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timeout_timestamp": {
+ "description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "type": [
+ "integer",
+ "null"
+ ],
+ "format": "uint64",
+ "minimum": 0.0
+ },
+ "to_address": {
+ "description": "address on the remote chain to receive these tokens",
+ "allOf": [
+ {
+ "$ref": "#/definitions/HumanAddr"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.",
+ "type": "object",
+ "required": [
+ "send_packet"
+ ],
+ "properties": {
+ "send_packet": {
+ "type": "object",
+ "required": [
+ "channel_id",
+ "data"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ },
+ "data": {
+ "$ref": "#/definitions/Binary"
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/IbcTimeoutHeight"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timeout_timestamp": {
+ "description": "block timestamp (in nanoseconds) after which the packet times out. at least one of timeout_height, timeout_timestamp is required",
+ "type": [
+ "integer",
+ "null"
+ ],
+ "format": "uint64",
+ "minimum": 0.0
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port",
+ "type": "object",
+ "required": [
+ "close_channel"
+ ],
+ "properties": {
+ "close_channel": {
+ "type": "object",
+ "required": [
+ "channel_id"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "IbcTimeoutHeight": {
+ "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
+ "type": "object",
+ "required": [
+ "revision_number",
+ "timeout_height"
+ ],
+ "properties": {
+ "revision_number": {
+ "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ },
+ "timeout_height": {
+ "description": "block height after which the packet times out. the height within the given revision",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0.0
+ }
+ }
+ },
"StakingMsg": {
"description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
"anyOf": [
diff --git a/contracts/reflect/schema/init_msg.json b/contracts/reflect/schema/init_msg.json
index 2b274b4e6d..c5d4551a8d 100644
--- a/contracts/reflect/schema/init_msg.json
+++ b/contracts/reflect/schema/init_msg.json
@@ -1,5 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InitMsg",
- "type": "object"
+ "type": "object",
+ "properties": {
+ "callback_id": {
+ "description": "if set, returns CallbackMsg::InitCallback{} to the caller with this contract's address and this id",
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
}
diff --git a/contracts/reflect/schema/query_msg.json b/contracts/reflect/schema/query_msg.json
index d8cde67a9c..913d4f9e3c 100644
--- a/contracts/reflect/schema/query_msg.json
+++ b/contracts/reflect/schema/query_msg.json
@@ -134,6 +134,69 @@
"HumanAddr": {
"type": "string"
},
+ "IbcQuery": {
+ "description": "These are queries to the various IBC modules to see the state of the contract's IBC connection. These will return errors if the contract is not \"ibc enabled\"",
+ "anyOf": [
+ {
+ "description": "Gets the Port ID the current contract is bound to. Returns PortIdResponse",
+ "type": "object",
+ "required": [
+ "port_id"
+ ],
+ "properties": {
+ "port_id": {
+ "type": "object"
+ }
+ }
+ },
+ {
+ "description": "Lists all (portID, channelID) pairs that are bound to a given port If port_id is omitted, list all channels bound to the contract's port. Returns ListChannelsResponse.",
+ "type": "object",
+ "required": [
+ "list_channels"
+ ],
+ "properties": {
+ "list_channels": {
+ "type": "object",
+ "properties": {
+ "port_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ "description": "Lists all information for a (portID, channelID) pair. If port_id is omitted, it will default to the contract's own channel. (To save a PortId{} call) Returns ChannelResponse.",
+ "type": "object",
+ "required": [
+ "channel"
+ ],
+ "properties": {
+ "channel": {
+ "type": "object",
+ "required": [
+ "channel_id"
+ ],
+ "properties": {
+ "channel_id": {
+ "type": "string"
+ },
+ "port_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
"QueryRequest_for_SpecialQuery": {
"anyOf": [
{
@@ -169,6 +232,47 @@
}
}
},
+ {
+ "description": "A Stargate query encoded the same way as abci_query, with path and protobuf encoded Data. The format is defined in [ADR-21](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-021-protobuf-query-encoding.md) The response is also protobuf encoded. The caller is responsible for compiling the proper protobuf definitions",
+ "type": "object",
+ "required": [
+ "stargate"
+ ],
+ "properties": {
+ "stargate": {
+ "type": "object",
+ "required": [
+ "data",
+ "path"
+ ],
+ "properties": {
+ "data": {
+ "description": "this is the expected protobuf message type (not any), binary encoded",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Binary"
+ }
+ ]
+ },
+ "path": {
+ "description": "this is the fully qualified service path used for routing, eg. custom/cosmos_sdk.x.bank.v1.Query/QueryBalance",
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ibc"
+ ],
+ "properties": {
+ "ibc": {
+ "$ref": "#/definitions/IbcQuery"
+ }
+ }
+ },
{
"type": "object",
"required": [
diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs
index 8d5cf8fdcb..92f220141b 100644
--- a/contracts/reflect/src/contract.rs
+++ b/contracts/reflect/src/contract.rs
@@ -1,28 +1,42 @@
use cosmwasm_std::{
- attr, to_binary, to_vec, Binary, ContractResult, CosmosMsg, Deps, DepsMut, Env, HandleResponse,
- HumanAddr, InitResponse, MessageInfo, QueryRequest, QueryResponse, StdError, StdResult,
- SystemResult,
+ attr, to_binary, to_vec, Binary, Context, ContractResult, CosmosMsg, Deps, DepsMut, Env,
+ HandleResponse, HumanAddr, InitResponse, MessageInfo, QueryRequest, QueryResponse, StdError,
+ StdResult, SystemResult, WasmMsg,
};
use crate::errors::ReflectError;
use crate::msg::{
- CapitalizedResponse, ChainResponse, CustomMsg, HandleMsg, InitMsg, OwnerResponse, QueryMsg,
- RawResponse, SpecialQuery, SpecialResponse,
+ CallbackMsg, CapitalizedResponse, ChainResponse, CustomMsg, HandleMsg, InitMsg, OwnerResponse,
+ QueryMsg, RawResponse, SpecialQuery, SpecialResponse,
};
use crate::state::{config, config_read, State};
+use std::convert::TryInto;
pub fn init(
deps: DepsMut,
- _env: Env,
+ env: Env,
info: MessageInfo,
- _msg: InitMsg,
+ msg: InitMsg,
) -> StdResult> {
let state = State {
owner: deps.api.canonical_address(&info.sender)?,
};
-
config(deps.storage).save(&state)?;
- Ok(InitResponse::default())
+
+ let mut ctx = Context::new();
+ if let Some(id) = msg.callback_id {
+ let data = CallbackMsg::InitCallback {
+ id,
+ contract_addr: env.contract.address,
+ };
+ let msg = WasmMsg::Execute {
+ contract_addr: info.sender,
+ msg: to_binary(&data)?,
+ send: vec![],
+ };
+ ctx.add_message(msg);
+ }
+ ctx.try_into()
}
pub fn handle(
@@ -149,7 +163,7 @@ mod tests {
fn proper_initialization() {
let mut deps = mock_dependencies_with_custom_querier(&[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(1000, "earth"));
// we can just call .unwrap() to assert this was a success
@@ -161,11 +175,50 @@ mod tests {
assert_eq!("creator", value.owner.as_str());
}
+ #[test]
+ fn init_with_callback() {
+ let mut deps = mock_dependencies_with_custom_querier(&[]);
+ let caller = HumanAddr::from("calling-contract");
+
+ let msg = InitMsg {
+ callback_id: Some("foobar".to_string()),
+ };
+ let info = mock_info(&caller, &coins(1000, "earth"));
+
+ // we can just call .unwrap() to assert this was a success
+ let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
+ assert_eq!(1, res.messages.len());
+ let msg = &res.messages[0];
+ match msg {
+ CosmosMsg::Wasm(WasmMsg::Execute {
+ contract_addr,
+ msg,
+ send,
+ }) => {
+ assert_eq!(contract_addr, &caller);
+ let parsed: CallbackMsg = from_binary(&msg).unwrap();
+ assert_eq!(
+ parsed,
+ CallbackMsg::InitCallback {
+ id: "foobar".to_string(),
+ contract_addr: MOCK_CONTRACT_ADDR.into(),
+ }
+ );
+ assert_eq!(0, send.len());
+ }
+ _ => panic!("expect wasm execute message"),
+ }
+
+ // it worked, let's query the state
+ let value = query_owner(deps.as_ref()).unwrap();
+ assert_eq!(caller, value.owner);
+ }
+
#[test]
fn reflect() {
let mut deps = mock_dependencies_with_custom_querier(&[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
@@ -187,7 +240,7 @@ mod tests {
fn reflect_requires_owner() {
let mut deps = mock_dependencies_with_custom_querier(&[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
@@ -213,7 +266,7 @@ mod tests {
fn reflect_reject_empty_msgs() {
let mut deps = mock_dependencies_with_custom_querier(&[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
@@ -231,7 +284,7 @@ mod tests {
fn reflect_multiple_messages() {
let mut deps = mock_dependencies_with_custom_querier(&[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
@@ -263,7 +316,7 @@ mod tests {
fn change_owner_works() {
let mut deps = mock_dependencies_with_custom_querier(&[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
@@ -284,7 +337,7 @@ mod tests {
fn change_owner_requires_current_owner_as_sender() {
let mut deps = mock_dependencies_with_custom_querier(&[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let creator = HumanAddr::from("creator");
let info = mock_info(&creator, &coins(2, "token"));
let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
@@ -307,7 +360,7 @@ mod tests {
let mut deps = mock_dependencies_with_custom_querier(&[]);
let creator = HumanAddr::from("creator");
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info(&creator, &coins(2, "token"));
let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
diff --git a/contracts/reflect/src/msg.rs b/contracts/reflect/src/msg.rs
index 233b4aa81a..1ae4ee7d24 100644
--- a/contracts/reflect/src/msg.rs
+++ b/contracts/reflect/src/msg.rs
@@ -6,7 +6,23 @@ use serde::{Deserialize, Serialize};
use cosmwasm_std::{Binary, CosmosMsg, CustomQuery, HumanAddr, QueryRequest};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct InitMsg {}
+pub struct InitMsg {
+ /// if set, returns CallbackMsg::InitCallback{} to the caller with this contract's address
+ /// and this id
+ pub callback_id: Option,
+}
+
+/// This is what we return upon init if callback is set
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum CallbackMsg {
+ InitCallback {
+ /// id was provided in the InitMsg
+ id: String,
+ /// contract_addr is the address of this contract
+ contract_addr: HumanAddr,
+ },
+}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs
index 4524b11703..e7302fe276 100644
--- a/contracts/reflect/tests/integration.rs
+++ b/contracts/reflect/tests/integration.rs
@@ -60,7 +60,7 @@ pub fn mock_dependencies_with_custom_querier(
fn proper_initialization() {
let mut deps = mock_instance(WASM, &[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(1000, "earth"));
// we can just call .unwrap() to assert this was a success
@@ -77,7 +77,7 @@ fn proper_initialization() {
fn reflect() {
let mut deps = mock_instance(WASM, &[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap();
@@ -110,7 +110,7 @@ fn reflect() {
fn reflect_requires_owner() {
let mut deps = mock_instance(WASM, &[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap();
@@ -134,7 +134,7 @@ fn reflect_requires_owner() {
fn transfer() {
let mut deps = mock_instance(WASM, &[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap();
@@ -156,7 +156,7 @@ fn transfer() {
fn transfer_requires_owner() {
let mut deps = mock_instance(WASM, &[]);
- let msg = InitMsg {};
+ let msg = InitMsg { callback_id: None };
let info = mock_info("creator", &coins(2, "token"));
let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap();
diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs
index c150cdae2d..e536f9d0e0 100644
--- a/packages/std/src/ibc.rs
+++ b/packages/std/src/ibc.rs
@@ -4,6 +4,7 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
+use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt;
use crate::addresses::HumanAddr;
@@ -22,7 +23,7 @@ pub enum IbcMsg {
/// and a matching module on the remote chain.
/// We cannot select the port_id, this is whatever the local chain has bound the ibctransfer
/// module to.
- Ics20Transfer {
+ Transfer {
/// exisiting channel to send the tokens over
channel_id: String,
/// address on the remote chain to receive these tokens
@@ -30,6 +31,12 @@ pub enum IbcMsg {
/// packet data only supports one coin
/// https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20
amount: Coin,
+ /// block height after which the packet times out.
+ /// at least one of timeout_height, timeout_timestamp is required
+ timeout_height: Option,
+ /// block timestamp (in nanoseconds) after which the packet times out.
+ /// at least one of timeout_height, timeout_timestamp is required
+ timeout_timestamp: Option,
},
/// Sends an IBC packet with given data over the existing channel.
/// Data should be encoded in a format defined by the channel version,
@@ -37,9 +44,12 @@ pub enum IbcMsg {
SendPacket {
channel_id: String,
data: Binary,
- timeout_height: IbcTimeoutHeight,
- timeout_timestamp: u64,
- version: u64,
+ /// block height after which the packet times out.
+ /// at least one of timeout_height, timeout_timestamp is required
+ timeout_height: Option,
+ /// block timestamp (in nanoseconds) after which the packet times out.
+ /// at least one of timeout_height, timeout_timestamp is required
+ timeout_timestamp: Option,
},
/// This will close an existing channel that is owned by this contract.
/// Port is auto-assigned to the contracts' ibc port
@@ -81,7 +91,7 @@ pub struct ListChannelsResponse {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ChannelResponse {
- pub channel: IbcChannel,
+ pub channel: Option,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -107,7 +117,6 @@ pub struct IbcChannel {
pub connection_id: String,
}
-// TODO: check what representation we want here for encoding - string or number
/// IbcOrder defines if a channel is ORDERED or UNORDERED
/// Values come from https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/core/channel/v1/channel.proto#L69-L80
/// Naming comes from the protobuf files and go translations.
@@ -119,11 +128,11 @@ pub enum IbcOrder {
Ordered,
}
-// IBCTimeoutHeight Height is a monotonically increasing data type
-// that can be compared against another Height for the purposes of updating and
-// freezing clients.
-// Ordering is (revision_number, timeout_height)
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+/// IBCTimeoutHeight Height is a monotonically increasing data type
+/// that can be compared against another Height for the purposes of updating and
+/// freezing clients.
+/// Ordering is (revision_number, timeout_height)
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct IbcTimeoutHeight {
/// the version that the client is currently on
/// (eg. after reseting the chain this could increment 1 as height drops to 0)
@@ -133,6 +142,27 @@ pub struct IbcTimeoutHeight {
pub timeout_height: u64,
}
+impl IbcTimeoutHeight {
+ pub fn is_zero(&self) -> bool {
+ self.revision_number == 0 && self.timeout_height == 0
+ }
+}
+
+impl PartialOrd for IbcTimeoutHeight {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for IbcTimeoutHeight {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match self.revision_number.cmp(&other.revision_number) {
+ Ordering::Equal => self.timeout_height.cmp(&other.timeout_height),
+ other => other,
+ }
+ }
+}
+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcPacket {
/// The raw data send from the other side in the packet
@@ -143,12 +173,12 @@ pub struct IbcPacket {
pub dest: IbcEndpoint,
/// The sequence number of the packet on the given channel
pub sequence: u64,
- /// block height after which the packet times out
- pub timeout_height: IbcTimeoutHeight,
- /// block timestamp (in nanoseconds) after which the packet times out
- pub timeout_timestamp: u64,
- // the version that the client is currently on
- pub version: u64,
+ /// block height after which the packet times out.
+ /// at least one of timeout_height, timeout_timestamp is required
+ pub timeout_height: Option,
+ /// block timestamp (in nanoseconds) after which the packet times out.
+ /// at least one of timeout_height, timeout_timestamp is required
+ pub timeout_timestamp: Option,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -186,13 +216,11 @@ where
}
}
-/// This is the return value for the majority of the ibc handlers.
-/// That are able to dispatch messages / events on their own,
-/// but have no meaningful return value to the calling code.
-///
-/// Callbacks that have return values (like receive_packet)
-/// or that cannot redispatch messages (like the handshake callbacks)
-/// will use other Response types
+// This defines the return value on packet response processing.
+// This "success" case should be returned even in application-level errors,
+// Where the acknowledgement bytes contain an encoded error message to be returned to
+// the calling chain. (Returning ContractResult::Err will abort processing of this packet
+// and not inform the calling chain).
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcReceiveResponse
where
@@ -218,3 +246,62 @@ where
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_json_wasm::to_string;
+
+ #[test]
+ // added this to check json format for go compat, as I was unsure how some messages are snake encoded
+ fn serialize_msg() {
+ let msg = IbcMsg::Transfer {
+ channel_id: "channel-123".to_string(),
+ to_address: "my-special-addr".into(),
+ amount: Coin::new(12345678, "uatom"),
+ timeout_height: None,
+ timeout_timestamp: Some(1234567890),
+ };
+ let encoded = to_string(&msg).unwrap();
+ let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout_height":null,"timeout_timestamp":1234567890}}"#;
+ assert_eq!(encoded.as_str(), expected);
+ }
+
+ #[test]
+ fn ibc_timeout_height_ord() {
+ let epoch1a = IbcTimeoutHeight {
+ revision_number: 1,
+ timeout_height: 1000,
+ };
+ let epoch1b = IbcTimeoutHeight {
+ revision_number: 1,
+ timeout_height: 3000,
+ };
+ let epoch2a = IbcTimeoutHeight {
+ revision_number: 2,
+ timeout_height: 500,
+ };
+ let epoch2b = IbcTimeoutHeight {
+ revision_number: 2,
+ timeout_height: 2500,
+ };
+
+ // basic checks
+ assert!(epoch1a == epoch1a);
+ assert!(epoch1a < epoch1b);
+ assert!(epoch1b > epoch1a);
+ assert!(epoch2a > epoch1a);
+ assert!(epoch2b > epoch1a);
+
+ // ensure epoch boundaries are correctly handled
+ assert!(epoch1b > epoch1a);
+ assert!(epoch2a > epoch1b);
+ assert!(epoch2b > epoch2a);
+ assert!(epoch2b > epoch1b);
+ // and check the inverse compare
+ assert!(epoch1a < epoch1b);
+ assert!(epoch1b < epoch2a);
+ assert!(epoch2a < epoch2b);
+ assert!(epoch1b < epoch2b);
+ }
+}
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index 6a4b627e45..c1231f9ca6 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -41,9 +41,9 @@ pub use crate::query::{
};
#[allow(deprecated)]
pub use crate::results::{
- attr, Attribute, BankMsg, Context, ContractResult, CosmosMsg, HandleResponse, HandleResult,
- InitResponse, InitResult, MigrateResponse, MigrateResult, QueryResponse, QueryResult,
- StakingMsg, SystemResult, WasmMsg,
+ attr, wasm_execute, wasm_instantiate, Attribute, BankMsg, Context, ContractResult, CosmosMsg,
+ HandleResponse, HandleResult, InitResponse, InitResult, MigrateResponse, MigrateResult,
+ QueryResponse, QueryResult, StakingMsg, SystemResult, WasmMsg,
};
pub use crate::serde::{from_binary, from_slice, to_binary, to_vec};
pub use crate::storage::MemoryStorage;
@@ -84,6 +84,8 @@ pub mod testing {
riffle_shuffle, BankQuerier, MockApi, MockQuerier, MockQuerierCustomHandlerResult,
MockStorage, StakingQuerier, MOCK_CONTRACT_ADDR,
};
+ #[cfg(feature = "stargate")]
+ pub use crate::mock::{mock_ibc_channel, mock_ibc_packet_ack, mock_ibc_packet_recv};
}
// Re-exports
diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs
index 0c62a84f5c..54844b692e 100644
--- a/packages/std/src/mock.rs
+++ b/packages/std/src/mock.rs
@@ -1,4 +1,6 @@
use serde::de::DeserializeOwned;
+#[cfg(feature = "stargate")]
+use serde::Serialize;
use std::collections::HashMap;
use crate::addresses::{CanonicalAddr, HumanAddr};
@@ -6,6 +8,8 @@ use crate::binary::Binary;
use crate::coins::Coin;
use crate::deps::OwnedDeps;
use crate::errors::{StdError, StdResult, SystemError};
+#[cfg(feature = "stargate")]
+use crate::ibc::{IbcChannel, IbcEndpoint, IbcOrder, IbcPacket, IbcTimeoutHeight};
use crate::query::{
AllBalanceResponse, AllDelegationsResponse, BalanceResponse, BankQuery, BondedDenomResponse,
CustomQuery, DelegationResponse, FullDelegation, QueryRequest, StakingQuery, Validator,
@@ -149,6 +153,73 @@ pub fn mock_info>(sender: U, sent: &[Coin]) -> MessageInfo {
}
}
+#[cfg(feature = "stargate")]
+/// Creates an IbcChannel for testing. You set a few key parameters for handshaking,
+/// If you want to set more, use this as a default and mutate other fields
+pub fn mock_ibc_channel(my_channel_id: &str, order: IbcOrder, version: &str) -> IbcChannel {
+ IbcChannel {
+ endpoint: IbcEndpoint {
+ port_id: "my_port".to_string(),
+ channel_id: my_channel_id.to_string(),
+ },
+ counterparty_endpoint: IbcEndpoint {
+ port_id: "their_port".to_string(),
+ channel_id: "channel-7".to_string(),
+ },
+ order,
+ version: version.to_string(),
+ counterparty_version: Some(version.to_string()),
+ connection_id: "connection-2".to_string(),
+ }
+}
+
+#[cfg(feature = "stargate")]
+/// Creates a IbcPacket for testing ibc_packet_receive. You set a few key parameters that are
+/// often parsed. If you want to set more, use this as a default and mutate other fields
+pub fn mock_ibc_packet_recv(my_channel_id: &str, data: &T) -> StdResult {
+ Ok(IbcPacket {
+ data: to_binary(data)?,
+ src: IbcEndpoint {
+ port_id: "their-port".to_string(),
+ channel_id: "channel-1234".to_string(),
+ },
+ dest: IbcEndpoint {
+ port_id: "our-port".to_string(),
+ channel_id: my_channel_id.into(),
+ },
+ sequence: 27,
+ timeout_height: Some(IbcTimeoutHeight {
+ revision_number: 1,
+ timeout_height: 12345678,
+ }),
+ timeout_timestamp: None,
+ })
+}
+
+#[cfg(feature = "stargate")]
+/// Creates a IbcPacket for testing ibc_packet_{ack,timeout}. You set a few key parameters that are
+/// often parsed. If you want to set more, use this as a default and mutate other fields.
+/// The difference between mock_ibc_packet_recv is if `my_channel_id` is src or dest.
+pub fn mock_ibc_packet_ack(my_channel_id: &str, data: &T) -> StdResult {
+ Ok(IbcPacket {
+ data: to_binary(data)?,
+ src: IbcEndpoint {
+ port_id: "their-port".to_string(),
+ channel_id: my_channel_id.into(),
+ },
+ dest: IbcEndpoint {
+ port_id: "our-port".to_string(),
+ channel_id: "channel-1234".to_string(),
+ },
+ sequence: 29,
+ timeout_height: Some(IbcTimeoutHeight {
+ revision_number: 1,
+ timeout_height: 432332552,
+ }),
+ timeout_timestamp: None,
+ })
+}
+
/// The same type as cosmwasm-std's QuerierResult, but easier to reuse in
/// cosmwasm-vm. It might diverge from QuerierResult at some point.
pub type MockQuerierCustomHandlerResult = SystemResult>;
diff --git a/packages/std/src/query.rs b/packages/std/src/query.rs
index 43a1f727cf..457660a9fe 100644
--- a/packages/std/src/query.rs
+++ b/packages/std/src/query.rs
@@ -132,6 +132,14 @@ pub struct AllBalanceResponse {
pub amount: Vec,
}
+#[cfg(feature = "stargate")]
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct StargateResponse {
+ /// This is the protobuf response, binary encoded.
+ /// The caller is responsible for knowing how to parse.
+ pub response: Binary,
+}
+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum StakingQuery {
diff --git a/packages/std/src/results/cosmos_msg.rs b/packages/std/src/results/cosmos_msg.rs
index d3958ef876..f8647d9294 100644
--- a/packages/std/src/results/cosmos_msg.rs
+++ b/packages/std/src/results/cosmos_msg.rs
@@ -5,8 +5,10 @@ use std::fmt;
use crate::addresses::HumanAddr;
use crate::binary::Binary;
use crate::coins::Coin;
+use crate::errors::StdResult;
#[cfg(feature = "stargate")]
use crate::ibc::IbcMsg;
+use crate::serde::to_binary;
use crate::types::Empty;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -26,7 +28,7 @@ where
#[cfg(feature = "stargate")]
Stargate {
type_url: String,
- data: Binary,
+ value: Binary,
},
#[cfg(feature = "stargate")]
Ibc(IbcMsg),
@@ -109,6 +111,39 @@ pub enum WasmMsg {
},
}
+/// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code
+pub fn wasm_instantiate(
+ code_id: u64,
+ msg: &T,
+ send: Vec,
+ label: Option,
+) -> StdResult
+where
+ T: Serialize,
+{
+ let payload = to_binary(msg)?;
+ Ok(WasmMsg::Instantiate {
+ code_id,
+ msg: payload,
+ send,
+ label,
+ })
+}
+
+/// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code
+pub fn wasm_execute(contract_addr: T, msg: &U, send: Vec) -> StdResult
+where
+ T: Into,
+ U: Serialize,
+{
+ let payload = to_binary(msg)?;
+ Ok(WasmMsg::Execute {
+ contract_addr: contract_addr.into(),
+ msg: payload,
+ send,
+ })
+}
+
impl From for CosmosMsg {
fn from(msg: BankMsg) -> Self {
CosmosMsg::Bank(msg)
diff --git a/packages/std/src/results/mod.rs b/packages/std/src/results/mod.rs
index 77f2d1c3a9..e1b1a725fa 100644
--- a/packages/std/src/results/mod.rs
+++ b/packages/std/src/results/mod.rs
@@ -13,7 +13,7 @@ mod system_result;
pub use attribute::{attr, Attribute};
pub use context::Context;
pub use contract_result::ContractResult;
-pub use cosmos_msg::{BankMsg, CosmosMsg, StakingMsg, WasmMsg};
+pub use cosmos_msg::{wasm_execute, wasm_instantiate, BankMsg, CosmosMsg, StakingMsg, WasmMsg};
#[allow(deprecated)]
pub use handle::{HandleResponse, HandleResult};
#[allow(deprecated)]
diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs
index b875cff861..68cdabf525 100644
--- a/packages/vm/src/environment.rs
+++ b/packages/vm/src/environment.rs
@@ -1,5 +1,6 @@
//! Internal details to be used by instance.rs only
use std::borrow::{Borrow, BorrowMut};
+use std::collections::HashSet;
use std::ptr::NonNull;
use std::sync::{Arc, RwLock};
@@ -249,6 +250,20 @@ impl Environment {
.expect("Wasmer instance is not set. This is a bug in the lifecycle.")
}
+ /// Returns the names of exported functions.
+ pub fn exported_functions(&self) -> HashSet {
+ self.with_wasmer_instance(|instance| {
+ let function_names: HashSet = instance
+ .exports
+ .iter()
+ .functions()
+ .map(|pair| pair.0.clone())
+ .collect();
+ Ok(function_names)
+ })
+ .expect("Wasmer instance is not set. This is a bug in the lifecycle.")
+ }
+
/// Moves owned instances of storage and querier into the env.
/// Should be followed by exactly one call to move_out when the instance is finished.
pub fn move_in(&self, storage: S, querier: Q) {
@@ -325,6 +340,7 @@ mod tests {
use cosmwasm_std::{
coins, from_binary, to_vec, AllBalanceResponse, BankQuery, Empty, HumanAddr, QueryRequest,
};
+ use std::iter::FromIterator;
use wasmer::{imports, Function, Instance as WasmerInstance};
static CONTRACT: &[u8] = include_bytes!("../testdata/hackatom.wasm");
@@ -764,4 +780,24 @@ mod tests {
})
.unwrap();
}
+
+ #[test]
+ fn exported_functions_works() {
+ let (env, _instance) = make_instance(TESTING_GAS_LIMIT);
+ leave_default_data(&env);
+
+ let functions = env.exported_functions();
+ assert_eq!(
+ functions,
+ HashSet::from_iter(vec![
+ "interface_version_5".to_string(),
+ "allocate".to_string(),
+ "deallocate".to_string(),
+ "init".to_string(),
+ "handle".to_string(),
+ "migrate".to_string(),
+ "query".to_string()
+ ])
+ );
+ }
}
diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs
index 46563566d2..add2aacf95 100644
--- a/packages/vm/src/instance.rs
+++ b/packages/vm/src/instance.rs
@@ -40,7 +40,9 @@ pub struct InstanceOptions {
pub struct Instance {
/// We put this instance in a box to maintain a constant memory address for the entire
/// lifetime of the instance in the cache. This is needed e.g. when linking the wasmer
- /// instance to a context. See also https://github.com/CosmWasm/cosmwasm/pull/245
+ /// instance to a context. See also https://github.com/CosmWasm/cosmwasm/pull/245.
+ ///
+ /// This instance should only be accessed via the Environment, which provides safe access.
_inner: Box,
env: Environment,
pub required_features: HashSet,
@@ -205,6 +207,20 @@ where
self.env.memory().size().0 as _
}
+ /// Returns true if and only if all IBC entry points
+ /// (`ibc_channel_open`, `ibc_channel_connect`, `ibc_channel_close`, `ibc_packet_receive`, `ibc_packet_receive`, `ibc_packet_ack` and `ibc_packet_timeout`)
+ /// exist as exported functions. This does not guarantee the entry points
+ /// are functional and for simplicity does not even check their signatures.
+ pub fn has_ibc_entry_points(&self) -> bool {
+ let entries = self.env.exported_functions();
+ entries.contains("ibc_channel_open")
+ && entries.contains("ibc_channel_connect")
+ && entries.contains("ibc_channel_close")
+ && entries.contains("ibc_packet_receive")
+ && entries.contains("ibc_packet_ack")
+ && entries.contains("ibc_packet_timeout")
+ }
+
/// Returns the currently remaining gas.
pub fn get_gas_left(&self) -> u64 {
self.env.get_gas_left()
@@ -538,6 +554,77 @@ mod tests {
assert_eq!(instance.memory_pages(), 19);
}
+ #[test]
+ fn has_ibc_entry_points_works() {
+ // Non-IBC contract
+ let wasm = wat::parse_str(
+ r#"(module
+ (memory 3)
+ (export "memory" (memory 0))
+
+ (type (func))
+ (func (type 0) nop)
+ (export "interface_version_5" (func 0))
+ (export "init" (func 0))
+ (export "handle" (func 0))
+ (export "allocate" (func 0))
+ (export "deallocate" (func 0))
+ )"#,
+ )
+ .unwrap();
+ let instance = mock_instance(&wasm, &[]);
+ assert_eq!(instance.has_ibc_entry_points(), false);
+
+ // IBC contract
+ let wasm = wat::parse_str(
+ r#"(module
+ (memory 3)
+ (export "memory" (memory 0))
+
+ (type (func))
+ (func (type 0) nop)
+ (export "interface_version_5" (func 0))
+ (export "init" (func 0))
+ (export "handle" (func 0))
+ (export "allocate" (func 0))
+ (export "deallocate" (func 0))
+ (export "ibc_channel_open" (func 0))
+ (export "ibc_channel_connect" (func 0))
+ (export "ibc_channel_close" (func 0))
+ (export "ibc_packet_receive" (func 0))
+ (export "ibc_packet_ack" (func 0))
+ (export "ibc_packet_timeout" (func 0))
+ )"#,
+ )
+ .unwrap();
+ let instance = mock_instance(&wasm, &[]);
+ assert_eq!(instance.has_ibc_entry_points(), true);
+
+ // Missing packet ack
+ let wasm = wat::parse_str(
+ r#"(module
+ (memory 3)
+ (export "memory" (memory 0))
+
+ (type (func))
+ (func (type 0) nop)
+ (export "interface_version_5" (func 0))
+ (export "init" (func 0))
+ (export "handle" (func 0))
+ (export "allocate" (func 0))
+ (export "deallocate" (func 0))
+ (export "ibc_channel_open" (func 0))
+ (export "ibc_channel_connect" (func 0))
+ (export "ibc_channel_close" (func 0))
+ (export "ibc_packet_receive" (func 0))
+ (export "ibc_packet_timeout" (func 0))
+ )"#,
+ )
+ .unwrap();
+ let instance = mock_instance(&wasm, &[]);
+ assert_eq!(instance.has_ibc_entry_points(), false);
+ }
+
#[test]
fn get_gas_left_works() {
let instance = mock_instance_with_gas_limit(&CONTRACT, 123321);