From 0f00d6d6400860f6d7db35d9d5147498885ba511 Mon Sep 17 00:00:00 2001 From: shiki Date: Fri, 6 Nov 2020 14:12:57 +0900 Subject: [PATCH 01/12] feat: enable circleci for extension package (#6) * feat: enable circleci * fix: apply review --- .circleci/config.yml | 116 ++ .github/PULL_REQUEST_TEMPLATE.md | 21 + Cargo.lock | 10 + contracts/token-tester/.cargo/config | 6 + contracts/token-tester/Cargo.lock | 1087 +++++++++++++++++ contracts/token-tester/Cargo.toml | 28 + contracts/token-tester/examples/schema.rs | 17 + contracts/token-tester/schema/handle_msg.json | 65 + contracts/token-tester/schema/init_msg.json | 5 + contracts/token-tester/schema/query_msg.json | 25 + contracts/token-tester/src/contract.rs | 132 ++ contracts/token-tester/src/lib.rs | 40 + contracts/token-tester/src/msg.rs | 29 + contracts/token-tester/src/state.rs | 20 + packages/ext/.cargo/config | 5 + packages/ext/Cargo.lock | 188 +++ packages/ext/Cargo.toml | 17 + packages/ext/README.md | 1 + packages/ext/examples/schema.rs | 18 + ..._collection_route_and__collection_msg.json | 101 ++ ...apper_for__token_route_and__token_msg.json | 112 ++ packages/ext/src/lib.rs | 12 + packages/ext/src/msg.rs | 56 + packages/ext/src/msg_collection.rs | 39 + packages/ext/src/msg_token.rs | 79 ++ 25 files changed, 2229 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 contracts/token-tester/.cargo/config create mode 100644 contracts/token-tester/Cargo.lock create mode 100644 contracts/token-tester/Cargo.toml create mode 100644 contracts/token-tester/examples/schema.rs create mode 100644 contracts/token-tester/schema/handle_msg.json create mode 100644 contracts/token-tester/schema/init_msg.json create mode 100644 contracts/token-tester/schema/query_msg.json create mode 100644 contracts/token-tester/src/contract.rs create mode 100644 contracts/token-tester/src/lib.rs create mode 100644 contracts/token-tester/src/msg.rs create mode 100644 contracts/token-tester/src/state.rs create mode 100644 packages/ext/.cargo/config create mode 100644 packages/ext/Cargo.lock create mode 100644 packages/ext/Cargo.toml create mode 100644 packages/ext/README.md create mode 100644 packages/ext/examples/schema.rs create mode 100644 packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json create mode 100644 packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json create mode 100644 packages/ext/src/lib.rs create mode 100644 packages/ext/src/msg.rs create mode 100644 packages/ext/src/msg_collection.rs create mode 100644 packages/ext/src/msg_token.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index bfd1adc1d..47b419ef9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,7 @@ workflows: - package_schema - package_std - package_storage + - package_ext - package_vm_cranelift - package_vm_singlepass - contract_burner @@ -144,6 +145,65 @@ jobs: - target/debug/deps key: cargocache-v2-package_storage-rust:1.44.1-{{ checksum "Cargo.lock" }} + package_ext: + docker: + - image: rust:1.44.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version; rustup target list --installed + - restore_cache: + keys: + - cargocache-v2-package_ext-rust:1.44.1-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown && rustup target list --installed + - run: + name: Build library for native target (no features) + working_directory: ~/project/packages/ext + command: cargo build --locked --no-default-features + - run: + name: Build library for wasm target (no features) + working_directory: ~/project/packages/ext + command: cargo wasm --locked --no-default-features + - run: + name: Run unit tests (no features) + working_directory: ~/project/packages/ext + command: cargo test --locked --no-default-features + - run: + name: Build library for native target (all features) + working_directory: ~/project/packages/ext + command: cargo build --locked + - run: + name: Build library for wasm target (all features) + working_directory: ~/project/packages/ext + command: cargo wasm --locked + - run: + name: Run unit tests (all features) + working_directory: ~/project/packages/ext + command: cargo test --locked + - run: + name: Build and run schema generator + working_directory: ~/project/packages/ext + 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 + key: cargocache-v2-package_ext-rust:1.44.1-{{ checksum "Cargo.lock" }} + package_vm_cranelift: docker: - image: rust:1.44.1 @@ -491,6 +551,54 @@ jobs: - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps key: cargocache-v2-contract_staking-rust:1.44.1-{{ checksum "Cargo.lock" }} + contract_token_tester: + docker: + - image: rust:1.44.1 + working_directory: ~/cosmwasm/contracts/token-tester + steps: + - checkout: + path: ~/cosmwasm + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-v2-contract_token_tester-rust:1.44.1-{{ 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 + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - 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_token_tester-rust:1.44.1-{{ checksum "Cargo.lock" }} fmt: docker: @@ -529,6 +637,10 @@ jobs: name: Check formatting of contract staking working_directory: ~/project/contracts/staking command: cargo fmt -- --check + - run: + name: Check formatting of contract token-tester + working_directory: ~/project/contracts/token-tester + command: cargo fmt -- --check - save_cache: paths: - /usr/local/cargo/registry @@ -625,6 +737,10 @@ jobs: name: Clippy linting on staking working_directory: ~/project/contracts/staking command: cargo clippy -- -D warnings + - run: + name: Clippy linting on token-tester + working_directory: ~/project/contracts/token-tester + command: cargo clippy -- -D warnings - save_cache: paths: - /usr/local/cargo/registry diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..3f422687d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ + +Closes: #XXX + +## Description + + +## Motivation and context + + + +## How has this been tested? + + + + +## Checklist: + + +- [ ] I followed the [contributing guidelines](https://github.com/line/link/blob/master/CONTRIBUTING.md). +- [ ] I have updated the documentation accordingly. +- [ ] I have added tests to cover my changes. diff --git a/Cargo.lock b/Cargo.lock index 6eeee4d23..4fb8e2d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "cosmwasm-ext" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cosmwasm-schema" version = "0.10.0" diff --git a/contracts/token-tester/.cargo/config b/contracts/token-tester/.cargo/config new file mode 100644 index 000000000..7c115322a --- /dev/null +++ b/contracts/token-tester/.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 --features backtraces" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/contracts/token-tester/Cargo.lock b/contracts/token-tester/Cargo.lock new file mode 100644 index 000000000..926b149db --- /dev/null +++ b/contracts/token-tester/Cargo.lock @@ -0,0 +1,1087 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +dependencies = [ + "gimli 0.22.0", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[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 = "blake3" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 0.1.10", + "constant_time_eq", + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[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.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" + +[[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 = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "const_fn" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cosmwasm-ext" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cosmwasm-schema" +version = "0.10.0" +dependencies = [ + "schemars", + "serde_json", +] + +[[package]] +name = "cosmwasm-std" +version = "0.10.0" +dependencies = [ + "base64", + "schemars", + "serde", + "serde-json-wasm", + "snafu", +] + +[[package]] +name = "cosmwasm-storage" +version = "0.10.0" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cosmwasm-vm" +version = "0.10.0" +dependencies = [ + "cosmwasm-std", + "hex", + "memmap", + "parity-wasm", + "schemars", + "serde", + "serde_json", + "sha2", + "snafu", + "wasmer-clif-backend", + "wasmer-middleware-common", + "wasmer-runtime-core", + "wasmer-singlepass-backend", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a9c21f8042b9857bda93f6c1910b9f9f24100187a3d3d52f214a34e3dc5818" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7853f77a6e4a33c67a69c40f5e1bb982bd2dc5c4a22e17e67b65bbccf9b33b2e" +dependencies = [ + "byteorder", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "gimli 0.20.0", + "log", + "smallvec", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084cd6d5fb0d1da28acd72c199471bfb09acc703ec8f3bf07b1699584272a3b9" +dependencies = [ + "cranelift-codegen-shared", + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701b599783305a58c25027a4d73f2d6b599b2d8ef3f26677275f480b4d51e05d" + +[[package]] +name = "cranelift-entity" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88e792b28e1ebbc0187b72ba5ba880dad083abe9231a99d19604d10c9e73f38" + +[[package]] +name = "cranelift-native" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32daf082da21c0c05d93394ff4842c2ab7c4991b1f3186a1d952f8ac660edd0b" +dependencies = [ + "cranelift-codegen", + "raw-cpuid", + "target-lexicon", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" +dependencies = [ + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "const_fn", + "lazy_static", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dynasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a814e1edeb85dd2a3c6fc0d6bf76d02ca5695d438c70ecee3d90774f3259c5" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "owning_ref", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dynasmrt" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a393aaeb4441a48bcf47b5b6155971f82cc1eb77e22855403ccc0415ac8328d" +dependencies = [ + "byteorder", + "memmap", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "errno" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[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 = "gimli" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" +dependencies = [ + "byteorder", + "indexmap", +] + +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" + +[[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 = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[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 = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +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 = "nix" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + +[[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.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parity-wasm" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-cpuid" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf" +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 = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-bench" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d733da87e79faaac25616e33d26299a41143fd4cd42746cbb0e91d8feea243fd" +dependencies = [ + "byteorder", + "serde", +] + +[[package]] +name = "serde-json-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7294d94d390f1d2334697c065ea591d7074c676e2d20aa6f1df752fced29823f" +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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +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.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +dependencies = [ + "block-buffer", + "cfg-if 0.1.10", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "smallvec" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" + +[[package]] +name = "snafu" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7073448732a89f2f3e6581989106067f403d378faeafb4a50812eb814170d3e5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" + +[[package]] +name = "syn" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "target-lexicon" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" + +[[package]] +name = "thiserror" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "token-tester" +version = "0.1.0" +dependencies = [ + "cosmwasm-ext", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cosmwasm-vm", + "schemars", + "serde", + "snafu", +] + +[[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 = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wasmer-clif-backend" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691ea323652d540a10722066dbf049936f4367bb22a96f8992a262a942a8b11b" +dependencies = [ + "byteorder", + "cranelift-codegen", + "cranelift-entity", + "cranelift-native", + "libc", + "nix", + "rayon", + "serde", + "serde-bench", + "serde_bytes", + "serde_derive", + "target-lexicon", + "wasmer-clif-fork-frontend", + "wasmer-clif-fork-wasm", + "wasmer-runtime-core", + "wasmer-win-exception-handler", + "wasmparser", + "winapi", +] + +[[package]] +name = "wasmer-clif-fork-frontend" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c23f2824f354a00a77e4b040eef6e1d4c595a8a3e9013bad65199cc8dade9a5a" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "wasmer-clif-fork-wasm" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35e21d3aebc51cc6ebc0e830cf8458a9891c3482fb3c65ad18d408102929ae5" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "log", + "thiserror", + "wasmer-clif-fork-frontend", + "wasmparser", +] + +[[package]] +name = "wasmer-middleware-common" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd94068186b25fbe5213442648ffe0fa65ee77389bed020404486fd22056cc87" +dependencies = [ + "wasmer-runtime-core", +] + +[[package]] +name = "wasmer-runtime-core" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d4253f097502423d8b19d54cb18745f61b984b9dbce32424cba7945cfef367" +dependencies = [ + "bincode", + "blake3", + "cc", + "digest 0.8.1", + "errno", + "hex", + "indexmap", + "lazy_static", + "libc", + "nix", + "page_size", + "parking_lot", + "rustc_version", + "serde", + "serde-bench", + "serde_bytes", + "serde_derive", + "smallvec", + "target-lexicon", + "wasmparser", + "winapi", +] + +[[package]] +name = "wasmer-singlepass-backend" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cf84179dd5e92b784f7bc190b237f1184916a6d6d3f87d4dd94ca371a2cc25" +dependencies = [ + "bincode", + "byteorder", + "dynasm", + "dynasmrt", + "lazy_static", + "libc", + "nix", + "serde", + "serde_derive", + "smallvec", + "wasmer-runtime-core", +] + +[[package]] +name = "wasmer-win-exception-handler" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf22ce6dc66d893099aac853d451bf9443fa8f5443f5bf4fc63f3aebd7b592b1" +dependencies = [ + "cc", + "libc", + "wasmer-runtime-core", + "winapi", +] + +[[package]] +name = "wasmparser" +version = "0.51.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" + +[[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/token-tester/Cargo.toml b/contracts/token-tester/Cargo.toml new file mode 100644 index 000000000..955f454af --- /dev/null +++ b/contracts/token-tester/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "token-tester" +version = "0.1.0" +authors = ["shiki.tak"] +edition = "2018" +description = "simple tester for cosmwasm/ext" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["singlepass"] +# For quicker tests, cargo test --lib. for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] +cranelift = ["cosmwasm-vm/default-cranelift"] +singlepass = ["cosmwasm-vm/default-singlepass"] + +[dependencies] +cosmwasm-ext = { path = "../../packages/ext" } +cosmwasm-std = { path = "../../packages/std", features = ["iterator"] } +cosmwasm-storage = { path = "../../packages/storage", features = ["iterator"] } +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +snafu = { version = "0.6.3" } + +[dev-dependencies] +cosmwasm-schema = { path = "../../packages/schema" } +cosmwasm-vm = { path = "../../packages/vm", default-features = false, features = ["iterator"] } diff --git a/contracts/token-tester/examples/schema.rs b/contracts/token-tester/examples/schema.rs new file mode 100644 index 000000000..0b9cda4a7 --- /dev/null +++ b/contracts/token-tester/examples/schema.rs @@ -0,0 +1,17 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use token_tester::msg::{HandleMsg, InitMsg, QueryMsg}; + +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!(InitMsg), &out_dir); + export_schema(&schema_for!(HandleMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); +} diff --git a/contracts/token-tester/schema/handle_msg.json b/contracts/token-tester/schema/handle_msg.json new file mode 100644 index 000000000..220f30969 --- /dev/null +++ b/contracts/token-tester/schema/handle_msg.json @@ -0,0 +1,65 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HandleMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "issue" + ], + "properties": { + "issue": { + "type": "object", + "required": [ + "amount", + "decimals", + "img_uri", + "meta", + "mintable", + "name", + "owner", + "symbol", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "decimals": { + "$ref": "#/definitions/Uint128" + }, + "img_uri": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "mintable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "symbol": { + "type": "string" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + } + ], + "definitions": { + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/token-tester/schema/init_msg.json b/contracts/token-tester/schema/init_msg.json new file mode 100644 index 000000000..2b274b4e6 --- /dev/null +++ b/contracts/token-tester/schema/init_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InitMsg", + "type": "object" +} diff --git a/contracts/token-tester/schema/query_msg.json b/contracts/token-tester/schema/query_msg.json new file mode 100644 index 000000000..24261cd9c --- /dev/null +++ b/contracts/token-tester/schema/query_msg.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "get_token" + ], + "properties": { + "get_token": { + "type": "object", + "required": [ + "contract_id" + ], + "properties": { + "contract_id": { + "type": "string" + } + } + } + } + } + ] +} diff --git a/contracts/token-tester/src/contract.rs b/contracts/token-tester/src/contract.rs new file mode 100644 index 000000000..c08947ff6 --- /dev/null +++ b/contracts/token-tester/src/contract.rs @@ -0,0 +1,132 @@ +use cosmwasm_std::{ + log, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr, + InitResponse, Querier, StdResult, Storage, Uint128, +}; + +use cosmwasm_ext::{LinkMsgWrapper, Module, MsgData, TokenMsg, TokenRoute}; + +use crate::msg::{HandleMsg, InitMsg, QueryMsg}; +use crate::state::{config, config_read, State}; + +pub fn init( + deps: &mut Extern, + env: Env, + _msg: InitMsg, +) -> StdResult { + let state = State { + owner: deps.api.canonical_address(&env.message.sender)?, + }; + + config(&mut deps.storage).save(&state)?; + + Ok(InitResponse::default()) +} + +pub fn handle( + deps: &mut Extern, + env: Env, + msg: HandleMsg, +) -> HandleResult> { + match msg { + HandleMsg::Issue { + owner, + to, + name, + symbol, + img_uri, + meta, + amount, + mintable, + decimals, + } => try_issue( + deps, env, owner, to, name, symbol, img_uri, meta, amount, mintable, decimals, + ), + } +} + +pub fn query( + deps: &Extern, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::GetToken { contract_id } => query_token(deps, contract_id), + } +} + +#[allow(clippy::too_many_arguments)] +pub fn try_issue( + _deps: &mut Extern, + _env: Env, + owner: HumanAddr, + to: HumanAddr, + name: String, + symbol: String, + img_uri: String, + meta: String, + amount: Uint128, + mintable: bool, + decimals: Uint128, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::Issue, + data: TokenMsg::Issue { + owner, + to, + name, + symbol, + img_uri, + meta, + amount, + mintable, + decimals, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "issue")], + data: None, + }; + Ok(res) +} + +fn query_token( + _deps: &Extern, + _contract_id: String, +) -> StdResult { + unimplemented!() +} + +fn _query_owner(deps: &Extern) -> StdResult { + let state = config_read(&deps.storage).load()?; + Ok(deps.api.human_address(&state.owner)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; + use cosmwasm_std::{coins, Env}; + + fn create_contract(owner: String) -> (Extern, Env) { + let mut deps = mock_dependencies(20, &coins(1000, "cony")); + let env = mock_env(owner, &coins(1000, "cony")); + let res = init(&mut deps, env.clone(), InitMsg {}).unwrap(); + assert_eq!(0, res.messages.len()); + (deps, env) + } + + #[test] + fn init_contract() { + let addr = "creator"; + + let (deps, _) = create_contract(addr.to_string()); + let value = _query_owner(&deps).unwrap(); + assert_eq!("creator", value.as_str()); + } +} diff --git a/contracts/token-tester/src/lib.rs b/contracts/token-tester/src/lib.rs new file mode 100644 index 000000000..5d3bf59e1 --- /dev/null +++ b/contracts/token-tester/src/lib.rs @@ -0,0 +1,40 @@ +pub mod contract; +pub mod msg; +pub mod state; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use cosmwasm_std::{ + do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/contracts/token-tester/src/msg.rs b/contracts/token-tester/src/msg.rs new file mode 100644 index 000000000..b8dce5194 --- /dev/null +++ b/contracts/token-tester/src/msg.rs @@ -0,0 +1,29 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{HumanAddr, Uint128}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InitMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HandleMsg { + Issue { + owner: HumanAddr, + to: HumanAddr, + name: String, + symbol: String, + img_uri: String, + meta: String, + amount: Uint128, + mintable: bool, + decimals: Uint128, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + GetToken { contract_id: String }, +} diff --git a/contracts/token-tester/src/state.rs b/contracts/token-tester/src/state.rs new file mode 100644 index 000000000..471b41e25 --- /dev/null +++ b/contracts/token-tester/src/state.rs @@ -0,0 +1,20 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{CanonicalAddr, Storage}; +use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; + +pub static CONFIG_KEY: &[u8] = b"config"; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct State { + pub owner: CanonicalAddr, +} + +pub fn config(storage: &mut S) -> Singleton { + singleton(storage, CONFIG_KEY) +} + +pub fn config_read(storage: &S) -> ReadonlySingleton { + singleton_read(storage, CONFIG_KEY) +} diff --git a/packages/ext/.cargo/config b/packages/ext/.cargo/config new file mode 100644 index 000000000..8c7cbe465 --- /dev/null +++ b/packages/ext/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib --features backtraces" +schema = "run --example schema" diff --git a/packages/ext/Cargo.lock b/packages/ext/Cargo.lock new file mode 100644 index 000000000..4cbfb4f25 --- /dev/null +++ b/packages/ext/Cargo.lock @@ -0,0 +1,188 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "cosmwasm-schema" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1592dd62fbf542dca476606d4768ae54c5febd586c5e246d147c3c90b2f4289c" +dependencies = [ + "schemars", + "serde_json", +] + +[[package]] +name = "cosmwasm-std" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7934161da3638926acf9ac1ee92d0080993052df6dcd09fc5deb1d1ba6fbcb02" +dependencies = [ + "base64", + "schemars", + "serde", + "serde-json-wasm", + "snafu", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[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 = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7294d94d390f1d2334697c065ea591d7074c676e2d20aa6f1df752fced29823f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +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.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "snafu" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5aed652511f5c9123cf2afbe9c244c29db6effa2abb05c866e965c82405ce" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf8f7d5720104a9df0f7076a8682024e958bba0fe9848767bb44f251f3648e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "ext" +version = "1.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/packages/ext/Cargo.toml b/packages/ext/Cargo.toml new file mode 100644 index 000000000..080179337 --- /dev/null +++ b/packages/ext/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cosmwasm-ext" +version = "0.1.0" +authors = ["shiki.tak"] +edition = "2018" +description = "Bindings for CosmWasm contracts to call into custom modules of Link Core" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-std = { path = "../std", version = "0.10.0" } +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } + +[dev-dependencies] +cosmwasm-schema = { path = "../schema" } diff --git a/packages/ext/README.md b/packages/ext/README.md new file mode 100644 index 000000000..d16900c42 --- /dev/null +++ b/packages/ext/README.md @@ -0,0 +1 @@ +# link-extension diff --git a/packages/ext/examples/schema.rs b/packages/ext/examples/schema.rs new file mode 100644 index 000000000..eb2904a21 --- /dev/null +++ b/packages/ext/examples/schema.rs @@ -0,0 +1,18 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_ext::{CollectionMsg, CollectionRoute, LinkMsgWrapper, TokenMsg, TokenRoute}; +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +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!(LinkMsgWrapper), &out_dir); + export_schema( + &schema_for!(LinkMsgWrapper), + &out_dir, + ); +} diff --git a/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json b/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json new file mode 100644 index 000000000..1305f0508 --- /dev/null +++ b/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json @@ -0,0 +1,101 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LinkMsgWrapper_for_CollectionRoute_and_CollectionMsg", + "type": "object", + "required": [ + "module", + "msg_data" + ], + "properties": { + "module": { + "$ref": "#/definitions/Module" + }, + "msg_data": { + "$ref": "#/definitions/MsgData_for_CollectionRoute_and_CollectionMsg" + } + }, + "definitions": { + "CollectionMsg": { + "anyOf": [ + { + "type": "object", + "required": [ + "create" + ], + "properties": { + "create": { + "type": "object", + "required": [ + "base_img_uri", + "meta", + "name", + "owner" + ], + "properties": { + "base_img_uri": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + } + ] + }, + "CollectionRoute": { + "enum": [ + "create", + "issue_nft", + "issue_ft", + "mint_nft", + "mint_ft", + "burn_nft", + "burn_nft_from", + "burn_ft", + "burn_ft_from", + "transfer_nft", + "transfer_nft_from", + "transfer_ft", + "transfer_ft_from", + "approve", + "disapprove", + "attach", + "detach", + "attach_from", + "detach_from" + ] + }, + "HumanAddr": { + "type": "string" + }, + "Module": { + "enum": [ + "tokenencode", + "collectionencode" + ] + }, + "MsgData_for_CollectionRoute_and_CollectionMsg": { + "type": "object", + "required": [ + "data", + "route" + ], + "properties": { + "data": { + "$ref": "#/definitions/CollectionMsg" + }, + "route": { + "$ref": "#/definitions/CollectionRoute" + } + } + } + } +} diff --git a/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json b/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json new file mode 100644 index 000000000..7bc656bdc --- /dev/null +++ b/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json @@ -0,0 +1,112 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LinkMsgWrapper_for_TokenRoute_and_TokenMsg", + "type": "object", + "required": [ + "module", + "msg_data" + ], + "properties": { + "module": { + "$ref": "#/definitions/Module" + }, + "msg_data": { + "$ref": "#/definitions/MsgData_for_TokenRoute_and_TokenMsg" + } + }, + "definitions": { + "HumanAddr": { + "type": "string" + }, + "Module": { + "enum": [ + "tokenencode", + "collectionencode" + ] + }, + "MsgData_for_TokenRoute_and_TokenMsg": { + "type": "object", + "required": [ + "data", + "route" + ], + "properties": { + "data": { + "$ref": "#/definitions/TokenMsg" + }, + "route": { + "$ref": "#/definitions/TokenRoute" + } + } + }, + "TokenMsg": { + "anyOf": [ + { + "type": "object", + "required": [ + "issue" + ], + "properties": { + "issue": { + "type": "object", + "required": [ + "amount", + "decimals", + "img_uri", + "meta", + "mintable", + "name", + "owner", + "symbol", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "decimals": { + "$ref": "#/definitions/Uint128" + }, + "img_uri": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "mintable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "symbol": { + "type": "string" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + } + ] + }, + "TokenRoute": { + "enum": [ + "issue", + "transfer", + "mint", + "burn", + "grant_perm", + "revoke_perm", + "modify" + ] + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/ext/src/lib.rs b/packages/ext/src/lib.rs new file mode 100644 index 000000000..86571a2a9 --- /dev/null +++ b/packages/ext/src/lib.rs @@ -0,0 +1,12 @@ +mod msg; +mod msg_collection; +mod msg_token; + +pub use msg::{Change, LinkMsgWrapper, Module, MsgData}; +pub use msg_collection::{CollectionMsg, CollectionRoute}; +pub use msg_token::{TokenMsg, TokenRoute}; + +// This export is added to all contracts that import this package, signifying that they require +// "link" support on the chain they run on. +#[no_mangle] +extern "C" fn requires_link() {} diff --git a/packages/ext/src/msg.rs b/packages/ext/src/msg.rs new file mode 100644 index 000000000..ea844d1ab --- /dev/null +++ b/packages/ext/src/msg.rs @@ -0,0 +1,56 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::msg_collection::{CollectionMsg, CollectionRoute}; +use crate::msg_token::{TokenMsg, TokenRoute}; +use cosmwasm_std::CosmosMsg; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Module { + Tokenencode, + Collectionencode, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct Change { + pub field: String, + pub value: String, +} + +impl Change { + pub fn new(field: String, value: String) -> Self { + Change { field, value } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct LinkMsgWrapper { + pub module: Module, + pub msg_data: MsgData, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct MsgData { + pub route: R, + pub data: D, +} + +impl Into>> + for LinkMsgWrapper +{ + fn into(self) -> CosmosMsg> { + CosmosMsg::Custom(self) + } +} + +impl Into>> + for LinkMsgWrapper +{ + fn into(self) -> CosmosMsg> { + CosmosMsg::Custom(self) + } +} diff --git a/packages/ext/src/msg_collection.rs b/packages/ext/src/msg_collection.rs new file mode 100644 index 000000000..90dfea837 --- /dev/null +++ b/packages/ext/src/msg_collection.rs @@ -0,0 +1,39 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::HumanAddr; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CollectionRoute { + Create, + IssueNft, + IssueFt, + MintNft, + MintFt, + BurnNft, + BurnNftFrom, + BurnFt, + BurnFtFrom, + TransferNft, + TransferNftFrom, + TransferFt, + TransferFtFrom, + Approve, + Disapprove, + Attach, + Detach, + AttachFrom, + DetachFrom, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CollectionMsg { + Create { + owner: HumanAddr, + name: String, + meta: String, + base_img_uri: String, + }, +} diff --git a/packages/ext/src/msg_token.rs b/packages/ext/src/msg_token.rs new file mode 100644 index 000000000..2f51d241a --- /dev/null +++ b/packages/ext/src/msg_token.rs @@ -0,0 +1,79 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{HumanAddr, Uint128}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TokenRoute { + Issue, + Transfer, + Mint, + Burn, + GrantPerm, + RevokePerm, + Modify, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TokenMsg { + Issue { + owner: HumanAddr, + to: HumanAddr, + name: String, + symbol: String, + img_uri: String, + meta: String, + amount: Uint128, + mintable: bool, + decimals: Uint128, + }, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[allow(irrefutable_let_patterns)] + fn new_msg_issue() { + let addr1 = HumanAddr::from("addr1"); + let addr2 = HumanAddr::from("addr2"); + + let msg_issue = TokenMsg::Issue { + owner: addr1.clone(), + to: addr2.clone(), + name: "test_token".to_string(), + symbol: "TT1".to_string(), + img_uri: "".to_string(), + meta: "".to_string(), + amount: Uint128(100), + mintable: true, + decimals: Uint128(18), + }; + + if let TokenMsg::Issue { + owner, + to, + name, + symbol, + img_uri, + meta, + amount, + mintable, + decimals, + } = msg_issue + { + assert_eq!(owner, addr1); + assert_eq!(to, addr2); + assert_eq!(name, "test_token".to_string()); + assert_eq!(symbol, "TT1".to_string()); + assert_eq!(img_uri, "".to_string()); + assert_eq!(meta, "".to_string()); + assert_eq!(amount, Uint128(100)); + assert_eq!(mintable, true); + assert_eq!(decimals, Uint128(18)); + } + } +} From 26ee37ee60b5d5982eeeddc73d029a473a267c97 Mon Sep 17 00:00:00 2001 From: shiki Date: Mon, 9 Nov 2020 13:16:39 +0900 Subject: [PATCH 02/12] feat: Implement token wrapper library and complete token tester (#7) * feat: Implement token wrapper library and complete token tester * fix: used enum for target param --- contracts/README.md | 5 + contracts/token-tester/examples/schema.rs | 2 + contracts/token-tester/schema/handle_msg.json | 170 +++++++++++ contracts/token-tester/schema/query_msg.json | 76 ++++- contracts/token-tester/schema/state.json | 22 ++ contracts/token-tester/src/contract.rs | 279 +++++++++++++++++- contracts/token-tester/src/msg.rs | 48 ++- ...apper_for__token_route_and__token_msg.json | 227 +++++++++++--- packages/ext/src/lib.rs | 6 + packages/ext/src/msg_token.rs | 37 +++ packages/ext/src/querier_token.rs | 133 +++++++++ packages/ext/src/query.rs | 42 +++ packages/ext/src/token.rs | 37 +++ 13 files changed, 1034 insertions(+), 50 deletions(-) create mode 100644 contracts/token-tester/schema/state.json create mode 100644 packages/ext/src/querier_token.rs create mode 100644 packages/ext/src/query.rs create mode 100644 packages/ext/src/token.rs diff --git a/contracts/README.md b/contracts/README.md index 9d0ed200a..0b7e5e62a 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -38,4 +38,9 @@ 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.9.0 ./contracts/staking + +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="devcontract_token_tester",target=/code/contracts/staking/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.9.0 ./contracts/token-tester ``` diff --git a/contracts/token-tester/examples/schema.rs b/contracts/token-tester/examples/schema.rs index 0b9cda4a7..6e83e2fd1 100644 --- a/contracts/token-tester/examples/schema.rs +++ b/contracts/token-tester/examples/schema.rs @@ -4,6 +4,7 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; use token_tester::msg::{HandleMsg, InitMsg, QueryMsg}; +use token_tester::state::State; fn main() { let mut out_dir = current_dir().unwrap(); @@ -14,4 +15,5 @@ fn main() { export_schema(&schema_for!(InitMsg), &out_dir); export_schema(&schema_for!(HandleMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(State), &out_dir); } diff --git a/contracts/token-tester/schema/handle_msg.json b/contracts/token-tester/schema/handle_msg.json index 220f30969..3e10b894e 100644 --- a/contracts/token-tester/schema/handle_msg.json +++ b/contracts/token-tester/schema/handle_msg.json @@ -52,6 +52,176 @@ } } } + }, + { + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "grant_perm" + ], + "properties": { + "grant_perm": { + "type": "object", + "required": [ + "contract_id", + "from", + "permission", + "to" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "type": "string" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "revoke_perm" + ], + "properties": { + "revoke_perm": { + "type": "object", + "required": [ + "contract_id", + "from", + "permission" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "modify" + ], + "properties": { + "modify": { + "type": "object", + "required": [ + "contract_id", + "owner" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } } ], "definitions": { diff --git a/contracts/token-tester/schema/query_msg.json b/contracts/token-tester/schema/query_msg.json index 24261cd9c..618f08f4b 100644 --- a/contracts/token-tester/schema/query_msg.json +++ b/contracts/token-tester/schema/query_msg.json @@ -20,6 +20,80 @@ } } } + }, + { + "type": "object", + "required": [ + "get_balance" + ], + "properties": { + "get_balance": { + "type": "object", + "required": [ + "address", + "contract_id" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_total" + ], + "properties": { + "get_total": { + "type": "object", + "required": [ + "contract_id", + "target" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "target": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_perm" + ], + "properties": { + "get_perm": { + "type": "object", + "required": [ + "address", + "contract_id" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + } + } + } + } + } + ], + "definitions": { + "HumanAddr": { + "type": "string" } - ] + } } diff --git a/contracts/token-tester/schema/state.json b/contracts/token-tester/schema/state.json new file mode 100644 index 000000000..d4b4a3340 --- /dev/null +++ b/contracts/token-tester/schema/state.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "$ref": "#/definitions/CanonicalAddr" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "CanonicalAddr": { + "$ref": "#/definitions/Binary" + } + } +} diff --git a/contracts/token-tester/src/contract.rs b/contracts/token-tester/src/contract.rs index c08947ff6..bbe5bed34 100644 --- a/contracts/token-tester/src/contract.rs +++ b/contracts/token-tester/src/contract.rs @@ -1,9 +1,14 @@ +use std::str::FromStr; + use cosmwasm_std::{ - log, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr, + log, to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr, InitResponse, Querier, StdResult, Storage, Uint128, }; -use cosmwasm_ext::{LinkMsgWrapper, Module, MsgData, TokenMsg, TokenRoute}; +use cosmwasm_ext::{ + Change, LinkMsgWrapper, LinkTokenQuerier, Module, MsgData, Response, Token, TokenMsg, + TokenPerm, TokenRoute, TokenTarget, +}; use crate::msg::{HandleMsg, InitMsg, QueryMsg}; use crate::state::{config, config_read, State}; @@ -41,6 +46,35 @@ pub fn handle( } => try_issue( deps, env, owner, to, name, symbol, img_uri, meta, amount, mintable, decimals, ), + HandleMsg::Transfer { + from, + contract_id, + to, + amount, + } => try_transfer(deps, env, from, contract_id, to, amount), + HandleMsg::Mint { + from, + contract_id, + to, + amount, + } => try_mint(deps, env, from, contract_id, to, amount), + HandleMsg::Burn { + from, + contract_id, + amount, + } => try_burn(deps, env, from, contract_id, amount), + HandleMsg::GrantPerm { + from, + contract_id, + to, + permission, + } => try_grant_perm(deps, env, from, contract_id, to, permission), + HandleMsg::RevokePerm { + from, + contract_id, + permission, + } => try_revoke_perm(deps, env, from, contract_id, permission), + HandleMsg::Modify { owner, contract_id } => try_modify(deps, env, owner, contract_id), } } @@ -50,6 +84,18 @@ pub fn query( ) -> StdResult { match msg { QueryMsg::GetToken { contract_id } => query_token(deps, contract_id), + QueryMsg::GetBalance { + contract_id, + address, + } => query_balance(deps, contract_id, address), + QueryMsg::GetTotal { + contract_id, + target, + } => query_supply(deps, contract_id, target), + QueryMsg::GetPerm { + contract_id, + address, + } => query_perm(deps, contract_id, address), } } @@ -95,11 +141,232 @@ pub fn try_issue( Ok(res) } +pub fn try_transfer( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, +) -> HandleResult> { + // Some kind of logic. + + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::Transfer, + data: TokenMsg::Transfer { + from, + contract_id, + to, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "transfer")], + data: None, + }; + Ok(res) +} + +pub fn try_mint( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, +) -> HandleResult> { + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::Mint, + data: TokenMsg::Mint { + from, + contract_id, + to, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "mint")], + data: None, + }; + Ok(res) +} + +pub fn try_burn( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + amount: Uint128, +) -> HandleResult> { + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::Burn, + data: TokenMsg::Burn { + from, + contract_id, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "burn")], + data: None, + }; + Ok(res) +} + +pub fn try_grant_perm( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + perm_str: String, +) -> HandleResult> { + let permission = TokenPerm::from_str(&perm_str).unwrap(); + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::GrantPerm, + data: TokenMsg::GrantPerm { + from, + contract_id, + to, + permission, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "grant_perm")], + data: None, + }; + Ok(res) +} + +pub fn try_revoke_perm( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + perm_str: String, +) -> HandleResult> { + let permission = TokenPerm::from_str(&perm_str).unwrap(); + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::RevokePerm, + data: TokenMsg::RevokePerm { + from, + contract_id, + permission, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "revoke_perm")], + data: None, + }; + Ok(res) +} + +pub fn try_modify( + _deps: &mut Extern, + _env: Env, + owner: HumanAddr, + contract_id: String, +) -> HandleResult> { + let change = Change::new("meta".to_string(), "update_token_meta".to_string()); + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::Modify, + data: TokenMsg::Modify { + owner, + contract_id, + changes: vec![change], + }, + }, + } + .into(); + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "modify")], + data: None, + }; + Ok(res) +} + fn query_token( - _deps: &Extern, - _contract_id: String, + deps: &Extern, + contract_id: String, ) -> StdResult { - unimplemented!() + let res = match LinkTokenQuerier::new(&deps.querier).query_token(contract_id)? { + Some(token_response) => token_response, + None => return to_binary(&None::>>), + }; + + let out = to_binary(&res)?; + Ok(out) +} + +fn query_balance( + deps: &Extern, + contract_id: String, + address: HumanAddr, +) -> StdResult { + let res = LinkTokenQuerier::new(&deps.querier) + .query_balance(contract_id, address) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_supply( + deps: &Extern, + contract_id: String, + target_str: String, +) -> StdResult { + let target = TokenTarget::from_str(&target_str).unwrap(); + let res = LinkTokenQuerier::new(&deps.querier) + .query_supply(contract_id, target) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_perm( + deps: &Extern, + contract_id: String, + address: HumanAddr, +) -> StdResult { + let res = match LinkTokenQuerier::new(&deps.querier).query_perm(contract_id, address)? { + Some(permissions) => permissions, + None => return to_binary(&None::>>), + }; + let out = to_binary(&res)?; + Ok(out) } fn _query_owner(deps: &Extern) -> StdResult { @@ -116,7 +383,7 @@ mod tests { fn create_contract(owner: String) -> (Extern, Env) { let mut deps = mock_dependencies(20, &coins(1000, "cony")); let env = mock_env(owner, &coins(1000, "cony")); - let res = init(&mut deps, env.clone(), InitMsg {}).unwrap(); + let res = init(&mut deps, env, InitMsg {}).unwrap(); assert_eq!(0, res.messages.len()); (deps, env) } diff --git a/contracts/token-tester/src/msg.rs b/contracts/token-tester/src/msg.rs index b8dce5194..e8eb7092c 100644 --- a/contracts/token-tester/src/msg.rs +++ b/contracts/token-tester/src/msg.rs @@ -20,10 +20,56 @@ pub enum HandleMsg { mintable: bool, decimals: Uint128, }, + Transfer { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, + }, + Mint { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, + }, + Burn { + from: HumanAddr, + contract_id: String, + amount: Uint128, + }, + GrantPerm { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + permission: String, + }, + RevokePerm { + from: HumanAddr, + contract_id: String, + permission: String, + }, + Modify { + owner: HumanAddr, + contract_id: String, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - GetToken { contract_id: String }, + GetToken { + contract_id: String, + }, + GetBalance { + contract_id: String, + address: HumanAddr, + }, + GetTotal { + contract_id: String, + target: String, + }, + GetPerm { + contract_id: String, + address: HumanAddr, + }, } diff --git a/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json b/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json index 7bc656bdc..9de62427f 100644 --- a/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json +++ b/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json @@ -15,6 +15,21 @@ } }, "definitions": { + "Change": { + "type": "object", + "required": [ + "field", + "value" + ], + "properties": { + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "HumanAddr": { "type": "string" }, @@ -44,51 +59,172 @@ { "type": "object", "required": [ - "issue" + "amount", + "decimals", + "img_uri", + "meta", + "mintable", + "name", + "owner", + "symbol", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "decimals": { + "$ref": "#/definitions/Uint128" + }, + "img_uri": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "mintable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "symbol": { + "type": "string" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "permission", + "to" ], "properties": { - "issue": { - "type": "object", - "required": [ - "amount", - "decimals", - "img_uri", - "meta", - "mintable", - "name", - "owner", - "symbol", - "to" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "decimals": { - "$ref": "#/definitions/Uint128" - }, - "img_uri": { - "type": "string" - }, - "meta": { - "type": "string" - }, - "mintable": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "symbol": { - "type": "string" - }, - "to": { - "$ref": "#/definitions/HumanAddr" - } + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "$ref": "#/definitions/permission" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "permission" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "$ref": "#/definitions/permission" + } + } + }, + { + "type": "object", + "required": [ + "changes", + "contract_id", + "owner" + ], + "properties": { + "changes": { + "type": "array", + "items": { + "$ref": "#/definitions/Change" } + }, + "contract_id": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" } } } @@ -107,6 +243,13 @@ }, "Uint128": { "type": "string" + }, + "permission": { + "enum": [ + "mint", + "burn", + "modify" + ] } } } diff --git a/packages/ext/src/lib.rs b/packages/ext/src/lib.rs index 86571a2a9..918ac0ea8 100644 --- a/packages/ext/src/lib.rs +++ b/packages/ext/src/lib.rs @@ -1,10 +1,16 @@ mod msg; mod msg_collection; mod msg_token; +mod querier_token; +mod query; +mod token; pub use msg::{Change, LinkMsgWrapper, Module, MsgData}; pub use msg_collection::{CollectionMsg, CollectionRoute}; pub use msg_token::{TokenMsg, TokenRoute}; +pub use querier_token::{LinkTokenQuerier, TokenQuery, TokenQueryRoute, TokenTarget}; +pub use query::{LinkQueryWrapper, QueryData, Response}; +pub use token::{Token, TokenPerm}; // This export is added to all contracts that import this package, signifying that they require // "link" support on the chain they run on. diff --git a/packages/ext/src/msg_token.rs b/packages/ext/src/msg_token.rs index 2f51d241a..49c1a53b8 100644 --- a/packages/ext/src/msg_token.rs +++ b/packages/ext/src/msg_token.rs @@ -3,6 +3,9 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{HumanAddr, Uint128}; +use crate::msg::Change; +use crate::token::TokenPerm; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TokenRoute { @@ -17,6 +20,7 @@ pub enum TokenRoute { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] +#[serde(untagged)] pub enum TokenMsg { Issue { owner: HumanAddr, @@ -29,6 +33,39 @@ pub enum TokenMsg { mintable: bool, decimals: Uint128, }, + Transfer { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, + }, + Mint { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, + }, + Burn { + from: HumanAddr, + contract_id: String, + amount: Uint128, + }, + GrantPerm { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + permission: TokenPerm, + }, + RevokePerm { + from: HumanAddr, + contract_id: String, + permission: TokenPerm, + }, + Modify { + owner: HumanAddr, + contract_id: String, + changes: Vec, + }, } #[cfg(test)] diff --git a/packages/ext/src/querier_token.rs b/packages/ext/src/querier_token.rs new file mode 100644 index 000000000..fc78680ec --- /dev/null +++ b/packages/ext/src/querier_token.rs @@ -0,0 +1,133 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +use cosmwasm_std::{HumanAddr, Querier, StdResult, Uint128}; + +use crate::query::{LinkQueryWrapper, Module, QueryData, Response}; +use crate::token::{Token, TokenPerm}; + +pub struct LinkTokenQuerier<'a, Q: Querier> { + querier: &'a Q, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename = "target")] +#[serde(rename_all = "snake_case")] +pub enum TokenTarget { + Mint, + Burn, + Supply, +} + +impl FromStr for TokenTarget { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "mint" => Ok(TokenTarget::Mint), + "burn" => Ok(TokenTarget::Burn), + "supply" => Ok(TokenTarget::Supply), + _ => Err("Unknown target type"), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TokenQueryRoute { + Tokens, + Balance, + Supply, + Perms, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TokenQuery { + QueryTokenParam { + contract_id: String, + }, + QueryBalanceParam { + contract_id: String, + address: HumanAddr, + }, + QueryTotalParam { + contract_id: String, + target: TokenTarget, + }, + QueryPermParam { + contract_id: String, + address: HumanAddr, + }, +} + +impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { + pub fn new(querier: &'a Q) -> Self { + LinkTokenQuerier { querier } + } + + pub fn query_token(&self, contract_id: String) -> StdResult>> { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Tokens, + data: TokenQuery::QueryTokenParam { contract_id }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_balance(&self, contract_id: String, address: HumanAddr) -> StdResult { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Balance, + data: TokenQuery::QueryBalanceParam { + contract_id, + address, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_supply(&self, contract_id: String, target: TokenTarget) -> StdResult { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Supply, + data: TokenQuery::QueryTotalParam { + contract_id, + target, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_perm( + &self, + contract_id: String, + address: HumanAddr, + ) -> StdResult>> { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Perms, + data: TokenQuery::QueryPermParam { + contract_id, + address, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } +} diff --git a/packages/ext/src/query.rs b/packages/ext/src/query.rs new file mode 100644 index 000000000..244556bd2 --- /dev/null +++ b/packages/ext/src/query.rs @@ -0,0 +1,42 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::QueryRequest; + +use crate::querier_token::{TokenQuery, TokenQueryRoute}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Module { + Tokenencode, + Collectionencode, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct LinkQueryWrapper { + pub module: Module, + pub query_data: QueryData, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct QueryData { + pub route: R, + pub data: D, +} + +impl Into>> + for LinkQueryWrapper +{ + fn into(self) -> QueryRequest> { + QueryRequest::Custom(self) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Response { + #[serde(rename = "type")] + pub key: String, + pub value: T, +} diff --git a/packages/ext/src/token.rs b/packages/ext/src/token.rs new file mode 100644 index 000000000..c15a90e90 --- /dev/null +++ b/packages/ext/src/token.rs @@ -0,0 +1,37 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +use cosmwasm_std::Uint128; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Token { + pub contract_id: String, + pub name: String, + pub symbol: String, + pub meta: String, + pub img_uri: String, + pub decimals: Uint128, + pub mintable: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename = "permission")] +#[serde(rename_all = "snake_case")] +pub enum TokenPerm { + Mint, + Burn, + Modify, +} + +impl FromStr for TokenPerm { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "mint" => Ok(TokenPerm::Mint), + "burn" => Ok(TokenPerm::Burn), + "modify" => Ok(TokenPerm::Modify), + _ => Err("Unknown permission type"), + } + } +} From 2cd044d593b5bcccee13551efa34b039035e14a1 Mon Sep 17 00:00:00 2001 From: shiki Date: Fri, 13 Nov 2020 14:32:29 +0900 Subject: [PATCH 03/12] feat: Implement collection wrapper library and complete collection tester (#8) --- .circleci/config.yml | 56 + contracts/collection-tester/.cargo/config | 6 + contracts/collection-tester/Cargo.lock | 1087 ++++++++++++++++ contracts/collection-tester/Cargo.toml | 28 + .../collection-tester/examples/schema.rs | 17 + .../collection-tester/schema/handle_msg.json | 956 ++++++++++++++ .../collection-tester/schema/init_msg.json | 5 + .../collection-tester/schema/query_msg.json | 229 ++++ contracts/collection-tester/src/contract.rs | 1137 +++++++++++++++++ contracts/collection-tester/src/lib.rs | 40 + contracts/collection-tester/src/msg.rs | 199 +++ contracts/collection-tester/src/state.rs | 20 + contracts/token-tester/src/contract.rs | 6 +- ..._collection_route_and__collection_msg.json | 629 ++++++++- packages/ext/src/collection.rs | 91 ++ packages/ext/src/lib.rs | 10 +- packages/ext/src/msg_collection.rs | 137 +- packages/ext/src/querier_collection.rs | 364 ++++++ packages/ext/src/querier_token.rs | 28 +- packages/ext/src/query.rs | 30 + 20 files changed, 5019 insertions(+), 56 deletions(-) create mode 100644 contracts/collection-tester/.cargo/config create mode 100644 contracts/collection-tester/Cargo.lock create mode 100644 contracts/collection-tester/Cargo.toml create mode 100644 contracts/collection-tester/examples/schema.rs create mode 100644 contracts/collection-tester/schema/handle_msg.json create mode 100644 contracts/collection-tester/schema/init_msg.json create mode 100644 contracts/collection-tester/schema/query_msg.json create mode 100644 contracts/collection-tester/src/contract.rs create mode 100644 contracts/collection-tester/src/lib.rs create mode 100644 contracts/collection-tester/src/msg.rs create mode 100644 contracts/collection-tester/src/state.rs create mode 100644 packages/ext/src/collection.rs create mode 100644 packages/ext/src/querier_collection.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 47b419ef9..d52101371 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -600,6 +600,54 @@ jobs: - target/wasm32-unknown-unknown/release/deps key: cargocache-v2-contract_token_tester-rust:1.44.1-{{ checksum "Cargo.lock" }} + contract_collection_tester: + docker: + - image: rust:1.44.1 + working_directory: ~/cosmwasm/contracts/collection-tester + steps: + - checkout: + path: ~/cosmwasm + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-v2-contract_collection_tester-rust:1.44.1-{{ 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 + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - 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_collection_tester-rust:1.44.1-{{ checksum "Cargo.lock" }} fmt: docker: - image: rust:1.44.1 @@ -641,6 +689,10 @@ jobs: name: Check formatting of contract token-tester working_directory: ~/project/contracts/token-tester command: cargo fmt -- --check + - run: + name: Check formatting of contract collection-tester + working_directory: ~/project/contracts/collection-tester + command: cargo fmt -- --check - save_cache: paths: - /usr/local/cargo/registry @@ -741,6 +793,10 @@ jobs: name: Clippy linting on token-tester working_directory: ~/project/contracts/token-tester command: cargo clippy -- -D warnings + - run: + name: Clippy linting on collection-tester + working_directory: ~/project/contracts/collection-tester + command: cargo clippy -- -D warnings - save_cache: paths: - /usr/local/cargo/registry diff --git a/contracts/collection-tester/.cargo/config b/contracts/collection-tester/.cargo/config new file mode 100644 index 000000000..7c115322a --- /dev/null +++ b/contracts/collection-tester/.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 --features backtraces" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/contracts/collection-tester/Cargo.lock b/contracts/collection-tester/Cargo.lock new file mode 100644 index 000000000..541bb3efa --- /dev/null +++ b/contracts/collection-tester/Cargo.lock @@ -0,0 +1,1087 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" +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 = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2baad346b2d4e94a24347adeee9c7a93f412ee94b9cc26e5b59dea23848e9f28" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[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 = "blake3" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 0.1.10", + "constant_time_eq", + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[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.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" + +[[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 = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "collection-tester" +version = "0.1.0" +dependencies = [ + "cosmwasm-ext", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cosmwasm-vm", + "schemars", + "serde", + "snafu", +] + +[[package]] +name = "const_fn" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cosmwasm-ext" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cosmwasm-schema" +version = "0.10.0" +dependencies = [ + "schemars", + "serde_json", +] + +[[package]] +name = "cosmwasm-std" +version = "0.10.0" +dependencies = [ + "base64", + "schemars", + "serde", + "serde-json-wasm", + "snafu", +] + +[[package]] +name = "cosmwasm-storage" +version = "0.10.0" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cosmwasm-vm" +version = "0.10.0" +dependencies = [ + "cosmwasm-std", + "hex", + "memmap", + "parity-wasm", + "schemars", + "serde", + "serde_json", + "sha2", + "snafu", + "wasmer-clif-backend", + "wasmer-middleware-common", + "wasmer-runtime-core", + "wasmer-singlepass-backend", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a9c21f8042b9857bda93f6c1910b9f9f24100187a3d3d52f214a34e3dc5818" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7853f77a6e4a33c67a69c40f5e1bb982bd2dc5c4a22e17e67b65bbccf9b33b2e" +dependencies = [ + "byteorder", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "gimli 0.20.0", + "log", + "smallvec", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084cd6d5fb0d1da28acd72c199471bfb09acc703ec8f3bf07b1699584272a3b9" +dependencies = [ + "cranelift-codegen-shared", + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701b599783305a58c25027a4d73f2d6b599b2d8ef3f26677275f480b4d51e05d" + +[[package]] +name = "cranelift-entity" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88e792b28e1ebbc0187b72ba5ba880dad083abe9231a99d19604d10c9e73f38" + +[[package]] +name = "cranelift-native" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32daf082da21c0c05d93394ff4842c2ab7c4991b1f3186a1d952f8ac660edd0b" +dependencies = [ + "cranelift-codegen", + "raw-cpuid", + "target-lexicon", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" +dependencies = [ + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "const_fn", + "lazy_static", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dynasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a814e1edeb85dd2a3c6fc0d6bf76d02ca5695d438c70ecee3d90774f3259c5" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "owning_ref", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dynasmrt" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a393aaeb4441a48bcf47b5b6155971f82cc1eb77e22855403ccc0415ac8328d" +dependencies = [ + "byteorder", + "memmap", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "errno" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[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 = "gimli" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" +dependencies = [ + "byteorder", + "indexmap", +] + +[[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 = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[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 = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +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 = "nix" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + +[[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" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parity-wasm" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-cpuid" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf" +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 = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-bench" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d733da87e79faaac25616e33d26299a41143fd4cd42746cbb0e91d8feea243fd" +dependencies = [ + "byteorder", + "serde", +] + +[[package]] +name = "serde-json-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7294d94d390f1d2334697c065ea591d7074c676e2d20aa6f1df752fced29823f" +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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +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.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +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 0.9.0", + "opaque-debug", +] + +[[package]] +name = "smallvec" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" + +[[package]] +name = "snafu" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7073448732a89f2f3e6581989106067f403d378faeafb4a50812eb814170d3e5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" + +[[package]] +name = "syn" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "target-lexicon" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" + +[[package]] +name = "thiserror" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wasmer-clif-backend" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691ea323652d540a10722066dbf049936f4367bb22a96f8992a262a942a8b11b" +dependencies = [ + "byteorder", + "cranelift-codegen", + "cranelift-entity", + "cranelift-native", + "libc", + "nix", + "rayon", + "serde", + "serde-bench", + "serde_bytes", + "serde_derive", + "target-lexicon", + "wasmer-clif-fork-frontend", + "wasmer-clif-fork-wasm", + "wasmer-runtime-core", + "wasmer-win-exception-handler", + "wasmparser", + "winapi", +] + +[[package]] +name = "wasmer-clif-fork-frontend" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c23f2824f354a00a77e4b040eef6e1d4c595a8a3e9013bad65199cc8dade9a5a" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "wasmer-clif-fork-wasm" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35e21d3aebc51cc6ebc0e830cf8458a9891c3482fb3c65ad18d408102929ae5" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "log", + "thiserror", + "wasmer-clif-fork-frontend", + "wasmparser", +] + +[[package]] +name = "wasmer-middleware-common" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd94068186b25fbe5213442648ffe0fa65ee77389bed020404486fd22056cc87" +dependencies = [ + "wasmer-runtime-core", +] + +[[package]] +name = "wasmer-runtime-core" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d4253f097502423d8b19d54cb18745f61b984b9dbce32424cba7945cfef367" +dependencies = [ + "bincode", + "blake3", + "cc", + "digest 0.8.1", + "errno", + "hex", + "indexmap", + "lazy_static", + "libc", + "nix", + "page_size", + "parking_lot", + "rustc_version", + "serde", + "serde-bench", + "serde_bytes", + "serde_derive", + "smallvec", + "target-lexicon", + "wasmparser", + "winapi", +] + +[[package]] +name = "wasmer-singlepass-backend" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cf84179dd5e92b784f7bc190b237f1184916a6d6d3f87d4dd94ca371a2cc25" +dependencies = [ + "bincode", + "byteorder", + "dynasm", + "dynasmrt", + "lazy_static", + "libc", + "nix", + "serde", + "serde_derive", + "smallvec", + "wasmer-runtime-core", +] + +[[package]] +name = "wasmer-win-exception-handler" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf22ce6dc66d893099aac853d451bf9443fa8f5443f5bf4fc63f3aebd7b592b1" +dependencies = [ + "cc", + "libc", + "wasmer-runtime-core", + "winapi", +] + +[[package]] +name = "wasmparser" +version = "0.51.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" + +[[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/collection-tester/Cargo.toml b/contracts/collection-tester/Cargo.toml new file mode 100644 index 000000000..6b40a1c64 --- /dev/null +++ b/contracts/collection-tester/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "collection-tester" +version = "0.1.0" +authors = ["shiki.tak"] +edition = "2018" +description = "simple tester for cosmwasm/ext" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["singlepass"] +# For quicker tests, cargo test --lib. for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] +cranelift = ["cosmwasm-vm/default-cranelift"] +singlepass = ["cosmwasm-vm/default-singlepass"] + +[dependencies] +cosmwasm-ext = { path = "../../packages/ext" } +cosmwasm-std = { path = "../../packages/std", features = ["iterator"] } +cosmwasm-storage = { path = "../../packages/storage", features = ["iterator"] } +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +snafu = { version = "0.6.3" } + +[dev-dependencies] +cosmwasm-schema = { path = "../../packages/schema" } +cosmwasm-vm = { path = "../../packages/vm", default-features = false, features = ["iterator"] } diff --git a/contracts/collection-tester/examples/schema.rs b/contracts/collection-tester/examples/schema.rs new file mode 100644 index 000000000..cda49d257 --- /dev/null +++ b/contracts/collection-tester/examples/schema.rs @@ -0,0 +1,17 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use bindings_tester::msg::{HandleMsg, InitMsg, QueryMsg}; + +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!(InitMsg), &out_dir); + export_schema(&schema_for!(HandleMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); +} diff --git a/contracts/collection-tester/schema/handle_msg.json b/contracts/collection-tester/schema/handle_msg.json new file mode 100644 index 000000000..385704b45 --- /dev/null +++ b/contracts/collection-tester/schema/handle_msg.json @@ -0,0 +1,956 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HandleMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "issue" + ], + "properties": { + "issue": { + "type": "object", + "required": [ + "amount", + "decimals", + "img_uri", + "meta", + "mintable", + "name", + "owner", + "symbol", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "decimals": { + "$ref": "#/definitions/Uint128" + }, + "img_uri": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "mintable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "symbol": { + "type": "string" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "grant_perm" + ], + "properties": { + "grant_perm": { + "type": "object", + "required": [ + "contract_id", + "from", + "permission", + "to" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "type": "string" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "revoke_perm" + ], + "properties": { + "revoke_perm": { + "type": "object", + "required": [ + "contract_id", + "from", + "permission" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "modify_token" + ], + "properties": { + "modify_token": { + "type": "object", + "required": [ + "contract_id", + "owner" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "create" + ], + "properties": { + "create": { + "type": "object", + "required": [ + "base_img_uri", + "meta", + "name", + "owner" + ], + "properties": { + "base_img_uri": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "issue_nft" + ], + "properties": { + "issue_nft": { + "type": "object", + "required": [ + "contract_id", + "meta", + "name", + "owner" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "issue_ft" + ], + "properties": { + "issue_ft": { + "type": "object", + "required": [ + "amount", + "contract_id", + "decimals", + "meta", + "mintable", + "name", + "owner", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "decimals": { + "$ref": "#/definitions/Uint128" + }, + "meta": { + "type": "string" + }, + "mintable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "mint_nft" + ], + "properties": { + "mint_nft": { + "type": "object", + "required": [ + "contract_id", + "from", + "to", + "token_types" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "token_types": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "mint_ft" + ], + "properties": { + "mint_ft": { + "type": "object", + "required": [ + "contract_id", + "from", + "to", + "tokens" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "tokens": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn_nft" + ], + "properties": { + "burn_nft": { + "type": "object", + "required": [ + "contract_id", + "from", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn_nft_from" + ], + "properties": { + "burn_nft_from": { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "token_ids" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn_ft" + ], + "properties": { + "burn_ft": { + "type": "object", + "required": [ + "amounts", + "contract_id", + "from" + ], + "properties": { + "amounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn_ft_from" + ], + "properties": { + "burn_ft_from": { + "type": "object", + "required": [ + "amounts", + "contract_id", + "from", + "proxy" + ], + "properties": { + "amounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer_n_f_t" + ], + "properties": { + "transfer_n_f_t": { + "type": "object", + "required": [ + "contract_id", + "from", + "to", + "token_ids" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer_n_f_t_from" + ], + "properties": { + "transfer_n_f_t_from": { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "to", + "token_ids" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer_ft" + ], + "properties": { + "transfer_ft": { + "type": "object", + "required": [ + "contract_id", + "from", + "to", + "tokens" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "tokens": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer_f_t_from" + ], + "properties": { + "transfer_f_t_from": { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "to", + "tokens" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "tokens": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "modify_collection" + ], + "properties": { + "modify_collection": { + "type": "object", + "required": [ + "contract_id", + "key", + "owner", + "token_index", + "token_type", + "value" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "key": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "token_index": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "disapprove" + ], + "properties": { + "disapprove": { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "grant_perm_collection" + ], + "properties": { + "grant_perm_collection": { + "type": "object", + "required": [ + "contract_id", + "from", + "permission", + "to" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "type": "string" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "revoke_perm_collection" + ], + "properties": { + "revoke_perm_collection": { + "type": "object", + "required": [ + "contract_id", + "from", + "permission" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "attach" + ], + "properties": { + "attach": { + "type": "object", + "required": [ + "contract_id", + "from", + "to_token_id", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to_token_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "detach" + ], + "properties": { + "detach": { + "type": "object", + "required": [ + "contract_id", + "from", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "attach_from" + ], + "properties": { + "attach_from": { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "to_token_id", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to_token_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "detach_from" + ], + "properties": { + "detach_from": { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "token_id": { + "type": "string" + } + } + } + } + } + ], + "definitions": { + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/collection-tester/schema/init_msg.json b/contracts/collection-tester/schema/init_msg.json new file mode 100644 index 000000000..2b274b4e6 --- /dev/null +++ b/contracts/collection-tester/schema/init_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InitMsg", + "type": "object" +} diff --git a/contracts/collection-tester/schema/query_msg.json b/contracts/collection-tester/schema/query_msg.json new file mode 100644 index 000000000..f5a7e20ce --- /dev/null +++ b/contracts/collection-tester/schema/query_msg.json @@ -0,0 +1,229 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "get_token" + ], + "properties": { + "get_token": { + "type": "object", + "required": [ + "contract_id" + ], + "properties": { + "contract_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_token_balance" + ], + "properties": { + "get_token_balance": { + "type": "object", + "required": [ + "address", + "contract_id" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_total" + ], + "properties": { + "get_total": { + "type": "object", + "required": [ + "contract_id", + "target" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "target": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_token_perm" + ], + "properties": { + "get_token_perm": { + "type": "object", + "required": [ + "address", + "contract_id" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_collection" + ], + "properties": { + "get_collection": { + "type": "object", + "required": [ + "contract_id" + ], + "properties": { + "contract_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_collection_balance" + ], + "properties": { + "get_collection_balance": { + "type": "object", + "required": [ + "addr", + "contract_id", + "token_id" + ], + "properties": { + "addr": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_token_type" + ], + "properties": { + "get_token_type": { + "type": "object", + "required": [ + "contract_id", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_token_types" + ], + "properties": { + "get_token_types": { + "type": "object", + "required": [ + "contract_id" + ], + "properties": { + "contract_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_token_with_collection" + ], + "properties": { + "get_token_with_collection": { + "type": "object", + "required": [ + "contract_id", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_tokens" + ], + "properties": { + "get_tokens": { + "type": "object", + "required": [ + "contract_id" + ], + "properties": { + "contract_id": { + "type": "string" + } + } + } + } + } + ], + "definitions": { + "HumanAddr": { + "type": "string" + } + } +} diff --git a/contracts/collection-tester/src/contract.rs b/contracts/collection-tester/src/contract.rs new file mode 100644 index 000000000..a9c882475 --- /dev/null +++ b/contracts/collection-tester/src/contract.rs @@ -0,0 +1,1137 @@ +use std::convert::TryFrom; +use std::str::FromStr; + +use cosmwasm_std::{ + log, to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr, + InitResponse, Querier, StdResult, Storage, Uint128, +}; + +use cosmwasm_ext::{ + Change, Coin, Collection, CollectionMsg, CollectionPerm, CollectionRoute, + LinkCollectionQuerier, LinkMsgWrapper, MintNFTParam, Module, MsgData, Response, Target, +}; + +use crate::msg::{HandleMsg, InitMsg, QueryMsg}; +use crate::state::{config, State}; + +pub fn init( + deps: &mut Extern, + env: Env, + _msg: InitMsg, +) -> StdResult { + let state = State { + owner: deps.api.canonical_address(&env.message.sender)?, + }; + + config(&mut deps.storage).save(&state)?; + + Ok(InitResponse::default()) +} + +pub fn handle( + deps: &mut Extern, + env: Env, + msg: HandleMsg, +) -> HandleResult> { + match msg { + HandleMsg::Create { + owner, + name, + meta, + base_img_uri, + } => try_create(deps, env, owner, name, meta, base_img_uri), + HandleMsg::IssueNft { + owner, + contract_id, + name, + meta, + } => try_issue_nft(deps, env, owner, contract_id, name, meta), + HandleMsg::IssueFt { + owner, + contract_id, + to, + name, + meta, + amount, + mintable, + decimals, + } => try_issue_ft( + deps, + env, + owner, + contract_id, + to, + name, + meta, + amount, + mintable, + decimals, + ), + HandleMsg::MintNft { + from, + contract_id, + to, + token_types, + } => try_mint_nft(deps, env, from, contract_id, to, token_types), + HandleMsg::MintFt { + from, + contract_id, + to, + tokens, + } => try_mint_ft(deps, env, from, contract_id, to, tokens), + HandleMsg::BurnNft { + from, + contract_id, + token_id, + } => try_burn_nft(deps, env, from, contract_id, token_id), + HandleMsg::BurnNftFrom { + proxy, + contract_id, + from, + token_ids, + } => try_burn_nft_from(deps, env, proxy, contract_id, from, token_ids), + HandleMsg::BurnFt { + from, + contract_id, + amounts, + } => try_burn_ft(deps, env, from, contract_id, amounts), + HandleMsg::BurnFtFrom { + proxy, + contract_id, + from, + amounts, + } => try_burn_ft_from(deps, env, proxy, contract_id, from, amounts), + HandleMsg::TransferNFT { + from, + contract_id, + to, + token_ids, + } => try_transfer_nft(deps, env, from, contract_id, to, token_ids), + HandleMsg::TransferNFTFrom { + proxy, + contract_id, + from, + to, + token_ids, + } => try_transfer_nft_from(deps, env, proxy, contract_id, from, to, token_ids), + HandleMsg::TransferFt { + from, + contract_id, + to, + tokens, + } => try_transfer_ft(deps, env, from, contract_id, to, tokens), + HandleMsg::TransferFTFrom { + proxy, + contract_id, + from, + to, + tokens, + } => try_transfer_ft_from(deps, env, proxy, contract_id, from, to, tokens), + HandleMsg::Modify { + owner, + contract_id, + token_type, + token_index, + key, + value, + } => try_modify( + deps, + env, + owner, + contract_id, + token_type, + token_index, + key, + value, + ), + HandleMsg::Approve { + approver, + contract_id, + proxy, + } => try_approve(deps, env, approver, contract_id, proxy), + HandleMsg::Disapprove { + approver, + contract_id, + proxy, + } => try_disapprove(deps, env, approver, contract_id, proxy), + HandleMsg::GrantPerm { + from, + contract_id, + to, + permission, + } => try_grant_perm(deps, env, from, contract_id, to, permission), + HandleMsg::RevokePerm { + from, + contract_id, + permission, + } => try_revoke_perm(deps, env, from, contract_id, permission), + HandleMsg::Attach { + from, + contract_id, + to_token_id, + token_id, + } => try_attach(deps, env, from, contract_id, to_token_id, token_id), + HandleMsg::Detach { + from, + contract_id, + token_id, + } => try_detach(deps, env, from, contract_id, token_id), + HandleMsg::AttachFrom { + proxy, + contract_id, + from, + to_token_id, + token_id, + } => try_attach_from(deps, env, proxy, contract_id, from, to_token_id, token_id), + HandleMsg::DetachFrom { + proxy, + contract_id, + from, + token_id, + } => try_detach_from(deps, env, proxy, contract_id, from, token_id), + } +} + +pub fn query( + deps: &Extern, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::GetCollection { contract_id } => query_collection(deps, contract_id), + QueryMsg::GetBalance { + contract_id, + token_id, + addr, + } => query_balance(deps, contract_id, token_id, addr), + QueryMsg::GetTokenType { + contract_id, + token_id, + } => query_token_type(deps, contract_id, token_id), + QueryMsg::GetTokenTypes { contract_id } => query_token_types(deps, contract_id), + QueryMsg::GetToken { + contract_id, + token_id, + } => query_token(deps, contract_id, token_id), + QueryMsg::GetTokens { contract_id } => query_tokens(deps, contract_id), + QueryMsg::GetNft { + contract_id, + token_id, + target, + } => query_nft(deps, contract_id, token_id, target), + QueryMsg::GetTotal { + contract_id, + token_id, + target, + } => query_total(deps, contract_id, token_id, target), + QueryMsg::GetRootOrParentOrChildren { + contract_id, + token_id, + target, + } => query_root_or_parent_or_children(deps, contract_id, token_id, target), + QueryMsg::GetPerms { contract_id, addr } => query_perms(deps, contract_id, addr), + QueryMsg::GetApproved { + contract_id, + proxy, + approver, + } => query_approved(deps, contract_id, proxy, approver), + } +} + +pub fn try_create( + _deps: &mut Extern, + _env: Env, + owner: HumanAddr, + name: String, + meta: String, + base_img_uri: String, +) -> HandleResult> { + // Some kind of logic. + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::Create, + data: CollectionMsg::Create { + owner, + name, + meta, + base_img_uri, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "create")], + data: None, + }; + Ok(res) +} + +pub fn try_issue_nft( + _deps: &mut Extern, + _env: Env, + owner: HumanAddr, + contract_id: String, + name: String, + meta: String, +) -> HandleResult> { + // Some kind of logic. + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::IssueNft, + data: CollectionMsg::IssueNft { + owner, + contract_id, + name, + meta, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "issue_nft")], + data: None, + }; + Ok(res) +} + +#[allow(clippy::too_many_arguments)] +pub fn try_issue_ft( + _deps: &mut Extern, + _env: Env, + owner: HumanAddr, + contract_id: String, + to: HumanAddr, + name: String, + meta: String, + amount: Uint128, + mintable: bool, + decimals: Uint128, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::IssueFt, + data: CollectionMsg::IssueFt { + owner, + contract_id, + to, + name, + meta, + amount, + mintable, + decimals, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "issue_ft")], + data: None, + }; + Ok(res) +} + +pub fn try_mint_nft( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + token_types: Vec, +) -> HandleResult> { + let mut params: Vec = vec![]; + for (i, _) in token_types.iter().enumerate() { + let mint_nft_param = MintNFTParam::new( + "nft-".to_string() + &(i.to_string()), + "".to_string(), + token_types[i].clone(), + ); + params.push(mint_nft_param) + } + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::MintNft, + data: CollectionMsg::MintNft { + from, + contract_id, + to, + params, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "mint_nft")], + data: None, + }; + Ok(res) +} + +pub fn try_mint_ft( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + tokens: Vec, +) -> HandleResult> { + let mut amount: Vec = vec![]; + tokens.iter().for_each(|token| { + let v: Vec<&str> = (token).split(':').collect(); + let coin = Coin::new(v[1].to_string(), Uint128::try_from(v[0]).unwrap()); + amount.push(coin); + }); + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::MintFt, + data: CollectionMsg::MintFt { + from, + contract_id, + to, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "mint_ft")], + data: None, + }; + Ok(res) +} + +pub fn try_burn_nft( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + token_id: String, +) -> HandleResult> { + let token_ids = vec![token_id]; + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::BurnNft, + data: CollectionMsg::BurnNft { + from, + contract_id, + token_ids, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "burn_nft")], + data: None, + }; + Ok(res) +} + +pub fn try_burn_nft_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + token_ids: Vec, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::BurnNftFrom, + data: CollectionMsg::BurnNftFrom { + proxy, + contract_id, + from, + token_ids, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "burn_nft_from")], + data: None, + }; + Ok(res) +} + +pub fn try_burn_ft( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + tokens: Vec, +) -> HandleResult> { + let mut amount: Vec = vec![]; + tokens.iter().for_each(|token| { + let v: Vec<&str> = (token).split(':').collect(); + let coin = Coin::new(v[1].to_string(), Uint128::try_from(v[0]).unwrap()); + amount.push(coin); + }); + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::BurnFt, + data: CollectionMsg::BurnFt { + from, + contract_id, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "burn_nft")], + data: None, + }; + Ok(res) +} + +pub fn try_burn_ft_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + tokens: Vec, +) -> HandleResult> { + let mut amount: Vec = vec![]; + tokens.iter().for_each(|token| { + let v: Vec<&str> = (token).split(':').collect(); + let coin = Coin::new(v[1].to_string(), Uint128::try_from(v[0]).unwrap()); + amount.push(coin); + }); + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::BurnFtFrom, + data: CollectionMsg::BurnFtFrom { + proxy, + contract_id, + from, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "burn_nft_from")], + data: None, + }; + Ok(res) +} + +pub fn try_transfer_nft( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + token_ids: Vec, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::TransferNft, + data: CollectionMsg::TransferNft { + from, + contract_id, + to, + token_ids, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "transfer_nft")], + data: None, + }; + Ok(res) +} + +pub fn try_transfer_nft_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to: HumanAddr, + token_ids: Vec, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::TransferNftFrom, + data: CollectionMsg::TransferNftFrom { + proxy, + contract_id, + from, + to, + token_ids, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "transfer_nft_from")], + data: None, + }; + Ok(res) +} + +pub fn try_transfer_ft( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + tokens: Vec, +) -> HandleResult> { + let mut amount: Vec = vec![]; + tokens.iter().for_each(|token| { + let v: Vec<&str> = (token).split(':').collect(); + let coin = Coin::new(v[1].to_string(), Uint128::try_from(v[0]).unwrap()); + amount.push(coin); + }); + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::TransferFt, + data: CollectionMsg::TransferFt { + from, + contract_id, + to, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "transfer_ft")], + data: None, + }; + Ok(res) +} + +pub fn try_transfer_ft_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to: HumanAddr, + tokens: Vec, +) -> HandleResult> { + let mut amount: Vec = vec![]; + tokens.iter().for_each(|token| { + let v: Vec<&str> = (token).split(':').collect(); + let coin = Coin::new(v[1].to_string(), Uint128::try_from(v[0]).unwrap()); + amount.push(coin); + }); + + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::TransferFtFrom, + data: CollectionMsg::TransferFtFrom { + proxy, + contract_id, + from, + to, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "transfer_ft_from")], + data: None, + }; + Ok(res) +} + +#[allow(clippy::too_many_arguments)] +pub fn try_modify( + _deps: &mut Extern, + _env: Env, + owner: HumanAddr, + contract_id: String, + token_type: String, + token_index: String, + key: String, + value: String, +) -> HandleResult> { + let change = Change::new(key, value); + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::Modify, + data: CollectionMsg::Modify { + owner, + contract_id, + token_type, + token_index, + changes: vec![change], + }, + }, + } + .into(); + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "modify_collection")], + data: None, + }; + Ok(res) +} + +pub fn try_approve( + _deps: &mut Extern, + _env: Env, + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::Approve, + data: CollectionMsg::Approve { + approver, + contract_id, + proxy, + }, + }, + } + .into(); + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "approve")], + data: None, + }; + Ok(res) +} + +pub fn try_disapprove( + _deps: &mut Extern, + _env: Env, + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::Disapprove, + data: CollectionMsg::Disapprove { + approver, + contract_id, + proxy, + }, + }, + } + .into(); + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "approve")], + data: None, + }; + Ok(res) +} + +pub fn try_grant_perm( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + perm_str: String, +) -> HandleResult> { + let permission = CollectionPerm::from_str(&perm_str).unwrap(); + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::GrantPerm, + data: CollectionMsg::GrantPerm { + from, + contract_id, + to, + permission, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "grant_perm")], + data: None, + }; + Ok(res) +} + +pub fn try_revoke_perm( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + perm_str: String, +) -> HandleResult> { + let permission = CollectionPerm::from_str(&perm_str).unwrap(); + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::RevokePerm, + data: CollectionMsg::RevokePerm { + from, + contract_id, + permission, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "revoke_perm")], + data: None, + }; + Ok(res) +} + +pub fn try_attach( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + to_token_id: String, + token_id: String, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::Attach, + data: CollectionMsg::Attach { + from, + contract_id, + to_token_id, + token_id, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "attach")], + data: None, + }; + Ok(res) +} + +pub fn try_detach( + _deps: &mut Extern, + _env: Env, + from: HumanAddr, + contract_id: String, + token_id: String, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::Detach, + data: CollectionMsg::Detach { + from, + contract_id, + token_id, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "detach")], + data: None, + }; + Ok(res) +} + +pub fn try_attach_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to_token_id: String, + token_id: String, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::AttachFrom, + data: CollectionMsg::AttachFrom { + proxy, + contract_id, + from, + to_token_id, + token_id, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "attach_from")], + data: None, + }; + Ok(res) +} + +pub fn try_detach_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + token_id: String, +) -> HandleResult> { + let msg: CosmosMsg> = + LinkMsgWrapper:: { + module: Module::Collectionencode, + msg_data: MsgData { + route: CollectionRoute::DetachFrom, + data: CollectionMsg::DetachFrom { + proxy, + contract_id, + from, + token_id, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "detach_from")], + data: None, + }; + Ok(res) +} + +fn query_collection( + deps: &Extern, + contract_id: String, +) -> StdResult { + let res = match LinkCollectionQuerier::new(&deps.querier).query_collection(contract_id)? { + Some(collection_response) => collection_response, + None => return to_binary(&None::>>), + }; + let out = to_binary(&res)?; + Ok(out) +} + +fn query_balance( + deps: &Extern, + contract_id: String, + token_id: String, + addr: HumanAddr, +) -> StdResult { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_balance(contract_id, token_id, addr) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_token_type( + deps: &Extern, + contract_id: String, + token_id: String, +) -> StdResult { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_token_type(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_token_types( + deps: &Extern, + contract_id: String, +) -> StdResult { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_token_types(contract_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_token( + deps: &Extern, + contract_id: String, + token_id: String, +) -> StdResult { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_token(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_tokens( + deps: &Extern, + contract_id: String, +) -> StdResult { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_tokens(contract_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_nft( + deps: &Extern, + contract_id: String, + token_id: String, + target: String, +) -> StdResult { + let res = match &*target { + "count" => LinkCollectionQuerier::new(&deps.querier) + .query_nft_count(contract_id, token_id) + .unwrap(), + "mint" => LinkCollectionQuerier::new(&deps.querier) + .query_nft_mint(contract_id, token_id) + .unwrap(), + "burn" => LinkCollectionQuerier::new(&deps.querier) + .query_nft_burn(contract_id, token_id) + .unwrap(), + _ => Uint128(0), + }; + let out = to_binary(&res)?; + Ok(out) +} + +fn query_total( + deps: &Extern, + contract_id: String, + token_id: String, + target_str: String, +) -> StdResult { + let target = Target::from_str(&target_str).unwrap(); + let res = LinkCollectionQuerier::new(&deps.querier) + .query_supply(contract_id, token_id, target) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_root_or_parent_or_children( + deps: &Extern, + contract_id: String, + token_id: String, + target: String, +) -> StdResult { + if target == "root" { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_root(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } else if target == "parent" { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_parent(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } else { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_children(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } +} + +fn query_perms( + deps: &Extern, + contract_id: String, + addr: HumanAddr, +) -> StdResult { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_perm(contract_id, addr) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_approved( + deps: &Extern, + contract_id: String, + proxy: HumanAddr, + approver: HumanAddr, +) -> StdResult { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_approved(contract_id, proxy, approver) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} diff --git a/contracts/collection-tester/src/lib.rs b/contracts/collection-tester/src/lib.rs new file mode 100644 index 000000000..5d3bf59e1 --- /dev/null +++ b/contracts/collection-tester/src/lib.rs @@ -0,0 +1,40 @@ +pub mod contract; +pub mod msg; +pub mod state; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use cosmwasm_std::{ + do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/contracts/collection-tester/src/msg.rs b/contracts/collection-tester/src/msg.rs new file mode 100644 index 000000000..0a6988744 --- /dev/null +++ b/contracts/collection-tester/src/msg.rs @@ -0,0 +1,199 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{HumanAddr, Uint128}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InitMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HandleMsg { + Create { + owner: HumanAddr, + name: String, + meta: String, + base_img_uri: String, + }, + IssueNft { + owner: HumanAddr, + contract_id: String, + name: String, + meta: String, + }, + IssueFt { + owner: HumanAddr, + contract_id: String, + to: HumanAddr, + name: String, + meta: String, + amount: Uint128, + mintable: bool, + decimals: Uint128, + }, + MintNft { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + token_types: Vec, + }, + MintFt { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + tokens: Vec, + }, + BurnNft { + from: HumanAddr, + contract_id: String, + token_id: String, + }, + BurnNftFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + token_ids: Vec, + }, + BurnFt { + from: HumanAddr, + contract_id: String, + amounts: Vec, + }, + BurnFtFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + amounts: Vec, + }, + TransferNFT { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + token_ids: Vec, + }, + TransferNFTFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to: HumanAddr, + token_ids: Vec, + }, + TransferFt { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + tokens: Vec, + }, + TransferFTFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to: HumanAddr, + tokens: Vec, + }, + Modify { + owner: HumanAddr, + contract_id: String, + token_type: String, + token_index: String, + key: String, + value: String, + }, + Approve { + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, + }, + Disapprove { + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, + }, + GrantPerm { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + permission: String, + }, + RevokePerm { + from: HumanAddr, + contract_id: String, + permission: String, + }, + Attach { + from: HumanAddr, + contract_id: String, + to_token_id: String, + token_id: String, + }, + Detach { + from: HumanAddr, + contract_id: String, + token_id: String, + }, + AttachFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to_token_id: String, + token_id: String, + }, + DetachFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + token_id: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + GetCollection { + contract_id: String, + }, + GetBalance { + contract_id: String, + token_id: String, + addr: HumanAddr, + }, + GetTokenType { + contract_id: String, + token_id: String, + }, + GetTokenTypes { + contract_id: String, + }, + GetToken { + contract_id: String, + token_id: String, + }, + GetTokens { + contract_id: String, + }, + GetNft { + contract_id: String, + token_id: String, + target: String, + }, + GetTotal { + contract_id: String, + token_id: String, + target: String, + }, + GetRootOrParentOrChildren { + contract_id: String, + token_id: String, + target: String, + }, + GetPerms { + contract_id: String, + addr: HumanAddr, + }, + GetApproved { + contract_id: String, + proxy: HumanAddr, + approver: HumanAddr, + }, +} diff --git a/contracts/collection-tester/src/state.rs b/contracts/collection-tester/src/state.rs new file mode 100644 index 000000000..471b41e25 --- /dev/null +++ b/contracts/collection-tester/src/state.rs @@ -0,0 +1,20 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{CanonicalAddr, Storage}; +use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; + +pub static CONFIG_KEY: &[u8] = b"config"; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct State { + pub owner: CanonicalAddr, +} + +pub fn config(storage: &mut S) -> Singleton { + singleton(storage, CONFIG_KEY) +} + +pub fn config_read(storage: &S) -> ReadonlySingleton { + singleton_read(storage, CONFIG_KEY) +} diff --git a/contracts/token-tester/src/contract.rs b/contracts/token-tester/src/contract.rs index bbe5bed34..a34a9dc0d 100644 --- a/contracts/token-tester/src/contract.rs +++ b/contracts/token-tester/src/contract.rs @@ -6,8 +6,8 @@ use cosmwasm_std::{ }; use cosmwasm_ext::{ - Change, LinkMsgWrapper, LinkTokenQuerier, Module, MsgData, Response, Token, TokenMsg, - TokenPerm, TokenRoute, TokenTarget, + Change, LinkMsgWrapper, LinkTokenQuerier, Module, MsgData, Response, Target, Token, TokenMsg, + TokenPerm, TokenRoute, }; use crate::msg::{HandleMsg, InitMsg, QueryMsg}; @@ -348,7 +348,7 @@ fn query_supply( contract_id: String, target_str: String, ) -> StdResult { - let target = TokenTarget::from_str(&target_str).unwrap(); + let target = Target::from_str(&target_str).unwrap(); let res = LinkTokenQuerier::new(&deps.querier) .query_supply(contract_id, target) .unwrap(); diff --git a/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json b/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json index 1305f0508..bdb9c46d8 100644 --- a/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json +++ b/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json @@ -15,36 +15,582 @@ } }, "definitions": { + "Change": { + "type": "object", + "required": [ + "field", + "value" + ], + "properties": { + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "token_id": { + "type": "string" + } + } + }, "CollectionMsg": { "anyOf": [ { "type": "object", "required": [ - "create" - ], - "properties": { - "create": { - "type": "object", - "required": [ - "base_img_uri", - "meta", - "name", - "owner" - ], - "properties": { - "base_img_uri": { - "type": "string" - }, - "meta": { - "type": "string" - }, - "name": { - "type": "string" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } + "base_img_uri", + "meta", + "name", + "owner" + ], + "properties": { + "base_img_uri": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "meta", + "name", + "owner" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "decimals", + "meta", + "mintable", + "name", + "owner", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "decimals": { + "$ref": "#/definitions/Uint128" + }, + "meta": { + "type": "string" + }, + "mintable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "params", + "to" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/MintNFTParam" + } + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "token_ids" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "token_ids" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "proxy" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "to", + "token_ids" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "to", + "token_ids" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "to" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" } + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "proxy", + "to" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "permission", + "to" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "$ref": "#/definitions/permission" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "permission" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "permission": { + "$ref": "#/definitions/permission" + } + } + }, + { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "to_token_id", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "to_token_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "token_id": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "to_token_id", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to_token_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "contract_id", + "from", + "proxy", + "token_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "token_id": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "changes", + "contract_id", + "owner", + "token_index", + "token_type" + ], + "properties": { + "changes": { + "type": "array", + "items": { + "$ref": "#/definitions/Change" + } + }, + "contract_id": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "token_index": { + "type": "string" + }, + "token_type": { + "type": "string" } } } @@ -70,12 +616,34 @@ "attach", "detach", "attach_from", - "detach_from" + "detach_from", + "grant_perm", + "revoke_perm", + "modify" ] }, "HumanAddr": { "type": "string" }, + "MintNFTParam": { + "type": "object", + "required": [ + "meta", + "name", + "token_type" + ], + "properties": { + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "token_type": { + "type": "string" + } + } + }, "Module": { "enum": [ "tokenencode", @@ -96,6 +664,17 @@ "$ref": "#/definitions/CollectionRoute" } } + }, + "Uint128": { + "type": "string" + }, + "permission": { + "enum": [ + "Mint", + "Burn", + "Issue", + "Modify" + ] } } } diff --git a/packages/ext/src/collection.rs b/packages/ext/src/collection.rs new file mode 100644 index 000000000..329413d1b --- /dev/null +++ b/packages/ext/src/collection.rs @@ -0,0 +1,91 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +use cosmwasm_std::{HumanAddr, Uint128}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Collection { + pub contract_id: String, + pub name: String, + pub meta: String, + pub base_img_uri: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct NonFungibleToken { + pub contract_id: String, + pub token_id: String, + pub owner: HumanAddr, + pub name: String, + pub meta: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct FungibleToken { + pub contract_id: String, + pub token_id: String, + pub decimals: Uint128, + pub mintable: bool, + pub name: String, + pub meta: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct TokenType { + pub contract_id: String, + pub token_type: String, + pub name: String, + pub meta: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Coin { + pub token_id: String, + pub amount: Uint128, +} + +impl Coin { + pub fn new(token_id: String, amount: Uint128) -> Self { + Coin { token_id, amount } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct MintNFTParam { + pub name: String, + pub meta: String, + pub token_type: String, +} + +impl MintNFTParam { + pub fn new(name: String, meta: String, token_type: String) -> Self { + MintNFTParam { + name, + meta, + token_type, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename = "permission")] +pub enum CollectionPerm { + Mint, + Burn, + Issue, + Modify, +} + +impl FromStr for CollectionPerm { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "mint" => Ok(CollectionPerm::Mint), + "burn" => Ok(CollectionPerm::Burn), + "issue" => Ok(CollectionPerm::Issue), + "modify" => Ok(CollectionPerm::Modify), + _ => Err("Unknown permission type"), + } + } +} diff --git a/packages/ext/src/lib.rs b/packages/ext/src/lib.rs index 918ac0ea8..01cc05b19 100644 --- a/packages/ext/src/lib.rs +++ b/packages/ext/src/lib.rs @@ -1,15 +1,21 @@ +mod collection; mod msg; mod msg_collection; mod msg_token; +mod querier_collection; mod querier_token; mod query; mod token; +pub use collection::{ + Coin, Collection, CollectionPerm, FungibleToken, MintNFTParam, NonFungibleToken, TokenType, +}; pub use msg::{Change, LinkMsgWrapper, Module, MsgData}; pub use msg_collection::{CollectionMsg, CollectionRoute}; pub use msg_token::{TokenMsg, TokenRoute}; -pub use querier_token::{LinkTokenQuerier, TokenQuery, TokenQueryRoute, TokenTarget}; -pub use query::{LinkQueryWrapper, QueryData, Response}; +pub use querier_collection::{CollectionQuery, CollectionQueryRoute, LinkCollectionQuerier}; +pub use querier_token::{LinkTokenQuerier, TokenQuery, TokenQueryRoute}; +pub use query::{LinkQueryWrapper, QueryData, Response, Target}; pub use token::{Token, TokenPerm}; // This export is added to all contracts that import this package, signifying that they require diff --git a/packages/ext/src/msg_collection.rs b/packages/ext/src/msg_collection.rs index 90dfea837..8dd462bac 100644 --- a/packages/ext/src/msg_collection.rs +++ b/packages/ext/src/msg_collection.rs @@ -1,7 +1,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::HumanAddr; +use cosmwasm_std::{HumanAddr, Uint128}; + +use crate::collection::{Coin, CollectionPerm, MintNFTParam}; +use crate::msg::Change; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -25,10 +28,14 @@ pub enum CollectionRoute { Detach, AttachFrom, DetachFrom, + GrantPerm, + RevokePerm, + Modify, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] +#[serde(untagged)] pub enum CollectionMsg { Create { owner: HumanAddr, @@ -36,4 +43,132 @@ pub enum CollectionMsg { meta: String, base_img_uri: String, }, + IssueNft { + owner: HumanAddr, + contract_id: String, + name: String, + meta: String, + }, + IssueFt { + owner: HumanAddr, + contract_id: String, + to: HumanAddr, + name: String, + meta: String, + amount: Uint128, + mintable: bool, + decimals: Uint128, + }, + MintNft { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + params: Vec, + }, + MintFt { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Vec, + }, + BurnNft { + from: HumanAddr, + contract_id: String, + token_ids: Vec, + }, + BurnNftFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + token_ids: Vec, + }, + BurnFt { + from: HumanAddr, + contract_id: String, + amount: Vec, + }, + BurnFtFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + amount: Vec, + }, + TransferNft { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + token_ids: Vec, + }, + TransferNftFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to: HumanAddr, + token_ids: Vec, + }, + TransferFt { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Vec, + }, + TransferFtFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to: HumanAddr, + amount: Vec, + }, + GrantPerm { + from: HumanAddr, + contract_id: String, + to: HumanAddr, + permission: CollectionPerm, + }, + RevokePerm { + from: HumanAddr, + contract_id: String, + permission: CollectionPerm, + }, + Approve { + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, + }, + Disapprove { + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, + }, + Attach { + from: HumanAddr, + contract_id: String, + to_token_id: String, + token_id: String, + }, + Detach { + from: HumanAddr, + contract_id: String, + token_id: String, + }, + AttachFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + to_token_id: String, + token_id: String, + }, + DetachFrom { + proxy: HumanAddr, + contract_id: String, + from: HumanAddr, + token_id: String, + }, + Modify { + owner: HumanAddr, + contract_id: String, + token_type: String, + token_index: String, + changes: Vec, + }, } diff --git a/packages/ext/src/querier_collection.rs b/packages/ext/src/querier_collection.rs new file mode 100644 index 000000000..d2e804597 --- /dev/null +++ b/packages/ext/src/querier_collection.rs @@ -0,0 +1,364 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{HumanAddr, Querier, StdResult, Uint128}; + +use crate::collection::{Collection, CollectionPerm, FungibleToken, NonFungibleToken, TokenType}; +use crate::query::{LinkQueryWrapper, Module, QueryData, Response, Target}; + +pub struct LinkCollectionQuerier<'a, Q: Querier> { + querier: &'a Q, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CollectionQueryRoute { + Collections, + Balance, + Tokens, + Supply, + Perms, + Tokentypes, + Nftcount, + Nftmint, + Nftburn, + Mint, + Burn, + Total, + Root, + Parent, + Children, + Approved, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CollectionQuery { + QueryCollectionParam { + contract_id: String, + }, + QueryBalanceParam { + contract_id: String, + token_id: String, + addr: HumanAddr, + }, + QueryTokentypesParam { + contract_id: String, + token_id: String, + }, + QueryTokensParam { + contract_id: String, + token_id: String, + }, + QueryTotalParam { + contract_id: String, + token_id: String, + target: Target, + }, + QueryPermParam { + contract_id: String, + address: HumanAddr, + }, + QueryParentParam { + contract_id: String, + token_id: String, + }, + QueryRootParam { + contract_id: String, + token_id: String, + }, + QueryChildrenParam { + contract_id: String, + token_id: String, + }, + QueryApprovedParam { + contract_id: String, + proxy: HumanAddr, + approver: HumanAddr, + }, +} + +impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { + pub fn new(querier: &'a Q) -> Self { + LinkCollectionQuerier { querier } + } + + pub fn query_collection(&self, contract_id: String) -> StdResult>> { + let request = LinkQueryWrapper:: { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Collections, + data: CollectionQuery::QueryCollectionParam { contract_id }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_balance( + &self, + contract_id: String, + token_id: String, + addr: HumanAddr, + ) -> StdResult { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Balance, + data: CollectionQuery::QueryBalanceParam { + contract_id, + token_id, + addr, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_token_type( + &self, + contract_id: String, + token_id: String, + ) -> StdResult> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Tokentypes, + data: CollectionQuery::QueryTokentypesParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_token_types(&self, contract_id: String) -> StdResult>> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Tokentypes, + data: CollectionQuery::QueryTokentypesParam { + contract_id, + token_id: "".to_string(), + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_token( + &self, + contract_id: String, + token_id: String, + ) -> StdResult> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Tokens, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_tokens(&self, contract_id: String) -> StdResult>> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Tokens, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id: "".to_string(), + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_nft_count(&self, contract_id: String, token_id: String) -> StdResult { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Nftcount, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_nft_mint(&self, contract_id: String, token_id: String) -> StdResult { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Nftmint, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_nft_burn(&self, contract_id: String, token_id: String) -> StdResult { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Nftburn, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_supply( + &self, + contract_id: String, + token_id: String, + target: Target, + ) -> StdResult { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Supply, + data: CollectionQuery::QueryTotalParam { + contract_id, + token_id, + target, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_parent( + &self, + contract_id: String, + token_id: String, + ) -> StdResult> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Parent, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_root( + &self, + contract_id: String, + token_id: String, + ) -> StdResult> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Root, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_children( + &self, + contract_id: String, + token_id: String, + ) -> StdResult>> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Children, + data: CollectionQuery::QueryTokensParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_perm( + &self, + contract_id: String, + address: HumanAddr, + ) -> StdResult>> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Perms, + data: CollectionQuery::QueryPermParam { + contract_id, + address, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_approved( + &self, + contract_id: String, + proxy: HumanAddr, + approver: HumanAddr, + ) -> StdResult>> { + let request = LinkQueryWrapper { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Approved, + data: CollectionQuery::QueryApprovedParam { + contract_id, + proxy, + approver, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } +} diff --git a/packages/ext/src/querier_token.rs b/packages/ext/src/querier_token.rs index fc78680ec..17d8f1027 100644 --- a/packages/ext/src/querier_token.rs +++ b/packages/ext/src/querier_token.rs @@ -1,37 +1,15 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::str::FromStr; use cosmwasm_std::{HumanAddr, Querier, StdResult, Uint128}; -use crate::query::{LinkQueryWrapper, Module, QueryData, Response}; +use crate::query::{LinkQueryWrapper, Module, QueryData, Response, Target}; use crate::token::{Token, TokenPerm}; pub struct LinkTokenQuerier<'a, Q: Querier> { querier: &'a Q, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename = "target")] -#[serde(rename_all = "snake_case")] -pub enum TokenTarget { - Mint, - Burn, - Supply, -} - -impl FromStr for TokenTarget { - type Err = &'static str; - fn from_str(s: &str) -> Result { - match s { - "mint" => Ok(TokenTarget::Mint), - "burn" => Ok(TokenTarget::Burn), - "supply" => Ok(TokenTarget::Supply), - _ => Err("Unknown target type"), - } - } -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TokenQueryRoute { @@ -53,7 +31,7 @@ pub enum TokenQuery { }, QueryTotalParam { contract_id: String, - target: TokenTarget, + target: Target, }, QueryPermParam { contract_id: String, @@ -95,7 +73,7 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { Ok(res) } - pub fn query_supply(&self, contract_id: String, target: TokenTarget) -> StdResult { + pub fn query_supply(&self, contract_id: String, target: Target) -> StdResult { let request = LinkQueryWrapper:: { module: Module::Tokenencode, query_data: QueryData { diff --git a/packages/ext/src/query.rs b/packages/ext/src/query.rs index 244556bd2..8c5c1de12 100644 --- a/packages/ext/src/query.rs +++ b/packages/ext/src/query.rs @@ -1,8 +1,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::str::FromStr; use cosmwasm_std::QueryRequest; +use crate::querier_collection::{CollectionQuery, CollectionQueryRoute}; use crate::querier_token::{TokenQuery, TokenQueryRoute}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -34,6 +36,34 @@ impl Into>> } } +impl Into>> + for LinkQueryWrapper +{ + fn into(self) -> QueryRequest> { + QueryRequest::Custom(self) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Target { + Mint, + Burn, + Supply, +} + +impl FromStr for Target { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "mint" => Ok(Target::Mint), + "burn" => Ok(Target::Burn), + "supply" => Ok(Target::Supply), + _ => Err("Unknown target type"), + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Response { #[serde(rename = "type")] From 32503149ea68f5ab4293ec117610c5dea10cc6d3 Mon Sep 17 00:00:00 2001 From: shiki Date: Thu, 19 Nov 2020 12:49:36 +0900 Subject: [PATCH 04/12] feat: add approve, burn_from, transfer_from (#29) * feat: add approve, burn_from, transfer_from * fix: config.yml * fix: update schema * fix: msg name * fix: update schema --- .circleci/config.yml | 12 +- .../collection-tester/examples/schema.rs | 4 +- .../collection-tester/schema/handle_msg.json | 245 +----------------- .../collection-tester/schema/query_msg.json | 123 ++++++--- contracts/collection-tester/schema/state.json | 22 ++ contracts/collection-tester/src/contract.rs | 6 +- contracts/collection-tester/src/msg.rs | 6 +- contracts/token-tester/schema/handle_msg.json | 93 +++++++ contracts/token-tester/schema/query_msg.json | 50 ++++ contracts/token-tester/src/contract.rs | 144 +++++++++- contracts/token-tester/src/msg.rs | 27 ++ ...apper_for__token_route_and__token_msg.json | 74 +++++- packages/ext/src/msg_token.rs | 21 ++ packages/ext/src/querier_token.rs | 50 ++++ 14 files changed, 587 insertions(+), 290 deletions(-) create mode 100644 contracts/collection-tester/schema/state.json diff --git a/.circleci/config.yml b/.circleci/config.yml index d52101371..4a5f0cf20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,8 @@ workflows: - contract_queue - contract_reflect - contract_staking + - contract_token_tester + - contract_collection_tester - fmt - clippy deploy: @@ -553,7 +555,7 @@ jobs: key: cargocache-v2-contract_staking-rust:1.44.1-{{ checksum "Cargo.lock" }} contract_token_tester: docker: - - image: rust:1.44.1 + - image: rustlang/rust:nightly working_directory: ~/cosmwasm/contracts/token-tester steps: - checkout: @@ -574,9 +576,6 @@ jobs: name: Unit tests env: RUST_BACKTRACE=1 command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - run: name: Build and run schema generator command: cargo schema --locked @@ -602,7 +601,7 @@ jobs: contract_collection_tester: docker: - - image: rust:1.44.1 + - image: rustlang/rust:nightly working_directory: ~/cosmwasm/contracts/collection-tester steps: - checkout: @@ -623,9 +622,6 @@ jobs: name: Unit tests env: RUST_BACKTRACE=1 command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - run: name: Build and run schema generator command: cargo schema --locked diff --git a/contracts/collection-tester/examples/schema.rs b/contracts/collection-tester/examples/schema.rs index cda49d257..1ea654fd4 100644 --- a/contracts/collection-tester/examples/schema.rs +++ b/contracts/collection-tester/examples/schema.rs @@ -3,7 +3,8 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use bindings_tester::msg::{HandleMsg, InitMsg, QueryMsg}; +use collection_tester::msg::{HandleMsg, InitMsg, QueryMsg}; +use collection_tester::state::State; fn main() { let mut out_dir = current_dir().unwrap(); @@ -14,4 +15,5 @@ fn main() { export_schema(&schema_for!(InitMsg), &out_dir); export_schema(&schema_for!(HandleMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(State), &out_dir); } diff --git a/contracts/collection-tester/schema/handle_msg.json b/contracts/collection-tester/schema/handle_msg.json index 385704b45..ef75f28b5 100644 --- a/contracts/collection-tester/schema/handle_msg.json +++ b/contracts/collection-tester/schema/handle_msg.json @@ -2,227 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "HandleMsg", "anyOf": [ - { - "type": "object", - "required": [ - "issue" - ], - "properties": { - "issue": { - "type": "object", - "required": [ - "amount", - "decimals", - "img_uri", - "meta", - "mintable", - "name", - "owner", - "symbol", - "to" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "decimals": { - "$ref": "#/definitions/Uint128" - }, - "img_uri": { - "type": "string" - }, - "meta": { - "type": "string" - }, - "mintable": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "symbol": { - "type": "string" - }, - "to": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "contract_id", - "from", - "to" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "contract_id": { - "type": "string" - }, - "from": { - "$ref": "#/definitions/HumanAddr" - }, - "to": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "type": "object", - "required": [ - "amount", - "contract_id", - "from", - "to" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "contract_id": { - "type": "string" - }, - "from": { - "$ref": "#/definitions/HumanAddr" - }, - "to": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount", - "contract_id", - "from" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "contract_id": { - "type": "string" - }, - "from": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "grant_perm" - ], - "properties": { - "grant_perm": { - "type": "object", - "required": [ - "contract_id", - "from", - "permission", - "to" - ], - "properties": { - "contract_id": { - "type": "string" - }, - "from": { - "$ref": "#/definitions/HumanAddr" - }, - "permission": { - "type": "string" - }, - "to": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "revoke_perm" - ], - "properties": { - "revoke_perm": { - "type": "object", - "required": [ - "contract_id", - "from", - "permission" - ], - "properties": { - "contract_id": { - "type": "string" - }, - "from": { - "$ref": "#/definitions/HumanAddr" - }, - "permission": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "modify_token" - ], - "properties": { - "modify_token": { - "type": "object", - "required": [ - "contract_id", - "owner" - ], - "properties": { - "contract_id": { - "type": "string" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, { "type": "object", "required": [ @@ -528,10 +307,10 @@ { "type": "object", "required": [ - "transfer_n_f_t" + "transfer_nft" ], "properties": { - "transfer_n_f_t": { + "transfer_nft": { "type": "object", "required": [ "contract_id", @@ -562,10 +341,10 @@ { "type": "object", "required": [ - "transfer_n_f_t_from" + "transfer_nft_from" ], "properties": { - "transfer_n_f_t_from": { + "transfer_nft_from": { "type": "object", "required": [ "contract_id", @@ -634,10 +413,10 @@ { "type": "object", "required": [ - "transfer_f_t_from" + "transfer_ft_from" ], "properties": { - "transfer_f_t_from": { + "transfer_ft_from": { "type": "object", "required": [ "contract_id", @@ -672,10 +451,10 @@ { "type": "object", "required": [ - "modify_collection" + "modify" ], "properties": { - "modify_collection": { + "modify": { "type": "object", "required": [ "contract_id", @@ -765,10 +544,10 @@ { "type": "object", "required": [ - "grant_perm_collection" + "grant_perm" ], "properties": { - "grant_perm_collection": { + "grant_perm": { "type": "object", "required": [ "contract_id", @@ -796,10 +575,10 @@ { "type": "object", "required": [ - "revoke_perm_collection" + "revoke_perm" ], "properties": { - "revoke_perm_collection": { + "revoke_perm": { "type": "object", "required": [ "contract_id", diff --git a/contracts/collection-tester/schema/query_msg.json b/contracts/collection-tester/schema/query_msg.json index f5a7e20ce..cd5fa8696 100644 --- a/contracts/collection-tester/schema/query_msg.json +++ b/contracts/collection-tester/schema/query_msg.json @@ -5,10 +5,10 @@ { "type": "object", "required": [ - "get_token" + "get_collection" ], "properties": { - "get_token": { + "get_collection": { "type": "object", "required": [ "contract_id" @@ -24,21 +24,25 @@ { "type": "object", "required": [ - "get_token_balance" + "get_balance" ], "properties": { - "get_token_balance": { + "get_balance": { "type": "object", "required": [ - "address", - "contract_id" + "addr", + "contract_id", + "token_id" ], "properties": { - "address": { + "addr": { "$ref": "#/definitions/HumanAddr" }, "contract_id": { "type": "string" + }, + "token_id": { + "type": "string" } } } @@ -47,20 +51,20 @@ { "type": "object", "required": [ - "get_total" + "get_token_type" ], "properties": { - "get_total": { + "get_token_type": { "type": "object", "required": [ "contract_id", - "target" + "token_id" ], "properties": { "contract_id": { "type": "string" }, - "target": { + "token_id": { "type": "string" } } @@ -70,19 +74,15 @@ { "type": "object", "required": [ - "get_token_perm" + "get_token_types" ], "properties": { - "get_token_perm": { + "get_token_types": { "type": "object", "required": [ - "address", "contract_id" ], "properties": { - "address": { - "$ref": "#/definitions/HumanAddr" - }, "contract_id": { "type": "string" } @@ -93,17 +93,21 @@ { "type": "object", "required": [ - "get_collection" + "get_token" ], "properties": { - "get_collection": { + "get_token": { "type": "object", "required": [ - "contract_id" + "contract_id", + "token_id" ], "properties": { "contract_id": { "type": "string" + }, + "token_id": { + "type": "string" } } } @@ -112,25 +116,17 @@ { "type": "object", "required": [ - "get_collection_balance" + "get_tokens" ], "properties": { - "get_collection_balance": { + "get_tokens": { "type": "object", "required": [ - "addr", - "contract_id", - "token_id" + "contract_id" ], "properties": { - "addr": { - "$ref": "#/definitions/HumanAddr" - }, "contract_id": { "type": "string" - }, - "token_id": { - "type": "string" } } } @@ -139,19 +135,23 @@ { "type": "object", "required": [ - "get_token_type" + "get_nft" ], "properties": { - "get_token_type": { + "get_nft": { "type": "object", "required": [ "contract_id", + "target", "token_id" ], "properties": { "contract_id": { "type": "string" }, + "target": { + "type": "string" + }, "token_id": { "type": "string" } @@ -162,17 +162,25 @@ { "type": "object", "required": [ - "get_token_types" + "get_total" ], "properties": { - "get_token_types": { + "get_total": { "type": "object", "required": [ - "contract_id" + "contract_id", + "target", + "token_id" ], "properties": { "contract_id": { "type": "string" + }, + "target": { + "type": "string" + }, + "token_id": { + "type": "string" } } } @@ -181,19 +189,23 @@ { "type": "object", "required": [ - "get_token_with_collection" + "get_root_or_parent_or_children" ], "properties": { - "get_token_with_collection": { + "get_root_or_parent_or_children": { "type": "object", "required": [ "contract_id", + "target", "token_id" ], "properties": { "contract_id": { "type": "string" }, + "target": { + "type": "string" + }, "token_id": { "type": "string" } @@ -204,17 +216,48 @@ { "type": "object", "required": [ - "get_tokens" + "get_perms" ], "properties": { - "get_tokens": { + "get_perms": { "type": "object", "required": [ + "addr", "contract_id" ], "properties": { + "addr": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_approved" + ], + "properties": { + "get_approved": { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, "contract_id": { "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" } } } diff --git a/contracts/collection-tester/schema/state.json b/contracts/collection-tester/schema/state.json new file mode 100644 index 000000000..d4b4a3340 --- /dev/null +++ b/contracts/collection-tester/schema/state.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "$ref": "#/definitions/CanonicalAddr" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "CanonicalAddr": { + "$ref": "#/definitions/Binary" + } + } +} diff --git a/contracts/collection-tester/src/contract.rs b/contracts/collection-tester/src/contract.rs index a9c882475..d6918280b 100644 --- a/contracts/collection-tester/src/contract.rs +++ b/contracts/collection-tester/src/contract.rs @@ -101,13 +101,13 @@ pub fn handle( from, amounts, } => try_burn_ft_from(deps, env, proxy, contract_id, from, amounts), - HandleMsg::TransferNFT { + HandleMsg::TransferNft { from, contract_id, to, token_ids, } => try_transfer_nft(deps, env, from, contract_id, to, token_ids), - HandleMsg::TransferNFTFrom { + HandleMsg::TransferNftFrom { proxy, contract_id, from, @@ -120,7 +120,7 @@ pub fn handle( to, tokens, } => try_transfer_ft(deps, env, from, contract_id, to, tokens), - HandleMsg::TransferFTFrom { + HandleMsg::TransferFtFrom { proxy, contract_id, from, diff --git a/contracts/collection-tester/src/msg.rs b/contracts/collection-tester/src/msg.rs index 0a6988744..503b7c5dd 100644 --- a/contracts/collection-tester/src/msg.rs +++ b/contracts/collection-tester/src/msg.rs @@ -65,13 +65,13 @@ pub enum HandleMsg { from: HumanAddr, amounts: Vec, }, - TransferNFT { + TransferNft { from: HumanAddr, contract_id: String, to: HumanAddr, token_ids: Vec, }, - TransferNFTFrom { + TransferNftFrom { proxy: HumanAddr, contract_id: String, from: HumanAddr, @@ -84,7 +84,7 @@ pub enum HandleMsg { to: HumanAddr, tokens: Vec, }, - TransferFTFrom { + TransferFtFrom { proxy: HumanAddr, contract_id: String, from: HumanAddr, diff --git a/contracts/token-tester/schema/handle_msg.json b/contracts/token-tester/schema/handle_msg.json index 3e10b894e..69678a8ff 100644 --- a/contracts/token-tester/schema/handle_msg.json +++ b/contracts/token-tester/schema/handle_msg.json @@ -84,6 +84,41 @@ } } }, + { + "type": "object", + "required": [ + "transfer_from" + ], + "properties": { + "transfer_from": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "proxy", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, { "type": "object", "required": [ @@ -142,6 +177,37 @@ } } }, + { + "type": "object", + "required": [ + "burn_from" + ], + "properties": { + "burn_from": { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "proxy" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, { "type": "object", "required": [ @@ -222,6 +288,33 @@ } } } + }, + { + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } } ], "definitions": { diff --git a/contracts/token-tester/schema/query_msg.json b/contracts/token-tester/schema/query_msg.json index 618f08f4b..324a37a79 100644 --- a/contracts/token-tester/schema/query_msg.json +++ b/contracts/token-tester/schema/query_msg.json @@ -89,6 +89,56 @@ } } } + }, + { + "type": "object", + "required": [ + "get_is_approved" + ], + "properties": { + "get_is_approved": { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "get_approvers" + ], + "properties": { + "get_approvers": { + "type": "object", + "required": [ + "contract_id", + "proxy" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } } ], "definitions": { diff --git a/contracts/token-tester/src/contract.rs b/contracts/token-tester/src/contract.rs index a34a9dc0d..39dca751b 100644 --- a/contracts/token-tester/src/contract.rs +++ b/contracts/token-tester/src/contract.rs @@ -52,6 +52,13 @@ pub fn handle( to, amount, } => try_transfer(deps, env, from, contract_id, to, amount), + HandleMsg::TransferFrom { + proxy, + from, + contract_id, + to, + amount, + } => try_transfer_from(deps, env, proxy, from, contract_id, to, amount), HandleMsg::Mint { from, contract_id, @@ -63,6 +70,12 @@ pub fn handle( contract_id, amount, } => try_burn(deps, env, from, contract_id, amount), + HandleMsg::BurnFrom { + proxy, + from, + contract_id, + amount, + } => try_burn_from(deps, env, proxy, from, contract_id, amount), HandleMsg::GrantPerm { from, contract_id, @@ -75,6 +88,11 @@ pub fn handle( permission, } => try_revoke_perm(deps, env, from, contract_id, permission), HandleMsg::Modify { owner, contract_id } => try_modify(deps, env, owner, contract_id), + HandleMsg::Approve { + approver, + contract_id, + proxy, + } => try_approve(deps, env, approver, contract_id, proxy), } } @@ -96,6 +114,12 @@ pub fn query( contract_id, address, } => query_perm(deps, contract_id, address), + QueryMsg::GetIsApproved { + proxy, + contract_id, + approver, + } => query_is_approved(deps, proxy, contract_id, approver), + QueryMsg::GetApprovers { proxy, contract_id } => query_approvers(deps, proxy, contract_id), } } @@ -173,6 +197,40 @@ pub fn try_transfer( Ok(res) } +pub fn try_transfer_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, +) -> HandleResult> { + // Some kind of logic. + + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::TransferFrom, + data: TokenMsg::TransferFrom { + proxy, + from, + contract_id, + to, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "transfer_from")], + data: None, + }; + Ok(res) +} + pub fn try_mint( _deps: &mut Extern, _env: Env, @@ -231,6 +289,36 @@ pub fn try_burn( Ok(res) } +pub fn try_burn_from( + _deps: &mut Extern, + _env: Env, + proxy: HumanAddr, + from: HumanAddr, + contract_id: String, + amount: Uint128, +) -> HandleResult> { + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::BurnFrom, + data: TokenMsg::BurnFrom { + proxy, + from, + contract_id, + amount, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "burn_from")], + data: None, + }; + Ok(res) +} + pub fn try_grant_perm( _deps: &mut Extern, _env: Env, @@ -318,6 +406,34 @@ pub fn try_modify( Ok(res) } +pub fn try_approve( + _deps: &mut Extern, + _env: Env, + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, +) -> HandleResult> { + let msg: CosmosMsg> = LinkMsgWrapper { + module: Module::Tokenencode, + msg_data: MsgData { + route: TokenRoute::Approve, + data: TokenMsg::Approve { + approver, + contract_id, + proxy, + }, + }, + } + .into(); + + let res = HandleResponse { + messages: vec![msg], + log: vec![log("action", "approve")], + data: None, + }; + Ok(res) +} + fn query_token( deps: &Extern, contract_id: String, @@ -369,6 +485,32 @@ fn query_perm( Ok(out) } +fn query_is_approved( + deps: &Extern, + proxy: HumanAddr, + contract_id: String, + approver: HumanAddr, +) -> StdResult { + let res = LinkTokenQuerier::new(&deps.querier) + .query_is_approved(proxy, contract_id, approver) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) +} + +fn query_approvers( + deps: &Extern, + proxy: HumanAddr, + contract_id: String, +) -> StdResult { + let res = match LinkTokenQuerier::new(&deps.querier).query_approvers(proxy, contract_id)? { + Some(approvers) => approvers, + None => return to_binary(&None::>>), + }; + let out = to_binary(&res)?; + Ok(out) +} + fn _query_owner(deps: &Extern) -> StdResult { let state = config_read(&deps.storage).load()?; Ok(deps.api.human_address(&state.owner)?) @@ -383,7 +525,7 @@ mod tests { fn create_contract(owner: String) -> (Extern, Env) { let mut deps = mock_dependencies(20, &coins(1000, "cony")); let env = mock_env(owner, &coins(1000, "cony")); - let res = init(&mut deps, env, InitMsg {}).unwrap(); + let res = init(&mut deps, env.clone(), InitMsg {}).unwrap(); assert_eq!(0, res.messages.len()); (deps, env) } diff --git a/contracts/token-tester/src/msg.rs b/contracts/token-tester/src/msg.rs index e8eb7092c..d1732c004 100644 --- a/contracts/token-tester/src/msg.rs +++ b/contracts/token-tester/src/msg.rs @@ -26,6 +26,13 @@ pub enum HandleMsg { to: HumanAddr, amount: Uint128, }, + TransferFrom { + proxy: HumanAddr, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, + }, Mint { from: HumanAddr, contract_id: String, @@ -37,6 +44,12 @@ pub enum HandleMsg { contract_id: String, amount: Uint128, }, + BurnFrom { + proxy: HumanAddr, + from: HumanAddr, + contract_id: String, + amount: Uint128, + }, GrantPerm { from: HumanAddr, contract_id: String, @@ -52,6 +65,11 @@ pub enum HandleMsg { owner: HumanAddr, contract_id: String, }, + Approve { + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -72,4 +90,13 @@ pub enum QueryMsg { contract_id: String, address: HumanAddr, }, + GetIsApproved { + proxy: HumanAddr, + contract_id: String, + approver: HumanAddr, + }, + GetApprovers { + proxy: HumanAddr, + contract_id: String, + }, } diff --git a/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json b/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json index 9de62427f..208a48bc2 100644 --- a/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json +++ b/packages/ext/schema/link_msg_wrapper_for__token_route_and__token_msg.json @@ -122,6 +122,33 @@ } } }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "proxy", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + }, + "to": { + "$ref": "#/definitions/HumanAddr" + } + } + }, { "type": "object", "required": [ @@ -164,6 +191,29 @@ } } }, + { + "type": "object", + "required": [ + "amount", + "contract_id", + "from", + "proxy" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_id": { + "type": "string" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + }, { "type": "object", "required": [ @@ -227,6 +277,25 @@ "$ref": "#/definitions/HumanAddr" } } + }, + { + "type": "object", + "required": [ + "approver", + "contract_id", + "proxy" + ], + "properties": { + "approver": { + "$ref": "#/definitions/HumanAddr" + }, + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } } ] }, @@ -234,11 +303,14 @@ "enum": [ "issue", "transfer", + "transfer_from", "mint", "burn", + "burn_from", "grant_perm", "revoke_perm", - "modify" + "modify", + "approve" ] }, "Uint128": { diff --git a/packages/ext/src/msg_token.rs b/packages/ext/src/msg_token.rs index 49c1a53b8..af74e699b 100644 --- a/packages/ext/src/msg_token.rs +++ b/packages/ext/src/msg_token.rs @@ -11,11 +11,14 @@ use crate::token::TokenPerm; pub enum TokenRoute { Issue, Transfer, + TransferFrom, Mint, Burn, + BurnFrom, GrantPerm, RevokePerm, Modify, + Approve, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -39,6 +42,13 @@ pub enum TokenMsg { to: HumanAddr, amount: Uint128, }, + TransferFrom { + proxy: HumanAddr, + from: HumanAddr, + contract_id: String, + to: HumanAddr, + amount: Uint128, + }, Mint { from: HumanAddr, contract_id: String, @@ -50,6 +60,12 @@ pub enum TokenMsg { contract_id: String, amount: Uint128, }, + BurnFrom { + proxy: HumanAddr, + from: HumanAddr, + contract_id: String, + amount: Uint128, + }, GrantPerm { from: HumanAddr, contract_id: String, @@ -66,6 +82,11 @@ pub enum TokenMsg { contract_id: String, changes: Vec, }, + Approve { + approver: HumanAddr, + contract_id: String, + proxy: HumanAddr, + }, } #[cfg(test)] diff --git a/packages/ext/src/querier_token.rs b/packages/ext/src/querier_token.rs index 17d8f1027..dee2951ab 100644 --- a/packages/ext/src/querier_token.rs +++ b/packages/ext/src/querier_token.rs @@ -17,6 +17,8 @@ pub enum TokenQueryRoute { Balance, Supply, Perms, + Approved, + Approvers, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -37,6 +39,15 @@ pub enum TokenQuery { contract_id: String, address: HumanAddr, }, + QueryIsApprovedParam { + proxy: HumanAddr, + contract_id: String, + approver: HumanAddr, + }, + QueryApproversParam { + proxy: HumanAddr, + contract_id: String, + }, } impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { @@ -108,4 +119,43 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { let res = self.querier.custom_query(&request.into())?; Ok(res) } + + pub fn query_is_approved( + &self, + proxy: HumanAddr, + contract_id: String, + approver: HumanAddr, + ) -> StdResult { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Approved, + data: TokenQuery::QueryIsApprovedParam { + proxy, + contract_id, + approver, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_approvers( + &self, + proxy: HumanAddr, + contract_id: String, + ) -> StdResult>> { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Approvers, + data: TokenQuery::QueryApproversParam { proxy, contract_id }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } } From 237441e30c7c22e0194b3aa00b3b3e4677205a1c Mon Sep 17 00:00:00 2001 From: shiki Date: Mon, 7 Dec 2020 15:10:33 +0900 Subject: [PATCH 05/12] chore: add semantic.yml (#42) --- .github/semantic.yml | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/semantic.yml diff --git a/.github/semantic.yml b/.github/semantic.yml new file mode 100644 index 000000000..96ed67a7d --- /dev/null +++ b/.github/semantic.yml @@ -0,0 +1,72 @@ +# Always validate the PR title AND all the commits +titleAndCommits: true +# Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns") +# this is only relevant when using commitsOnly: true (or titleAndCommits: true) +allowMergeCommits: true +# Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"") +# this is only relevant when using commitsOnly: true (or titleAndCommits: true) +allowRevertCommits: true +# By default types specified in commitizen/conventional-commit-types is used. +# See: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json +# { + # "types": { + # "feat": { + # "description": "A new feature", + # "title": "Features" + # }, + # "fix": { + # "description": "A bug fix", + # "title": "Bug Fixes" + # }, + # "docs": { + # "description": "Documentation only changes", + # "title": "Documentation" + # }, + # "style": { + # "description": "Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)", + # "title": "Styles" + # }, + # "refactor": { + # "description": "A code change that neither fixes a bug nor adds a feature", + # "title": "Code Refactoring" + # }, + # "perf": { + # "description": "A code change that improves performance", + # "title": "Performance Improvements" + # }, + # "test": { + # "description": "Adding missing tests or correcting existing tests", + # "title": "Tests" + # }, + # "build": { + # "description": "Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)", + # "title": "Builds" + # }, + # "ci": { + # "description": "Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)", + # "title": "Continuous Integrations" + # }, + # "chore": { + # "description": "Other changes that don't modify src or test files", + # "title": "Chores" + # }, + # "revert": { + # "description": "Reverts a previous commit", + # "title": "Reverts" + # } +# } +#} +# You can override the valid types +types: + - feat + - fix + - docs + - style + - refactor + - perf + - test + - build + - ci + - chore + - revert + - release From f4b735c67cd98f4222b66e35acc7eac8b32ce1af Mon Sep 17 00:00:00 2001 From: loloicci Date: Wed, 23 Dec 2020 17:20:31 +0900 Subject: [PATCH 06/12] Modify command to build token-tester contract in README (#37) --- contracts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/README.md b/contracts/README.md index 0b7e5e62a..68f8701b0 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -40,7 +40,7 @@ docker run --rm -v "$(pwd)":/code \ cosmwasm/rust-optimizer:0.9.0 ./contracts/staking docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="devcontract_token_tester",target=/code/contracts/staking/target \ + --mount type=volume,source="devcontract_token_tester",target=/code/contracts/token-tester/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer:0.9.0 ./contracts/token-tester ``` From 5a5c784055ed800be4bfe88687ecd49ff1d34b21 Mon Sep 17 00:00:00 2001 From: shiki Date: Tue, 29 Dec 2020 16:06:31 +0900 Subject: [PATCH 07/12] fix: refining ext and test contract (#35) * feat: add query approved function for collection module * fix: fix LinkQueryWrapper type * fix: rename query_approved to query_is_approved * fix: bug fix query_is_approved return value * refactor: unify NFT and FT types into one * refactor: change the arguments of modify function * refactor: rename GetNft to GetNftCount * chore: add snake_case * refactor: remove target param from query_supply and split it into query_total, query_mint and query_burn in token module * refactor: remove target param from query_supply and split it into query_mint and query_burn in collection module * fix: update schema json * fix: separate token into FT and NFT and implement deserialize * fix: swap the order of parameters * fix: rename query param * fix: fix lint error * fix: change return error with match statement * chore: add unit test for deserialize * fix: fix ci error --- Cargo.lock | 1 + contracts/collection-tester/Cargo.lock | 1 + .../collection-tester/schema/query_msg.json | 27 +- contracts/collection-tester/src/contract.rs | 50 +- contracts/collection-tester/src/msg.rs | 6 +- contracts/token-tester/Cargo.lock | 1 + contracts/token-tester/schema/handle_msg.json | 10 +- contracts/token-tester/src/contract.rs | 39 +- contracts/token-tester/src/msg.rs | 2 + packages/ext/Cargo.toml | 1 + ..._collection_route_and__collection_msg.json | 8 +- packages/ext/src/collection.rs | 431 +++++++++++++++++- packages/ext/src/lib.rs | 2 +- packages/ext/src/querier_collection.rs | 167 ++++--- packages/ext/src/querier_token.rs | 60 ++- 15 files changed, 690 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fb8e2d71..00e7885de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,7 @@ dependencies = [ "cosmwasm-std", "schemars", "serde", + "serde_json", ] [[package]] diff --git a/contracts/collection-tester/Cargo.lock b/contracts/collection-tester/Cargo.lock index 541bb3efa..642fae2ce 100644 --- a/contracts/collection-tester/Cargo.lock +++ b/contracts/collection-tester/Cargo.lock @@ -159,6 +159,7 @@ dependencies = [ "cosmwasm-std", "schemars", "serde", + "serde_json", ] [[package]] diff --git a/contracts/collection-tester/schema/query_msg.json b/contracts/collection-tester/schema/query_msg.json index cd5fa8696..2ac0f0bab 100644 --- a/contracts/collection-tester/schema/query_msg.json +++ b/contracts/collection-tester/schema/query_msg.json @@ -135,10 +135,10 @@ { "type": "object", "required": [ - "get_nft" + "get_nft_count" ], "properties": { - "get_nft": { + "get_nft_count": { "type": "object", "required": [ "contract_id", @@ -262,6 +262,29 @@ } } } + }, + { + "type": "object", + "required": [ + "get_approvers" + ], + "properties": { + "get_approvers": { + "type": "object", + "required": [ + "contract_id", + "proxy" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "proxy": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } } ], "definitions": { diff --git a/contracts/collection-tester/src/contract.rs b/contracts/collection-tester/src/contract.rs index d6918280b..3c61b3c13 100644 --- a/contracts/collection-tester/src/contract.rs +++ b/contracts/collection-tester/src/contract.rs @@ -213,11 +213,11 @@ pub fn query( token_id, } => query_token(deps, contract_id, token_id), QueryMsg::GetTokens { contract_id } => query_tokens(deps, contract_id), - QueryMsg::GetNft { + QueryMsg::GetNftCount { contract_id, token_id, target, - } => query_nft(deps, contract_id, token_id, target), + } => query_nft_count(deps, contract_id, token_id, target), QueryMsg::GetTotal { contract_id, token_id, @@ -233,7 +233,8 @@ pub fn query( contract_id, proxy, approver, - } => query_approved(deps, contract_id, proxy, approver), + } => query_is_approved(deps, contract_id, proxy, approver), + QueryMsg::GetApprovers { proxy, contract_id } => query_approvers(deps, proxy, contract_id), } } @@ -1048,7 +1049,7 @@ fn query_tokens( Ok(out) } -fn query_nft( +fn query_nft_count( deps: &Extern, contract_id: String, token_id: String, @@ -1077,11 +1078,25 @@ fn query_total( target_str: String, ) -> StdResult { let target = Target::from_str(&target_str).unwrap(); - let res = LinkCollectionQuerier::new(&deps.querier) - .query_supply(contract_id, token_id, target) - .unwrap(); - let out = to_binary(&res)?; - Ok(out) + if Target::Supply == target { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_supply(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } else if Target::Mint == target { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_mint(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } else { + let res = LinkCollectionQuerier::new(&deps.querier) + .query_burn(contract_id, token_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } } fn query_root_or_parent_or_children( @@ -1123,15 +1138,28 @@ fn query_perms( Ok(out) } -fn query_approved( +fn query_is_approved( deps: &Extern, contract_id: String, proxy: HumanAddr, approver: HumanAddr, ) -> StdResult { let res = LinkCollectionQuerier::new(&deps.querier) - .query_approved(contract_id, proxy, approver) + .query_is_approved(contract_id, proxy, approver) .unwrap(); let out = to_binary(&res)?; Ok(out) } + +fn query_approvers( + deps: &Extern, + proxy: HumanAddr, + contract_id: String, +) -> StdResult { + let res = match LinkCollectionQuerier::new(&deps.querier).query_approvers(proxy, contract_id)? { + Some(approvers) => approvers, + None => return to_binary(&None::>>), + }; + let out = to_binary(&res)?; + Ok(out) +} diff --git a/contracts/collection-tester/src/msg.rs b/contracts/collection-tester/src/msg.rs index 503b7c5dd..c2c5d1eaf 100644 --- a/contracts/collection-tester/src/msg.rs +++ b/contracts/collection-tester/src/msg.rs @@ -172,7 +172,7 @@ pub enum QueryMsg { GetTokens { contract_id: String, }, - GetNft { + GetNftCount { contract_id: String, token_id: String, target: String, @@ -196,4 +196,8 @@ pub enum QueryMsg { proxy: HumanAddr, approver: HumanAddr, }, + GetApprovers { + proxy: HumanAddr, + contract_id: String, + }, } diff --git a/contracts/token-tester/Cargo.lock b/contracts/token-tester/Cargo.lock index 926b149db..b45d2f85f 100644 --- a/contracts/token-tester/Cargo.lock +++ b/contracts/token-tester/Cargo.lock @@ -145,6 +145,7 @@ dependencies = [ "cosmwasm-std", "schemars", "serde", + "serde_json", ] [[package]] diff --git a/contracts/token-tester/schema/handle_msg.json b/contracts/token-tester/schema/handle_msg.json index 69678a8ff..df2428819 100644 --- a/contracts/token-tester/schema/handle_msg.json +++ b/contracts/token-tester/schema/handle_msg.json @@ -276,14 +276,22 @@ "type": "object", "required": [ "contract_id", - "owner" + "key", + "owner", + "value" ], "properties": { "contract_id": { "type": "string" }, + "key": { + "type": "string" + }, "owner": { "$ref": "#/definitions/HumanAddr" + }, + "value": { + "type": "string" } } } diff --git a/contracts/token-tester/src/contract.rs b/contracts/token-tester/src/contract.rs index 39dca751b..b9801e729 100644 --- a/contracts/token-tester/src/contract.rs +++ b/contracts/token-tester/src/contract.rs @@ -87,7 +87,12 @@ pub fn handle( contract_id, permission, } => try_revoke_perm(deps, env, from, contract_id, permission), - HandleMsg::Modify { owner, contract_id } => try_modify(deps, env, owner, contract_id), + HandleMsg::Modify { + owner, + contract_id, + key, + value, + } => try_modify(deps, env, owner, contract_id, key, value), HandleMsg::Approve { approver, contract_id, @@ -109,7 +114,7 @@ pub fn query( QueryMsg::GetTotal { contract_id, target, - } => query_supply(deps, contract_id, target), + } => query_total(deps, contract_id, target), QueryMsg::GetPerm { contract_id, address, @@ -384,8 +389,10 @@ pub fn try_modify( _env: Env, owner: HumanAddr, contract_id: String, + key: String, + value: String, ) -> HandleResult> { - let change = Change::new("meta".to_string(), "update_token_meta".to_string()); + let change = Change::new(key, value); let msg: CosmosMsg> = LinkMsgWrapper { module: Module::Tokenencode, msg_data: MsgData { @@ -459,17 +466,31 @@ fn query_balance( Ok(out) } -fn query_supply( +fn query_total( deps: &Extern, contract_id: String, target_str: String, ) -> StdResult { let target = Target::from_str(&target_str).unwrap(); - let res = LinkTokenQuerier::new(&deps.querier) - .query_supply(contract_id, target) - .unwrap(); - let out = to_binary(&res)?; - Ok(out) + if Target::Supply == target { + let res = LinkTokenQuerier::new(&deps.querier) + .query_supply(contract_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } else if Target::Mint == target { + let res = LinkTokenQuerier::new(&deps.querier) + .query_mint(contract_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } else { + let res = LinkTokenQuerier::new(&deps.querier) + .query_burn(contract_id) + .unwrap(); + let out = to_binary(&res)?; + Ok(out) + } } fn query_perm( diff --git a/contracts/token-tester/src/msg.rs b/contracts/token-tester/src/msg.rs index d1732c004..976aebe7b 100644 --- a/contracts/token-tester/src/msg.rs +++ b/contracts/token-tester/src/msg.rs @@ -64,6 +64,8 @@ pub enum HandleMsg { Modify { owner: HumanAddr, contract_id: String, + key: String, + value: String, }, Approve { approver: HumanAddr, diff --git a/packages/ext/Cargo.toml b/packages/ext/Cargo.toml index 080179337..6014becef 100644 --- a/packages/ext/Cargo.toml +++ b/packages/ext/Cargo.toml @@ -12,6 +12,7 @@ readme = "README.md" cosmwasm-std = { path = "../std", version = "0.10.0" } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } +serde_json = "1.0" [dev-dependencies] cosmwasm-schema = { path = "../schema" } diff --git a/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json b/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json index bdb9c46d8..779c60028 100644 --- a/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json +++ b/packages/ext/schema/link_msg_wrapper_for__collection_route_and__collection_msg.json @@ -670,10 +670,10 @@ }, "permission": { "enum": [ - "Mint", - "Burn", - "Issue", - "Modify" + "mint", + "burn", + "issue", + "modify" ] } } diff --git a/packages/ext/src/collection.rs b/packages/ext/src/collection.rs index 329413d1b..c086e48a9 100644 --- a/packages/ext/src/collection.rs +++ b/packages/ext/src/collection.rs @@ -1,5 +1,8 @@ use schemars::JsonSchema; +use serde::de::{Deserialize as deDeserialize, Deserializer, MapAccess, Visitor}; use serde::{Deserialize, Serialize}; +use std::fmt; + use std::str::FromStr; use cosmwasm_std::{HumanAddr, Uint128}; @@ -12,21 +15,28 @@ pub struct Collection { pub base_img_uri: String, } +#[derive(Serialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(untagged)] +pub enum Token { + FT(FungibleToken), + NFT(NonFungibleToken), +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct NonFungibleToken { +pub struct FungibleToken { pub contract_id: String, pub token_id: String, - pub owner: HumanAddr, + pub decimals: Uint128, + pub mintable: bool, pub name: String, pub meta: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct FungibleToken { +pub struct NonFungibleToken { pub contract_id: String, pub token_id: String, - pub decimals: Uint128, - pub mintable: bool, + pub owner: HumanAddr, pub name: String, pub meta: String, } @@ -70,6 +80,7 @@ impl MintNFTParam { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename = "permission")] +#[serde(rename_all = "snake_case")] pub enum CollectionPerm { Mint, Burn, @@ -89,3 +100,413 @@ impl FromStr for CollectionPerm { } } } + +const FIELDS: &[&str] = &[ + "contract_id", + "token_id", + "name", + "meta", + "decimals", + "mintable", + "owner", +]; + +#[derive(Serialize)] +#[serde(rename_all = "snake_case")] +enum Field { + ContractId, + TokenId, + Name, + Meta, + Decimals, + Mintable, + Owner, +} + +struct FieldVisitor; + +impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&format!( + "`{}` or `{}` or `{}` or `{}` or `{}` or `{}` or `{}`", + FIELDS[0], FIELDS[1], FIELDS[2], FIELDS[3], FIELDS[4], FIELDS[5], FIELDS[6] + )) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v { + "contract_id" => Ok(Field::ContractId), + "token_id" => Ok(Field::TokenId), + "name" => Ok(Field::Name), + "meta" => Ok(Field::Meta), + "decimals" => Ok(Field::Decimals), + "mintable" => Ok(Field::Mintable), + "owner" => Ok(Field::Owner), + _ => Err(serde::de::Error::unknown_field(v, FIELDS)), + } + } +} + +impl<'de> deDeserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_identifier(FieldVisitor) + } +} + +struct TokenVisitor; + +impl<'de> Visitor<'de> for TokenVisitor { + type Value = Token; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Token") + } + + fn visit_map(self, mut map: A) -> Result>::Error> + where + A: MapAccess<'de>, + { + let mut contract_id = None; + let mut token_id = None; + let mut name = None; + let mut meta = None; + let mut decimals_str = None; + let mut mintable = None; + let mut owner = None; + + while let Some(key) = map.next_key::()? { + match key { + Field::ContractId => { + if contract_id.is_some() { + return Err(serde::de::Error::duplicate_field(FIELDS[0])); + } + contract_id = Some(map.next_value::()?); + } + Field::TokenId => { + if token_id.is_some() { + return Err(serde::de::Error::duplicate_field(FIELDS[1])); + } + token_id = Some(map.next_value::()?); + } + Field::Name => { + if name.is_some() { + return Err(serde::de::Error::duplicate_field(FIELDS[2])); + } + name = Some(map.next_value::()?); + } + Field::Meta => { + if meta.is_some() { + return Err(serde::de::Error::duplicate_field(FIELDS[3])); + } + meta = Some(map.next_value::()?); + } + Field::Decimals => { + if decimals_str.is_some() { + return Err(serde::de::Error::duplicate_field(FIELDS[4])); + } + decimals_str = Some(map.next_value::()?); + } + Field::Mintable => { + if mintable.is_some() { + return Err(serde::de::Error::duplicate_field(FIELDS[5])); + } + mintable = Some(map.next_value::()?); + } + Field::Owner => { + if owner.is_some() { + return Err(serde::de::Error::duplicate_field(FIELDS[6])); + } + owner = Some(map.next_value::()?); + } + } + } + + let res: Result = match ( + contract_id, + token_id, + name, + meta, + decimals_str, + mintable, + owner, + ) { + ( + Some(contract_id), + Some(token_id), + Some(name), + Some(meta), + Some(decimals_str), + Some(mintable), + None, + ) => { + let decimals = (&decimals_str) + .parse::() + .unwrap_or_else(|e| panic!(e)); + Ok(Token::FT(FungibleToken { + contract_id, + token_id, + name, + meta, + decimals: Uint128::from(decimals), + mintable, + })) + } + ( + Some(contract_id), + Some(token_id), + Some(name), + Some(meta), + None, + None, + Some(owner), + ) => Ok(Token::NFT(NonFungibleToken { + contract_id, + token_id, + name, + meta, + owner: HumanAddr::from(owner), + })), + _ => Err(serde::de::Error::missing_field( + "The fields required to deserialize to FT or NFT are missing", + )), + }; + res + } +} + +impl<'de> deDeserialize<'de> for Token { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_struct("Token", FIELDS, TokenVisitor) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::query::Response; + + #[test] + fn test_ft_and_nft_deserialize() { + let json = r#" + [ + { + "type": "collection/FT", + "value": { + "contract_id": "9be17165", + "token_id": "0000000100000000", + "decimals": "18", + "mintable": true, + "name": "ft_test_name-1", + "meta": "" + } + }, + { + "type": "collection/NFT", + "value": { + "contract_id": "9be17165", + "token_id": "1000000100000001", + "owner": "link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu", + "name": "nft-0", + "meta": "" + } + } + ] + "#; + + let res = serde_json::from_str::>>(json); + assert!(res.is_ok()); + let tokens = res.unwrap(); + let ft = tokens[0].clone(); + let nft = tokens[1].clone(); + + assert_eq!( + ft, + Response { + key: "collection/FT".to_string(), + value: Token::FT(FungibleToken { + contract_id: "9be17165".to_string(), + token_id: "0000000100000000".to_string(), + name: "ft_test_name-1".to_string(), + meta: "".to_string(), + decimals: Uint128(18), + mintable: true, + }) + } + ); + assert_eq!( + nft, + Response { + key: "collection/NFT".to_string(), + value: Token::NFT(NonFungibleToken { + contract_id: "9be17165".to_string(), + token_id: "1000000100000001".to_string(), + name: "nft-0".to_string(), + meta: "".to_string(), + owner: HumanAddr::from("link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu"), + }) + } + ) + } + + #[test] + fn test_fts_deserialize() { + let json = r#" + [ + { + "type": "collection/FT", + "value": { + "contract_id": "9be17165", + "token_id": "0000000100000000", + "decimals": "18", + "mintable": true, + "name": "ft_test_name-1", + "meta": "" + } + }, + { + "type": "collection/FT", + "value": { + "contract_id": "9be17165", + "token_id": "0000000100000001", + "decimals": "8", + "mintable": false, + "name": "ft_test_name-2", + "meta": "meta" + + } + } + ] + "#; + + let res = serde_json::from_str::>>(json); + assert!(res.is_ok()); + let tokens = res.unwrap(); + let ft1 = tokens[0].clone(); + let ft2 = tokens[1].clone(); + + assert_eq!( + ft1, + Response { + key: "collection/FT".to_string(), + value: Token::FT(FungibleToken { + contract_id: "9be17165".to_string(), + token_id: "0000000100000000".to_string(), + name: "ft_test_name-1".to_string(), + meta: "".to_string(), + decimals: Uint128(18), + mintable: true, + }) + } + ); + assert_eq!( + ft2, + Response { + key: "collection/FT".to_string(), + value: Token::FT(FungibleToken { + contract_id: "9be17165".to_string(), + token_id: "0000000100000001".to_string(), + name: "ft_test_name-2".to_string(), + meta: "meta".to_string(), + decimals: Uint128(8), + mintable: false, + }) + } + ) + } + + #[test] + fn test_nfts_deserialize() { + let json = r#" + [ + { + "type": "collection/NFT", + "value": { + "contract_id": "9be17165", + "token_id": "1000000100000001", + "owner": "link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu", + "name": "nft-0", + "meta": "" + } + }, + { + "type": "collection/NFT", + "value": { + "contract_id": "9be17165", + "token_id": "1000000100000002", + "owner": "link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu", + "name": "nft-1", + "meta": "" + } + } + ] + "#; + + let res = serde_json::from_str::>>(json); + assert!(res.is_ok()); + let tokens = res.unwrap(); + let nft1 = tokens[0].clone(); + let nft2 = tokens[1].clone(); + + assert_eq!( + nft1, + Response { + key: "collection/NFT".to_string(), + value: Token::NFT(NonFungibleToken { + contract_id: "9be17165".to_string(), + token_id: "1000000100000001".to_string(), + name: "nft-0".to_string(), + meta: "".to_string(), + owner: HumanAddr::from("link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu"), + }) + } + ); + assert_eq!( + nft2, + Response { + key: "collection/NFT".to_string(), + value: Token::NFT(NonFungibleToken { + contract_id: "9be17165".to_string(), + token_id: "1000000100000002".to_string(), + name: "nft-1".to_string(), + meta: "".to_string(), + owner: HumanAddr::from("link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu"), + }) + } + ) + } + + #[test] + fn test_invalid_token_deserialize() { + let json = r#" + [ + { + "type": "collection/FT", + "value": { + "contract_id": "9be17165", + "token_id": "0000000100000000", + "decimals": "18", + "mintable": true, + "name": "ft_test_name-1", + "meta": "" + "owner": "link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu", + } + } + ] + "#; + + let res = serde_json::from_str::>>(json); + assert!(!res.is_ok()); + } +} diff --git a/packages/ext/src/lib.rs b/packages/ext/src/lib.rs index 01cc05b19..76e0641b4 100644 --- a/packages/ext/src/lib.rs +++ b/packages/ext/src/lib.rs @@ -8,7 +8,7 @@ mod query; mod token; pub use collection::{ - Coin, Collection, CollectionPerm, FungibleToken, MintNFTParam, NonFungibleToken, TokenType, + Coin, Collection, CollectionPerm, MintNFTParam, Token as CollectionToken, TokenType, }; pub use msg::{Change, LinkMsgWrapper, Module, MsgData}; pub use msg_collection::{CollectionMsg, CollectionRoute}; diff --git a/packages/ext/src/querier_collection.rs b/packages/ext/src/querier_collection.rs index d2e804597..eb503a0a9 100644 --- a/packages/ext/src/querier_collection.rs +++ b/packages/ext/src/querier_collection.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{HumanAddr, Querier, StdResult, Uint128}; -use crate::collection::{Collection, CollectionPerm, FungibleToken, NonFungibleToken, TokenType}; -use crate::query::{LinkQueryWrapper, Module, QueryData, Response, Target}; +use crate::collection::{Collection, CollectionPerm, Token, TokenType}; +use crate::query::{LinkQueryWrapper, Module, QueryData, Response}; pub struct LinkCollectionQuerier<'a, Q: Querier> { querier: &'a Q, @@ -29,53 +29,57 @@ pub enum CollectionQueryRoute { Parent, Children, Approved, + Approver, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CollectionQuery { - QueryCollectionParam { + CollectionParam { contract_id: String, }, - QueryBalanceParam { + BalanceParam { contract_id: String, token_id: String, addr: HumanAddr, }, - QueryTokentypesParam { + TokentypesParam { contract_id: String, token_id: String, }, - QueryTokensParam { + TokensParam { contract_id: String, token_id: String, }, - QueryTotalParam { + TotalParam { contract_id: String, token_id: String, - target: Target, }, - QueryPermParam { + PermParam { contract_id: String, address: HumanAddr, }, - QueryParentParam { + ParentParam { contract_id: String, token_id: String, }, - QueryRootParam { + RootParam { contract_id: String, token_id: String, }, - QueryChildrenParam { + ChildrenParam { contract_id: String, token_id: String, }, - QueryApprovedParam { + IsApprovedParam { contract_id: String, proxy: HumanAddr, approver: HumanAddr, }, + ApproversParam { + contract_id: String, + proxy: HumanAddr, + }, } impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { @@ -88,7 +92,7 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Collections, - data: CollectionQuery::QueryCollectionParam { contract_id }, + data: CollectionQuery::CollectionParam { contract_id }, }, }; @@ -102,11 +106,11 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { token_id: String, addr: HumanAddr, ) -> StdResult { - let request = LinkQueryWrapper { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Balance, - data: CollectionQuery::QueryBalanceParam { + data: CollectionQuery::BalanceParam { contract_id, token_id, addr, @@ -123,11 +127,11 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { contract_id: String, token_id: String, ) -> StdResult> { - let request = LinkQueryWrapper { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Tokentypes, - data: CollectionQuery::QueryTokentypesParam { + data: CollectionQuery::TokentypesParam { contract_id, token_id, }, @@ -139,11 +143,11 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { } pub fn query_token_types(&self, contract_id: String) -> StdResult>> { - let request = LinkQueryWrapper { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Tokentypes, - data: CollectionQuery::QueryTokentypesParam { + data: CollectionQuery::TokentypesParam { contract_id, token_id: "".to_string(), }, @@ -154,16 +158,12 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { Ok(res) } - pub fn query_token( - &self, - contract_id: String, - token_id: String, - ) -> StdResult> { - let request = LinkQueryWrapper { + pub fn query_token(&self, contract_id: String, token_id: String) -> StdResult> { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Tokens, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id, }, @@ -174,12 +174,12 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { Ok(res) } - pub fn query_tokens(&self, contract_id: String) -> StdResult>> { - let request = LinkQueryWrapper { + pub fn query_tokens(&self, contract_id: String) -> StdResult>> { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Tokens, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id: "".to_string(), }, @@ -191,11 +191,11 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { } pub fn query_nft_count(&self, contract_id: String, token_id: String) -> StdResult { - let request = LinkQueryWrapper { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Nftcount, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id, }, @@ -207,11 +207,11 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { } pub fn query_nft_mint(&self, contract_id: String, token_id: String) -> StdResult { - let request = LinkQueryWrapper { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Nftmint, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id, }, @@ -223,11 +223,11 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { } pub fn query_nft_burn(&self, contract_id: String, token_id: String) -> StdResult { - let request = LinkQueryWrapper { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Nftburn, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id, }, @@ -238,20 +238,46 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { Ok(res) } - pub fn query_supply( - &self, - contract_id: String, - token_id: String, - target: Target, - ) -> StdResult { - let request = LinkQueryWrapper { + pub fn query_supply(&self, contract_id: String, token_id: String) -> StdResult { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Supply, - data: CollectionQuery::QueryTotalParam { + data: CollectionQuery::TotalParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_mint(&self, contract_id: String, token_id: String) -> StdResult { + let request = LinkQueryWrapper:: { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Mint, + data: CollectionQuery::TotalParam { + contract_id, + token_id, + }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_burn(&self, contract_id: String, token_id: String) -> StdResult { + let request = LinkQueryWrapper:: { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Burn, + data: CollectionQuery::TotalParam { contract_id, token_id, - target, }, }, }; @@ -264,12 +290,12 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { &self, contract_id: String, token_id: String, - ) -> StdResult> { - let request = LinkQueryWrapper { + ) -> StdResult> { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Parent, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id, }, @@ -280,16 +306,12 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { Ok(res) } - pub fn query_root( - &self, - contract_id: String, - token_id: String, - ) -> StdResult> { - let request = LinkQueryWrapper { + pub fn query_root(&self, contract_id: String, token_id: String) -> StdResult> { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Root, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id, }, @@ -304,12 +326,12 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { &self, contract_id: String, token_id: String, - ) -> StdResult>> { - let request = LinkQueryWrapper { + ) -> StdResult>> { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Children, - data: CollectionQuery::QueryTokensParam { + data: CollectionQuery::TokensParam { contract_id, token_id, }, @@ -325,11 +347,11 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { contract_id: String, address: HumanAddr, ) -> StdResult>> { - let request = LinkQueryWrapper { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Perms, - data: CollectionQuery::QueryPermParam { + data: CollectionQuery::PermParam { contract_id, address, }, @@ -340,17 +362,17 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { Ok(res) } - pub fn query_approved( + pub fn query_is_approved( &self, contract_id: String, proxy: HumanAddr, approver: HumanAddr, - ) -> StdResult>> { - let request = LinkQueryWrapper { + ) -> StdResult { + let request = LinkQueryWrapper:: { module: Module::Collectionencode, query_data: QueryData { route: CollectionQueryRoute::Approved, - data: CollectionQuery::QueryApprovedParam { + data: CollectionQuery::IsApprovedParam { contract_id, proxy, approver, @@ -361,4 +383,21 @@ impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { let res = self.querier.custom_query(&request.into())?; Ok(res) } + + pub fn query_approvers( + &self, + proxy: HumanAddr, + contract_id: String, + ) -> StdResult>> { + let request = LinkQueryWrapper:: { + module: Module::Collectionencode, + query_data: QueryData { + route: CollectionQueryRoute::Approver, + data: CollectionQuery::ApproversParam { proxy, contract_id }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } } diff --git a/packages/ext/src/querier_token.rs b/packages/ext/src/querier_token.rs index dee2951ab..71f7cb7e0 100644 --- a/packages/ext/src/querier_token.rs +++ b/packages/ext/src/querier_token.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{HumanAddr, Querier, StdResult, Uint128}; -use crate::query::{LinkQueryWrapper, Module, QueryData, Response, Target}; +use crate::query::{LinkQueryWrapper, Module, QueryData, Response}; use crate::token::{Token, TokenPerm}; pub struct LinkTokenQuerier<'a, Q: Querier> { @@ -16,6 +16,8 @@ pub enum TokenQueryRoute { Tokens, Balance, Supply, + Mint, + Burn, Perms, Approved, Approvers, @@ -24,27 +26,26 @@ pub enum TokenQueryRoute { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TokenQuery { - QueryTokenParam { + TokenParam { contract_id: String, }, - QueryBalanceParam { + BalanceParam { contract_id: String, address: HumanAddr, }, - QueryTotalParam { + TotalParam { contract_id: String, - target: Target, }, - QueryPermParam { + PermParam { contract_id: String, address: HumanAddr, }, - QueryIsApprovedParam { + IsApprovedParam { proxy: HumanAddr, contract_id: String, approver: HumanAddr, }, - QueryApproversParam { + ApproversParam { proxy: HumanAddr, contract_id: String, }, @@ -60,7 +61,7 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { module: Module::Tokenencode, query_data: QueryData { route: TokenQueryRoute::Tokens, - data: TokenQuery::QueryTokenParam { contract_id }, + data: TokenQuery::TokenParam { contract_id }, }, }; @@ -73,7 +74,7 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { module: Module::Tokenencode, query_data: QueryData { route: TokenQueryRoute::Balance, - data: TokenQuery::QueryBalanceParam { + data: TokenQuery::BalanceParam { contract_id, address, }, @@ -84,15 +85,38 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { Ok(res) } - pub fn query_supply(&self, contract_id: String, target: Target) -> StdResult { + pub fn query_supply(&self, contract_id: String) -> StdResult { let request = LinkQueryWrapper:: { module: Module::Tokenencode, query_data: QueryData { route: TokenQueryRoute::Supply, - data: TokenQuery::QueryTotalParam { - contract_id, - target, - }, + data: TokenQuery::TotalParam { contract_id }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_mint(&self, contract_id: String) -> StdResult { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Mint, + data: TokenQuery::TotalParam { contract_id }, + }, + }; + + let res = self.querier.custom_query(&request.into())?; + Ok(res) + } + + pub fn query_burn(&self, contract_id: String) -> StdResult { + let request = LinkQueryWrapper:: { + module: Module::Tokenencode, + query_data: QueryData { + route: TokenQueryRoute::Burn, + data: TokenQuery::TotalParam { contract_id }, }, }; @@ -109,7 +133,7 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { module: Module::Tokenencode, query_data: QueryData { route: TokenQueryRoute::Perms, - data: TokenQuery::QueryPermParam { + data: TokenQuery::PermParam { contract_id, address, }, @@ -130,7 +154,7 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { module: Module::Tokenencode, query_data: QueryData { route: TokenQueryRoute::Approved, - data: TokenQuery::QueryIsApprovedParam { + data: TokenQuery::IsApprovedParam { proxy, contract_id, approver, @@ -151,7 +175,7 @@ impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { module: Module::Tokenencode, query_data: QueryData { route: TokenQueryRoute::Approvers, - data: TokenQuery::QueryApproversParam { proxy, contract_id }, + data: TokenQuery::ApproversParam { proxy, contract_id }, }, }; From 1e74158eb89a4b0d27bb48c8744805cfa448cec4 Mon Sep 17 00:00:00 2001 From: loloicci Date: Tue, 5 Jan 2021 13:21:21 +0900 Subject: [PATCH 08/12] Merging 0.12 (#39) * Upgrade parity-wasm * Update contract lock files * Use the same contract version for borner as the rest of the contracts * Set version: 0.11.0-alpha3 * Implement From for Vec * Implement From for Vec * Add missing newlines * Add Binary::into_vec and CanonicalAddr::into_vec * Remove #[inline] attributes * Add riffle_shuffle * Remove canonical_length parameter * Remove the ability to change canonical length in VM's MockApi * Use riffle_shuffle in std's MockApi * Add simulation script * Use riffle_shuffle in vm's MockApi * Add left rotate by digit sum * Add CHANGELOG/MIGRATING entries * Update testing contract * Move MessageInfo outside of Env * Update do_init/handle/... exports * Update entry_points macro * Update burner contract * Fix VM calls for extra arg, burner integration tests pass * Hackatom builds properly * Fix tests for hackatom * Fixed all tests in vm package * Updated queue contract * Fix various test failures from CI * Update hackatom contract * Fix hackatom singlepass * Update staking contract * Fixed last test * Update CHANGELOG and README * Cleanup from PR review * Rebuild test contract, update gas prices * Fixed up merge conflict * Set version: 0.11.0-alpha4 * Remove unnecessary 'static restriction from Storage and Querier * Remove obsolete Instance::from_wasmer * Add Querier helpers query_wasm_{smart,raw} * Add special raw query to reflect contract * Simplify RawResponse * Sort imports in traits.rs * Add Deref to some common types * Test to show from_slice and from_binary both work on Binary now * Use &canonical instead of canonical.as_slice() some places * Delegate is_empty, len, into_vec into Deref target * Sort imports * Implement PartialEq for HumanAddr * Document and test impl Deref for HumanAddr * Document and test impl Deref for CanonicalAddr * Document and test impl Deref for Binary * Set version: 0.11.0 * Test Hash and HashSet for HumanAddr * Allow using Binary in HashSet and HashMap * Allow using CanonicalAddr in HashSet and HashMap * Add a Sum helper for Uint128 * Sort imports * Implement Add for Uint128 with reference right hand side * Implement AddAssign with reference rhs for Uint128 * Use checked_sub to implement Sub for Uint128 * Implement Sub for Uint128 with reference rhs * Implement sum with reference rhs using add * Cleanup error result test * Make source type in Sum explicit * Add CHANGELOG entries * Pull out fn deserialize * Test some properties of deserialized module * Fix module name: compatability -> compatibility * Set version: 0.11.1 * Add FfiError::IteratorDoesNotExist * Replace range with scan/next in Storage trait * Update MockStorage to new trait * Use with_storage_from_context in do_next * Remove with_iterator_from_context * Remove VmError::IteratorDoesNotExist * Remove iterators from ContextData * Implement AddAssign for GasInfo * Handle errors in self.next perperly * Adapt burner integration tests to new storage * Add note on iterator IDs * Remove StorageIterator and MockIterator * Add CHANGELOG entries * Remove outdated doc comment * Remove StdError::Unauthorized * Use thiserror instead of snafu in std * Roll out all features in CI testing, remove backtraces feature * Convert FfiError to thiserror * Convert CommunicationError from snafu to thiserror * Convert RegionValidationError from snafu to thiserror * Convert VmError from snafu to thiserror * Remove snafu dependency from storage * Remove the snafu dependency from cosmwasm-vm * Create new contract_0.12.wasm development contract * Upgrade to rust-optimizer:0.10.4 for all dev contracts * Implement From and From * Add a few more explicit result types * Rename `Extern` to `Deps` * Generalize denom argument in Coin constructors * Remove change_querier * Add ExternMut ExternRef types * Update hackatom with ExternRef ExternMut * Remove some generic type parameters * Simplify hackatom contract * Remove Q generic from ExternRef/Mut * Update entry points for hackatom, integration tests pass * Port burner contract * Port queue * Start updating storage-bucket * Bucket works with dyn trait object * Singleton and Sequence work with trait objects * Update reflect contract * Fixed iterator support in bucket * Update staking contract * Improve naming and docs for &dyn ReadonlyStorage cast * Format migrating.md * Rename Deps* types, add Copy to DepsRef * Update contracts for renaming * Rename Querier types, for cleaner public Api * Revert "Rename Querier types, for cleaner public Api" This reverts commit a7efaa34900b598088d3246e41a5e789f17d0e64. * Deps/DepsRef -> DepsMut/Deps * Implement PartialEq between Binary and Vec/&[u8] * Add missing PartialEq implementations between HumanAddr and str/&str * Compare Binary and Vec in hackatom * Rename module encoding -> binary * Sort imports * Add Binary::to_array * Document length limit of Binary::to_array * Ensure Binary::to_array cannot be used with Vec * Update vm test contract, fix typo * Let Binary::to_array compile with Rust lower than 1.47 * Remove TypedStorage and PrefixedStorage from the cosmwasm-storage * Remove ReadonlyStorage from cosmwasm-std and -storage * Remove ReadonlyStorage from sample contracts * Update CHANGELOG and MIGRATING * Set version: 0.11.2 * Bring back PrefixedStorage/ReadonlyPrefixedStorageage * Set publish = false consistently * Add Debug support for MemoryStorage * Pull out InstanceOptions * Make `FileSystemCache` crate internal * Convert modules from file to folder * Remove obsolete load_with_backend * Remove backend function in favour of BACKEND_NAME constant * Fix FileSystemCache::load return type * Use fresh temp dir for each run in test_file_system_cache_run * Rename to fs_cache and hits_fs_cache * Replace wabt with wat, which is written in pure Rust * Add Size type * Create InMemoryCache * Let FileSystemCache::store take a module reference only * Integrate memory cache * Ensure save_wasm does not write to memory cache * Rename CosmCache to Cache * Extract CacheOptions and make memory limit configurable * Rename `Extern` to `Backend` * Rename mock_dependencies -> mock_backend * Rename FfiError -> BackendError * Merge ffi and traits module into backend * Add panic when setting an empty value * Re-compile testing contract * Set version: 0.12.0-alpha1 * Allow constructing CacheOptions crate external * Set version: 0.12.0-alpha2 * Replace {Deps,DepsRef} with {Deps,DepsMut} * Add helper so handler can call other handler * Address concerns from PR review * Show users how to properly set dependencies in migration guide * Remove StorageTransaction and friends * Add LimitedDisplay and implement for HashSet * Implement LimitedDisplay for BTreeSet * Use to_string_limited for features * Cleanup error matching * Show all required imports in error message * Make imports test independent of iterator feature * Add compatibility table * Pull out collection_to_string_limited and implement for vector * Explain why sorting is needed * Test BTreeSet::to_string_limited * Set version: 0.12.0-alpha3 * Upgarde rust-optimizer to 0.10.5 * Set version: 0.12.0 * fix: fix package/ext for upstream v0.12.0 * fix: replace log with attributes in collection-tester It is to follow v0.12.0 * fix: replace Extern<...> with Deps[Mut] in collection_tester It is to follow v0.12.0. * fix: Insert arg MessageInfo in handle functions in collection_tester It is to follow v0.12.0. * fix: Remove unused imports * fix: fix the type of functions in collection_tester/src/state.rs It is to follow v0.12.0 * fix: fix collection-tester/lib.rs to use macro * fix: delete unneeded & for querier Now, querier is QuerierWrapper, so the & is unneeded * fix: insert a arg Env to query function It is to follow v0.12.0. * fix: replace env as an arg of init with _env * fix: version up and update Cargo.* for collection-tester * fix: replace log with attr in token-tester It is to follow v0.12.0. * fix: replace Extern<...> with Deps[Mut] in token_tester It is to follow v0.12.0. * fix: fix the type of functions in collection_tester/src/state.rs It is to follow v0.12.0 * fix: remove unused imports * fix: delete unneeded & for querier Now, querier is QuerierWrapper, so the & is unneeded * fix: change the arg of config and config_read to follow the change of state.rs It is to follow v0.12.0 * fix: fix init of token-tester to use MessageInfo It is to follow v0.12.0. * fix: add env to args of query functions in token-tester It is to follow v0.12.0. * fix: fix token-tester/lib.rs to use macro It is to follow v0.12.0. * fix: fix test in token-tester to follow v0.12.0 * fix: versionup token-tester contract * fix: execute cargo fmt * fix: suppress clippy error in contracts by too_many_arguments * fix: execute cargo fmt Co-authored-by: Simon Warta Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Ethan Frey --- .circleci/config.yml | 99 ++- .editorconfig | 3 + .gitignore | 3 +- CHANGELOG.md | 169 ++++++ Cargo.lock | 180 +++--- MIGRATING.md | 366 +++++++++++- README.md | 51 +- contracts/README.md | 24 +- contracts/burner/.cargo/config | 2 +- contracts/burner/Cargo.lock | 94 +-- contracts/burner/Cargo.toml | 10 +- contracts/burner/src/contract.rs | 42 +- contracts/burner/tests/integration.rs | 44 +- contracts/collection-tester/Cargo.lock | 85 +-- contracts/collection-tester/Cargo.toml | 2 +- contracts/collection-tester/src/contract.rs | 356 +++++------ contracts/collection-tester/src/lib.rs | 36 +- contracts/collection-tester/src/state.rs | 4 +- contracts/hackatom/.cargo/config | 2 +- contracts/hackatom/Cargo.lock | 82 +-- contracts/hackatom/Cargo.toml | 3 +- contracts/hackatom/schema/query_msg.json | 6 +- contracts/hackatom/src/contract.rs | 246 ++++---- contracts/hackatom/src/errors.rs | 12 + contracts/hackatom/src/lib.rs | 1 + contracts/hackatom/tests/integration.rs | 153 ++--- contracts/queue/.cargo/config | 2 +- contracts/queue/Cargo.lock | 261 ++++---- contracts/queue/Cargo.toml | 2 +- contracts/queue/examples/schema.rs | 5 +- contracts/queue/schema/list_response.json | 39 ++ contracts/queue/schema/query_msg.json | 11 + contracts/queue/src/contract.rs | 247 ++++++-- contracts/queue/tests/integration.rs | 150 +++-- contracts/reflect/.cargo/config | 2 +- contracts/reflect/Cargo.lock | 82 +-- contracts/reflect/Cargo.toml | 11 +- contracts/reflect/examples/schema.rs | 10 +- .../reflect/schema/capitalized_response.json | 13 + contracts/reflect/schema/chain_response.json | 19 + .../handle_response_for__custom_msg.json | 46 +- contracts/reflect/schema/query_msg.json | 325 +++++++++- contracts/reflect/schema/raw_response.json | 24 + contracts/reflect/src/contract.rs | 291 ++++++--- contracts/reflect/src/errors.rs | 17 + contracts/reflect/src/lib.rs | 1 + contracts/reflect/src/msg.rs | 56 +- contracts/reflect/src/state.rs | 4 +- contracts/reflect/src/testing.rs | 40 +- contracts/reflect/tests/integration.rs | 88 ++- contracts/staking/.cargo/config | 2 +- contracts/staking/Cargo.lock | 54 +- contracts/staking/Cargo.toml | 11 +- contracts/staking/src/contract.rs | 345 +++++------ contracts/staking/src/errors.rs | 18 + contracts/staking/src/lib.rs | 1 + contracts/staking/src/state.rs | 30 +- contracts/staking/tests/integration.rs | 42 +- contracts/token-tester/Cargo.lock | 85 +-- contracts/token-tester/Cargo.toml | 2 +- contracts/token-tester/src/contract.rs | 200 ++++--- contracts/token-tester/src/lib.rs | 36 +- contracts/token-tester/src/state.rs | 4 +- devtools/set_version.sh | 8 +- docs/simulate_riffle_shuffle.py | 57 ++ packages/ext/Cargo.toml | 2 +- packages/ext/src/querier_collection.rs | 10 +- packages/ext/src/querier_token.rs | 10 +- packages/ext/src/query.rs | 18 +- packages/schema/Cargo.toml | 2 +- packages/std/.cargo/config | 2 +- packages/std/Cargo.toml | 9 +- packages/std/examples/schema.rs | 9 +- ...os_msg_for__empty.json => cosmos_msg.json} | 2 +- packages/std/schema/env.json | 55 +- packages/std/schema/handle_result.json | 544 ----------------- packages/std/schema/init_result.json | 534 ----------------- packages/std/schema/message_info.json | 49 ++ packages/std/schema/migrate_result.json | 544 ----------------- packages/std/schema/query_result.json | 199 ------ packages/std/src/addresses.rs | 351 ++++++++++- packages/std/src/{encoding.rs => binary.rs} | 263 +++++++- packages/std/src/coins.rs | 109 +++- packages/std/src/deps.rs | 88 +++ packages/std/src/entry_points.rs | 62 +- packages/std/src/errors/mod.rs | 2 +- packages/std/src/errors/std_error.rs | 374 ++++-------- packages/std/src/errors/system_error.rs | 6 +- packages/std/src/exports.rs | 234 +++++--- packages/std/src/imports.rs | 89 +-- packages/std/src/init_handle.rs | 416 ------------- packages/std/src/lib.rs | 31 +- packages/std/src/math.rs | 95 ++- packages/std/src/memory.rs | 18 +- packages/std/src/mock.rs | 249 +++++--- packages/std/src/query.rs | 52 +- packages/std/src/results/attribute.rs | 39 ++ packages/std/src/results/context.rs | 178 ++++++ packages/std/src/results/contract_result.rs | 165 +++++ packages/std/src/results/cosmos_msg.rs | 125 ++++ packages/std/src/results/handle.rs | 36 ++ packages/std/src/results/init.rs | 60 ++ packages/std/src/results/migrate.rs | 36 ++ packages/std/src/results/mod.rs | 21 + packages/std/src/results/query.rs | 6 + packages/std/src/results/system_result.rs | 76 +++ packages/std/src/serde.rs | 14 +- packages/std/src/storage.rs | 108 +++- packages/std/src/traits.rs | 176 ++++-- packages/std/src/types.rs | 54 +- packages/storage/Cargo.toml | 7 +- packages/storage/README.md | 2 +- packages/storage/src/bucket.rs | 138 +++-- packages/storage/src/lib.rs | 4 - packages/storage/src/namespace_helpers.rs | 16 +- packages/storage/src/prefixed_storage.rs | 156 ++--- packages/storage/src/sequence.rs | 6 +- packages/storage/src/singleton.rs | 113 ++-- packages/storage/src/transactions.rs | 565 ------------------ packages/storage/src/typed.rs | 330 ---------- packages/vm/Cargo.toml | 15 +- packages/vm/README.md | 18 +- packages/vm/src/backend.rs | 307 ++++++++++ packages/vm/src/backends/cranelift.rs | 6 +- packages/vm/src/backends/mod.rs | 11 +- packages/vm/src/backends/singlepass.rs | 13 +- packages/vm/src/cache.rs | 338 +++++++---- packages/vm/src/calls.rs | 80 ++- .../{compatability.rs => compatibility.rs} | 213 +++++-- packages/vm/src/context.rs | 176 +----- packages/vm/src/errors/communication_error.rs | 69 +-- .../vm/src/errors/region_validation_error.rs | 34 +- packages/vm/src/errors/vm_error.rs | 180 ++---- packages/vm/src/features.rs | 5 +- packages/vm/src/ffi.rs | 199 ------ packages/vm/src/imports.rs | 128 ++-- packages/vm/src/instance.rs | 146 +++-- packages/vm/src/lib.rs | 19 +- packages/vm/src/limited.rs | 226 +++++++ packages/vm/src/middleware/deterministic.rs | 5 +- .../file_system_cache.rs} | 84 +-- packages/vm/src/modules/in_memory_cache.rs | 81 +++ packages/vm/src/modules/mod.rs | 5 + packages/vm/src/size.rs | 71 +++ packages/vm/src/testing/calls.rs | 42 +- packages/vm/src/testing/instance.rs | 41 +- packages/vm/src/testing/mock.rs | 167 +++--- packages/vm/src/testing/mod.rs | 9 +- packages/vm/src/testing/querier.rs | 30 +- packages/vm/src/testing/storage.rs | 209 ++++--- packages/vm/src/traits.rs | 125 ---- packages/vm/testdata/contract.wasm | 2 +- packages/vm/testdata/contract_0.11.wasm | Bin 0 -> 170947 bytes packages/vm/testdata/contract_0.12.wasm | Bin 0 -> 182254 bytes 154 files changed, 7405 insertions(+), 7224 deletions(-) create mode 100644 contracts/hackatom/src/errors.rs create mode 100644 contracts/queue/schema/list_response.json create mode 100644 contracts/reflect/schema/capitalized_response.json create mode 100644 contracts/reflect/schema/chain_response.json create mode 100644 contracts/reflect/schema/raw_response.json create mode 100644 contracts/reflect/src/errors.rs create mode 100644 contracts/staking/src/errors.rs create mode 100644 docs/simulate_riffle_shuffle.py rename packages/std/schema/{cosmos_msg_for__empty.json => cosmos_msg.json} (99%) delete mode 100644 packages/std/schema/handle_result.json delete mode 100644 packages/std/schema/init_result.json create mode 100644 packages/std/schema/message_info.json delete mode 100644 packages/std/schema/migrate_result.json delete mode 100644 packages/std/schema/query_result.json rename packages/std/src/{encoding.rs => binary.rs} (51%) create mode 100644 packages/std/src/deps.rs delete mode 100644 packages/std/src/init_handle.rs create mode 100644 packages/std/src/results/attribute.rs create mode 100644 packages/std/src/results/context.rs create mode 100644 packages/std/src/results/contract_result.rs create mode 100644 packages/std/src/results/cosmos_msg.rs create mode 100644 packages/std/src/results/handle.rs create mode 100644 packages/std/src/results/init.rs create mode 100644 packages/std/src/results/migrate.rs create mode 100644 packages/std/src/results/mod.rs create mode 100644 packages/std/src/results/query.rs create mode 100644 packages/std/src/results/system_result.rs delete mode 100644 packages/storage/src/transactions.rs delete mode 100644 packages/storage/src/typed.rs create mode 100644 packages/vm/src/backend.rs rename packages/vm/src/{compatability.rs => compatibility.rs} (69%) delete mode 100644 packages/vm/src/ffi.rs create mode 100644 packages/vm/src/limited.rs rename packages/vm/src/{modules.rs => modules/file_system_cache.rs} (67%) create mode 100644 packages/vm/src/modules/in_memory_cache.rs create mode 100644 packages/vm/src/modules/mod.rs create mode 100644 packages/vm/src/size.rs delete mode 100644 packages/vm/src/traits.rs create mode 100644 packages/vm/testdata/contract_0.11.wasm create mode 100644 packages/vm/testdata/contract_0.12.wasm diff --git a/.circleci/config.yml b/.circleci/config.yml index 4a5f0cf20..88f8860ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,7 +32,7 @@ workflows: jobs: package_schema: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 steps: - checkout - run: @@ -40,7 +40,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-package_schema-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-package_schema-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Build working_directory: ~/project/packages/schema @@ -55,11 +55,11 @@ jobs: - target/debug/.fingerprint - target/debug/build - target/debug/deps - key: cargocache-v2-package_schema-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-package_schema-rust:1.45.2-{{ checksum "Cargo.lock" }} package_std: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 steps: - checkout - run: @@ -67,7 +67,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-package_std-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-package_std-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -86,15 +86,15 @@ jobs: - run: name: Build library for native target (all features) working_directory: ~/project/packages/std - command: cargo build --locked --all-features + command: cargo build --locked --features iterator,staking - run: name: Build library for wasm target (all features) working_directory: ~/project/packages/std - command: cargo wasm --locked --all-features + command: cargo wasm --locked --features iterator,staking - run: name: Run unit tests (all features) working_directory: ~/project/packages/std - command: cargo test --locked --all-features + command: cargo test --locked --features iterator,staking - run: name: Build and run schema generator working_directory: ~/project/packages/std @@ -114,11 +114,11 @@ jobs: - target/debug/.fingerprint - target/debug/build - target/debug/deps - key: cargocache-v2-package_std-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-package_std-rust:1.45.2-{{ checksum "Cargo.lock" }} package_storage: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 steps: - checkout - run: @@ -126,7 +126,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-package_storage-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-package_storage-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Build library for native target working_directory: ~/project/packages/storage @@ -145,11 +145,11 @@ jobs: - target/debug/.fingerprint - target/debug/build - target/debug/deps - key: cargocache-v2-package_storage-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-package_storage-rust:1.45.2-{{ checksum "Cargo.lock" }} package_ext: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 steps: - checkout - run: @@ -157,7 +157,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-package_ext-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-package_ext-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -204,22 +204,19 @@ jobs: - target/debug/.fingerprint - target/debug/build - target/debug/deps - key: cargocache-v2-package_ext-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-package_ext-rust:1.45.2-{{ checksum "Cargo.lock" }} package_vm_cranelift: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 steps: - checkout - - run: - name: Install CMake (required for compiling wabt-sys, a dev dependency of the VM) - command: apt-get update && apt-get install -y cmake - run: name: Version information command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-package_vm_cranelift-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-package_vm_cranelift-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Build working_directory: ~/project/packages/vm @@ -234,16 +231,13 @@ jobs: - target/debug/.fingerprint - target/debug/build - target/debug/deps - key: cargocache-v2-package_vm_cranelift-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-package_vm_cranelift-rust:1.45.2-{{ checksum "Cargo.lock" }} package_vm_singlepass: docker: - image: rustlang/rust:nightly steps: - checkout - - run: - name: Install CMake (required for compiling wabt-sys, a dev dependency of the VM) - command: apt-get update && apt-get install -y cmake - run: name: Version information command: rustc --version; cargo --version; rustup --version; rustup target list --installed @@ -276,7 +270,7 @@ jobs: contract_burner: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 working_directory: ~/cosmwasm/contracts/burner steps: - checkout: @@ -286,7 +280,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-v2-contract_burner-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-contract_burner-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -321,11 +315,11 @@ jobs: - target/wasm32-unknown-unknown/release/.fingerprint - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps - key: cargocache-v2-contract_burner-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-contract_burner-rust:1.45.2-{{ checksum "Cargo.lock" }} contract_hackatom: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 working_directory: ~/cosmwasm/contracts/hackatom steps: - checkout: @@ -335,7 +329,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-v2-contract_hackatom-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-contract_hackatom-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -370,7 +364,7 @@ jobs: - target/wasm32-unknown-unknown/release/.fingerprint - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps - key: cargocache-v2-contract_hackatom-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-contract_hackatom-rust:1.45.2-{{ checksum "Cargo.lock" }} # In this job we use singlepass as the VM to execute integration tests. This requires Rust nightly. # Avoid duplicating generic checks like unit tests or schema generation – they belong in the generic hackatom job. @@ -409,7 +403,7 @@ jobs: contract_queue: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 working_directory: ~/cosmwasm/contracts/queue steps: - checkout: @@ -419,7 +413,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-v2-contract_queue-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-contract_queue-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -454,11 +448,11 @@ jobs: - target/wasm32-unknown-unknown/release/.fingerprint - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps - key: cargocache-v2-contract_queue-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-contract_queue-rust:1.45.2-{{ checksum "Cargo.lock" }} contract_reflect: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 working_directory: ~/cosmwasm/contracts/reflect steps: - checkout: @@ -468,7 +462,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-v2-contract_reflect-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-contract_reflect-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -503,11 +497,11 @@ jobs: - target/wasm32-unknown-unknown/release/.fingerprint - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps - key: cargocache-v2-contract_reflect-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-contract_reflect-rust:1.45.2-{{ checksum "Cargo.lock" }} contract_staking: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 working_directory: ~/cosmwasm/contracts/staking steps: - checkout: @@ -517,7 +511,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-v2-contract_staking-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-contract_staking-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -552,7 +546,8 @@ jobs: - target/wasm32-unknown-unknown/release/.fingerprint - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps - key: cargocache-v2-contract_staking-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-contract_staking-rust:1.45.2-{{ checksum "Cargo.lock" }} + contract_token_tester: docker: - image: rustlang/rust:nightly @@ -565,7 +560,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-v2-contract_token_tester-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-contract_token_tester-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -597,7 +592,7 @@ jobs: - target/wasm32-unknown-unknown/release/.fingerprint - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps - key: cargocache-v2-contract_token_tester-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-contract_token_tester-rust:1.45.2-{{ checksum "Cargo.lock" }} contract_collection_tester: docker: @@ -611,7 +606,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-v2-contract_collection_tester-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-contract_collection_tester-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add wasm32 target command: rustup target add wasm32-unknown-unknown && rustup target list --installed @@ -643,10 +638,10 @@ jobs: - target/wasm32-unknown-unknown/release/.fingerprint - target/wasm32-unknown-unknown/release/build - target/wasm32-unknown-unknown/release/deps - key: cargocache-v2-contract_collection_tester-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-contract_collection_tester-rust:1.45.2-{{ checksum "Cargo.lock" }} fmt: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 steps: - checkout - run: @@ -654,7 +649,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-fmt-rust:1.44.1-{{ checksum "Cargo.lock" }} + - cargocache-v2-fmt-rust:1.45.2-{{ checksum "Cargo.lock" }} - run: name: Add rustfmt component command: rustup component add rustfmt @@ -695,14 +690,14 @@ jobs: - target/debug/.fingerprint - target/debug/build - target/debug/deps - key: cargocache-v2-fmt-rust:1.44.1-{{ checksum "Cargo.lock" }} + key: cargocache-v2-fmt-rust:1.45.2-{{ checksum "Cargo.lock" }} clippy: docker: - - image: rust:1.44.1 + - image: rust:1.45.2 environment: # Make sure to choose version with clippy present: https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu.html - NIGHTLY_TOOLCHAIN: nightly-2020-05-26 + NIGHTLY_TOOLCHAIN: nightly-2020-09-08 steps: - checkout - run: @@ -719,7 +714,7 @@ jobs: command: rustc +$NIGHTLY_TOOLCHAIN --version && cargo +$NIGHTLY_TOOLCHAIN --version - restore_cache: keys: - - cargocache-v2-clippy-rust:1.44.1-{{ checksum "Cargo.lock" }}-{{ checksum "contracts/burner/Cargo.lock" }}-{{ checksum "contracts/hackatom/Cargo.lock" }}-{{ checksum "contracts/queue/Cargo.lock" }}-{{ checksum "contracts/reflect/Cargo.lock" }}-{{ checksum "contracts/staking/Cargo.lock" }} + - cargocache-v2-clippy-rust:1.45.2-{{ checksum "Cargo.lock" }}-{{ checksum "contracts/burner/Cargo.lock" }}-{{ checksum "contracts/hackatom/Cargo.lock" }}-{{ checksum "contracts/queue/Cargo.lock" }}-{{ checksum "contracts/reflect/Cargo.lock" }}-{{ checksum "contracts/staking/Cargo.lock" }} - run: name: Add clippy component command: rustup component add clippy @@ -814,7 +809,7 @@ jobs: - contracts/staking/target/debug/.fingerprint - contracts/staking/target/debug/build - contracts/staking/target/debug/deps - key: cargocache-v2-clippy-rust:1.44.1-{{ checksum "Cargo.lock" }}-{{ checksum "contracts/burner/Cargo.lock" }}-{{ checksum "contracts/hackatom/Cargo.lock" }}-{{ checksum "contracts/queue/Cargo.lock" }}-{{ checksum "contracts/reflect/Cargo.lock" }}-{{ checksum "contracts/staking/Cargo.lock" }} + key: cargocache-v2-clippy-rust:1.45.2-{{ checksum "Cargo.lock" }}-{{ checksum "contracts/burner/Cargo.lock" }}-{{ checksum "contracts/hackatom/Cargo.lock" }}-{{ checksum "contracts/queue/Cargo.lock" }}-{{ checksum "contracts/reflect/Cargo.lock" }}-{{ checksum "contracts/staking/Cargo.lock" }} # This job roughly follows the instructions from https://circleci.com/blog/publishing-to-github-releases-via-circleci/ build_and_upload_devcontracts: @@ -845,8 +840,8 @@ jobs: for contract_dir in ./contracts/*/; do name=$(basename $contract_dir) echo "Building $name ..." - docker run --volumes-from with_code cosmwasm/rust-optimizer:0.9.0 ./contracts/$name - docker cp with_code:/code/contracts/$name/contract.wasm ./artifacts/$name.wasm + docker run --volumes-from with_code cosmwasm/rust-optimizer:0.10.5 ./contracts/$name + docker cp with_code:/code/artifacts/$name.wasm ./artifacts/$name.wasm done - run: name: Create checksums diff --git a/.editorconfig b/.editorconfig index 3d36f20b1..2c777280a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,3 +9,6 @@ insert_final_newline = true [*.rs] indent_size = 4 + +[*.py] +indent_size = 4 diff --git a/.gitignore b/.gitignore index 1c0cf829e..d81c4473a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,7 @@ # Build results target/ -contracts/**/contract.wasm -contracts/**/hash.txt +artifacts/ # IDEs .vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8415a4218..cb7707f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,174 @@ # CHANGELOG +## 0.12.0 (2020-11-19) + +**cosmwasm-std** + +- Remove the previously deprecated `StdError::Unauthorized`. Contract specific + errors should be implemented using custom error types now (see + [migration guide](./MIGRATING.md) 0.10 -> 0.11). +- Use dependency `thiserror` instead of `snafu` to implement `StdError`. Along + with this change, the `backtraces` feature now requires Rust nightly. +- Rename `StdError::ParseErr::source` to `StdError::ParseErr::source_type` and + `StdError::SerializeErr::target` to `StdError::SerializeErr::target_type` to + work around speacial treatment of the field name `source` in thiserror. +- Rename `Extern` to `Deps` to unify naming. +- Simplify ownership of calling `handle`, etc. with `Deps` and `DepsMut` struct + that just contains references (`DepsMut` has `&mut Storage` otherwise the + same) +- Remove unused `Deps::change_querier`. If you need this or similar + functionality, create a new struct with the right querier. +- Remove `ReadonlyStorage`. You can just use `Storage` everywhere. And use + `&Storage` to provide readonly access. This was only needed to let + `PrefixedStorage`/`ReadonlyPrefixedStorage` implement the common interface, + which is something we don't need. + +**cosmwasm-storage** + +- `PrefixedStorage`/`ReadonlyPrefixedStorage` do not implement the + `Storage`/`ReadonlyStorage` traits anymore. If you need nested prefixes, you + need to construct them directly via `PrefixedStorage::multilevel` and + `ReadonlyPrefixedStorage::multilevel`. +- Remove unused `TypedStorage`. If you need this or similar functionality, you + probably want to use `Bucket` or `Singleton`. If you really need it, please + copy the v0.11 code into your project. +- Remove `StorageTransaction` along with `transactional` and `RepLog`. This has + not been used actively for contract development and is now maintained in a + contract testing framework. + +**cosmwasm-vm** + +- Remove `Storage::range` and `StorageIterator`. The storage implementation is + now responsible for maintaining iterators internally and make them accessible + via the new `Storage::scan` and `Storage::next` methods. +- Add `FfiError::IteratorDoesNotExist`. Looking at this, `FfiError` should + probably be renamed to something that includes before, on and behind the FFI + boundary to Go. +- `MockStorage` now implementes the new `Storage` trait and has an additional + `MockStorage::all` for getting all elements of an iterator in tests. +- Remove unused `Extern::change_querier`. If you need this or similar + functionality, create a new struct with the right querier. +- Let `Instance::from_code` and `CosmCache::get_instance` take options as an + `InstanceOptions` struct. This contains `gas_limit` and `print_debug` for now + and can easily be extended. `cosmwasm_vm::testing::mock_instance_options` can + be used for creating such a struct in integration tests. +- Make `FileSystemCache` crate internal. This should be used via `CosmCache`. +- Fix return type of `FileSystemCache::load` to `VmResult>` in + order to differentiate missing files from errors. +- Add in-memory caching for recently used Wasm modules. +- Rename `CosmCache` to just `cosmwasm_vm::Cache` and add `CacheOptions` to + configure it. +- Rename `Extern` to `Backend`. +- Rename `mock_dependencies` to `mock_backend` and + `mock_dependencies_with_balances` to `mock_backend_with_balances`. +- Rename `FfiError`/`FfiResult` to `BackendError`/`BackendResult` and adapt + `VmError` accordingly. + +## 0.11.2 (2020-10-26) + +**cosmwasm-std** + +- Implement `From` and `From` + for `StdError`. +- Generalize denom argument from `&str` to `S: Into` in `coin`, `coins` + and `Coin::new`. +- Implement `PartialEq` between `Binary` and `Vec`/`&[u8]`. +- Add missing `PartialEq` implementations between `HumanAddr` and `str`/`&str`. +- Add `Binary::to_array`, which allows you to copy binary content into a + fixed-length `u8` array. This is espeically useful for creating integers from + binary data. + +## 0.11.1 (2020-10-12) + +**cosmwasm-std** + +- Implement `Hash` and `Eq` for `Binary` to allow using `Binary` in `HashSet` + and `HashMap`. +- Implement `Hash` and `Eq` for `CanonicalAddr` to allow using `CanonicalAddr` + in `HashSet` and `HashMap`. +- Implement `Add`, `AddAssign` and `Sub` with references on the right hand side + for `Uint128`. +- Implement `Sum` and `Sum<&'a Uint128>` for `Uint128`. + +## 0.11.0 (2020-10-08) + +**all** + +- Drop support for Rust versions lower than 1.45.2. +- The serialization of the result from `init`/`migrate`/`handle`/`query` changed + in an incompatible way. See the new `ContractResult` and `SystemResult` types + and their documentation. +- Pass `Env` into `query` as well. As this doesn't have `MessageInfo`, we + removed `MessageInfo` from `Env` and pass that as a separate argument to + `init`, `handle`, and `query`. See the example + [type definitions in the README](README.md#implementing-the-smart-contract) to + see how to update your contract exports (just add one extra arg each). + +**cosmwasm-std** + +- Add `time_nanos` to `BlockInfo` allowing access to high precision block times. +- Change `FullDelegation::accumulated_rewards` from `Coin` to `Vec`. +- Rename `InitResponse::log`, `MigrateResponse::log` and `HandleResponse::log` + to `InitResponse::attributes`, `MigrateResponse::attributes` and + `HandleResponse::attributes`. +- Rename `LogAttribute` to `Attribute`. +- Rename `log` to `attr`. +- Rename `Context::add_log` to `Context::add_attribute`. +- Add `Api::debug` for emitting debug messages during development. +- Fix error type for response parsing errors in `ExternalQuerier::raw_query`. + This was `Ok(Err(StdError::ParseErr))` instead of + `Err(SystemError::InvalidResponse)`, implying an error created in the target + contract. +- Deprecate `StdError::Unauthorized` and `StdError::unauthorized` in favour of + custom errors. From now on `StdError` should only be created by the standard + library and should only contain cases the standard library needs. +- Let `impl Display for CanonicalAddr` use upper case hex instead of base64. + This also affects `CanonicalAddr::to_string`. +- Create trait `CustomQuery` for the generic argument in + `QueryRequest`. This allows us to provide + `impl From for QueryRequest` for any custom query. +- Implement `From for Vec`. +- Implement `From for Vec`. +- Add `Binary::into_vec` and `CanonicalAddr::into_vec`. +- The `canonical_length` argument was removed from `mock_dependencies`, + `mock_dependencies_with_balances`. In the now deprecated `MockApi::new`, the + argument is unused. Contracts should not need to set this value and usually + should not make assumptions about the value. +- The canonical address encoding in `MockApi::canonical_address` and + `MockApi::human_address` was changed to an unpredicatable represenation of + non-standard length that aims to destroy most of the input structure. + +**cosmwasm-storage** + +- Change order of arguments such that `storage` is always first followed by + namespace in `Bucket::new`, `Bucket::multilevel`, `ReadonlyBucket::new`, + `ReadonlyBucket::multilevel`, `bucket` and `bucket_read`. +- Change order of arguments such that `storage` is always first followed by + namespace in `PrefixedStorage::new`, `PrefixedStorage::multilevel`, + `ReadonlyPrefixedStorage::new`, `ReadonlyPrefixedStorage::multilevel`, + `prefixed` and `prefixed_read`. + +**cosmwasm-vm** + +- `CosmCache::new`, `Instance::from_code` and `Instance::from_module` now take + an additional argument to enable/disable printing debug logs from contracts. +- Bump required export `cosmwasm_vm_version_3` to `cosmwasm_vm_version_4`. +- The `canonical_length` argument was removed from `mock_dependencies`, + `mock_dependencies_with_balances` and `MockApi::new_failing`. In the now + deprecated `MockApi::new`, the argument is unused. Contracts should not need + to set this value and usually should not make assumptions about the value. +- The canonical address encoding in `MockApi::canonical_address` and + `MockApi::human_address` was changed to an unpredicatable represenation of + non-standard length that aims to destroy most of the input structure. + +## 0.10.1 (2020-08-25) + +**cosmwasm-std** + +- Fix bug where `ExternalStorage.range()` would cause VM error if either lower + or upper bound was set + ([#508](https://github.com/CosmWasm/cosmwasm/issues/508)) + ## 0.10.0 (2020-07-30) **all** diff --git a/Cargo.lock b/Cargo.lock index 00e7885de..29a99765e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,28 +18,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "backtrace" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" version = "0.11.0" @@ -104,6 +82,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -114,13 +103,10 @@ dependencies = [ ] [[package]] -name = "cmake" -version = "0.1.42" +name = "clru" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fb25b677f8bf1eb325017cb6bb8452f87969db0fedb4f757b297bee78a7c62" -dependencies = [ - "cc", -] +checksum = "6add52b3696a2015ab5bab92e99e75e8a9eb7a10b76b92dbb3a43f25adbd7629" [[package]] name = "constant_time_eq" @@ -141,7 +127,7 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -149,29 +135,30 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", + "chrono", "cosmwasm-schema", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-std", "serde", - "snafu", ] [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -180,13 +167,13 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", "tempfile", - "wabt", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", "wasmer-singlepass-backend", + "wat", ] [[package]] @@ -330,12 +317,6 @@ dependencies = [ "generic-array 0.14.3", ] -[[package]] -name = "doc-comment" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807e5847c39ad6a11eac66de492ed1406f76a260eb8656e8740cad9eabc69c27" - [[package]] name = "dynasm" version = "0.5.2" @@ -434,12 +415,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "glob" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" - [[package]] name = "hermit-abi" version = "0.1.8" @@ -477,6 +452,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leb128" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" + [[package]] name = "libc" version = "0.2.68" @@ -539,6 +520,25 @@ dependencies = [ "void", ] +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.12.0" @@ -576,9 +576,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" @@ -719,12 +719,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "rustc_version" version = "0.2.3" @@ -873,28 +867,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" -[[package]] -name = "snafu" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1ec0ae2ed980f26e1ad62e717feb01df90731df56887b5391a2c79f9f6805be" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec32ba84a7a86aeb0bc32fd0c46d31b0285599f68ea72e87eff6127889d99e1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "stable_deref_trait" version = "1.1.1" @@ -958,6 +930,17 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +dependencies = [ + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "typenum" version = "1.11.2" @@ -982,29 +965,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -[[package]] -name = "wabt" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5c5c1286c6e578416982609f47594265f9d489f9b836157d403ad605a46693" -dependencies = [ - "serde", - "serde_derive", - "serde_json", - "wabt-sys", -] - -[[package]] -name = "wabt-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5d153dc96aad7dc13ab90835b892c69867948112d95299e522d370c4e13a08" -dependencies = [ - "cc", - "cmake", - "glob", -] - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1138,6 +1098,24 @@ version = "0.51.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" +[[package]] +name = "wast" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3f174eed73e885ede6c8fcc3fbea8c3757afa521840676496cde56bb742ddab" +dependencies = [ + "leb128", +] + +[[package]] +name = "wat" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b2dccbce4d0e14875091846e110a2369267b18ddd0d6423479b88dad914d71" +dependencies = [ + "wast", +] + [[package]] name = "winapi" version = "0.3.8" diff --git a/MIGRATING.md b/MIGRATING.md index 4c7a2ccfb..31317ba04 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -4,8 +4,357 @@ This guide explains what is needed to upgrade contracts when migrating over major releases of `cosmwasm`. Note that you can also view the [complete CHANGELOG](./CHANGELOG.md) to understand the differences. +## 0.11 -> 0.12 + +- Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use): + + ``` + [dependencies] + cosmwasm-std = "0.12.0" + cosmwasm-storage = "0.12.0" + # ... + + [dev-dependencies] + cosmwasm-schema = "0.12.0" + cosmwasm-vm = "0.12.0" + # ... + ``` + +- In your contract's `.cargo/config` remove `--features backtraces`, which is + now available in Rust nightly only: + + ```diff + @@ -1,6 +1,6 @@ + [alias] + wasm = "build --release --target wasm32-unknown-unknown" + wasm-debug = "build --target wasm32-unknown-unknown" + -unit-test = "test --lib --features backtraces" + +unit-test = "test --lib" + integration-test = "test --test integration" + schema = "run --example schema" + ``` + + In order to use backtraces for debugging, run + `RUST_BACKTRACE=1 cargo +nightly unit-test --features backtraces`. + +- Rename the type `Extern` to `Deps`, and radically simplify the + `init`/`handle`/`migrate`/`query` entrypoints. Rather than + `&mut Extern`, use `DepsMut`. And instead of `&Extern`, use + `Deps`. If you ever pass eg. `foo(api: A)` around, you must now use + dynamic trait pointers: `foo(api: &dyn Api)`. Here is the quick search-replace + guide on how to fix `contract.rs`: + + _In production (non-test) code:_ + + - `` => `` + - `&mut Extern` => `DepsMut` + - `&Extern` => `Deps` + - `&mut deps.storage` => `deps.storage` where passing into `state.rs` helpers + - `&deps.storage` => `deps.storage` where passing into `state.rs` helpers + + On the top, remove `use cosmwasm_std::{Api, Extern, Querier, Storage}`. Add + `use cosmwasm_std::{Deps, DepsMut}`. + + _In test code only:_ + + - `&mut deps,` => `deps.as_mut(),` + - `&deps,` => `deps.as_ref(),` + + You may have to add `use cosmwasm_std::{Storage}` if the compile complains + about the trait + + _If you use cosmwasm-storage, in `state.rs`:_ + + - `` => `` + - `` => `` + - ` `<` + - `&mut S` => `&mut dyn Storage` + - `&S` => `&dyn Storage` + +- If you have any references to `ReadonlyStorage` left after the above, please + replace them with `Storage` + +## 0.10 -> 0.11 + +- Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use): + + ``` + [dependencies] + cosmwasm-std = "0.11.0" + cosmwasm-storage = "0.11.0" + # ... + + [dev-dependencies] + cosmwasm-schema = "0.11.0" + cosmwasm-vm = "0.11.0" + # ... + ``` + +- Contracts now support any custom error type `E: ToString + From`. + Previously this has been `StdError`, which you can still use. However, you can + now create a much more structured error experience for your unit tests that + handels exactly the error cases of your contract. In order to get a convenient + implementation for `ToString` and `From`, we use the crate + [thiserror](https://crates.io/crates/thiserror), which needs to be added to + the contracts dependencies in `Cargo.toml`. To create the custom error, create + an error module `src/errors.rs` as follows: + + ```rust + use cosmwasm_std::{CanonicalAddr, StdError}; + use thiserror::Error; + + // thiserror implements Display and ToString if you + // set the `#[error("…")]` attribute for all cases + #[derive(Error, Debug)] + pub enum MyCustomError { + #[error("{0}")] + // let thiserror implement From for you + Std(#[from] StdError), + // this is whatever we want + #[error("Permission denied: the sender is not the current owner")] + NotCurrentOwner { + expected: CanonicalAddr, + actual: CanonicalAddr, + }, + #[error("Messages empty. Must reflect at least one message")] + MessagesEmpty, + } + ``` + + Then add `mod errors;` to `src/lib.rs` and `use crate::errors::MyCustomError;` + to `src/contract.rs`. Now adapt the return types as follows: + + - `fn init`: `Result`, + - `fn migrate` (if you have it): `Result`, + - `fn handle`: `Result`, + - `fn query`: `Result`. + + If one of your funtions does not use the custom error, you can continue to use + `StdError` as before. I.e. you can have `handle` returning + `Result` and `query` returning + `StdResult`. + + You can have a top-hevel `init`/`migrate`/`handle`/`query` that returns a + custom error but some of its implementations only return errors from the + standard library (`StdResult` aka. + `Result`). Then use `Ok(std_result?)` to convert + between the result types. E.g. + + ```rust + pub fn handle( + deps: &mut Extern, + env: Env, + msg: HandleMsg, + ) -> Result { + match msg { + // conversion to Result + HandleMsg::Bond {} => Ok(bond(deps, env)?), + // this already returns Result + HandleMsg::_BondAllTokens {} => _bond_all_tokens(deps, env), + } + } + ``` + + or + + ```rust + pub fn init( + deps: &mut Extern, + env: Env, + msg: InitMsg, + ) -> Result { + // … + + let mut ctx = Context::new(); + ctx.add_attribute("Let the", "hacking begin"); + Ok(ctx.try_into()?) + } + ``` + + Once you got familiar with the concept, you can create different error types + for each of the contract's functions. + + You can also try a different error library than + [thiserror](https://crates.io/crates/thiserror). The + [staking development contract](https://github.com/CosmWasm/cosmwasm/tree/master/contracts/staking) + shows how this would look like using [snafu](https://crates.io/crates/snafu). + +- Change order of arguments such that `storage` is always first followed by + namespace in `Bucket::new`, `Bucket::multilevel`, `ReadonlyBucket::new`, + `ReadonlyBucket::multilevel`, `PrefixedStorage::new`, + `PrefixedStorage::multilevel`, `ReadonlyPrefixedStorage::new`, + `ReadonlyPrefixedStorage::multilevel`, `bucket`, `bucket_read`, `prefixed` and + `prefixed_read`. + + ```rust + // before + let mut bucket = bucket::<_, Data>(b"data", &mut store); + + // after + let mut bucket = bucket::<_, Data>(&mut store, b"data"); + ``` + +- Rename `InitResponse::log`, `MigrateResponse::log` and `HandleResponse::log` + to `InitResponse::attributes`, `MigrateResponse::attributes` and + `HandleResponse::attributes`. Replace calls to `log` with `attr`: + + ```rust + // before + Ok(HandleResponse { + log: vec![log("action", "change_owner"), log("owner", owner)], + ..HandleResponse::default() + }) + + // after + Ok(HandleResponse { + attributes: vec![attr("action", "change_owner"), attr("owner", owner)], + ..HandleResponse::default() + }) + ``` + +- Rename `Context::add_log` to `Context::add_attribute`: + + ```rust + // before + let mut ctx = Context::new(); + ctx.add_log("action", "release"); + ctx.add_log("destination", &to_addr); + + // after + let mut ctx = Context::new(); + ctx.add_attribute("action", "release"); + ctx.add_attribute("destination", &to_addr); + ``` + +- Add result type to `Bucket::update` and `Singleton::update`: + + ```rust + // before + bucket.update(b"maria", |mayd: Option| { + let mut d = mayd.ok_or(StdError::not_found("Data"))?; + old_age = d.age; + d.age += 1; + Ok(d) + }) + + // after + bucket.update(b"maria", |mayd: Option| -> StdResult<_> { + let mut d = mayd.ok_or(StdError::not_found("Data"))?; + old_age = d.age; + d.age += 1; + Ok(d) + }) + ``` + +- Remove all `canonical_length` arguments from mock APIs in tests: + + ```rust + // before + let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(20, &coins(123456, "gold")); + let deps = mock_dependencies_with_balances(20, &[(&rich_addr, &rich_balance)]); + let api = MockApi::new(20); + + // after + let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(&coins(123456, "gold")); + let deps = mock_dependencies_with_balances(&[(&rich_addr, &rich_balance)]); + let api = MockApi::default(); + ``` + +- Add `MessageInfo` as separate arg after `Env` for `init`, `handle`, `migrate`. + Add `Env` arg to `query`. Use `info.sender` instead of `env.message.sender` + and `info.sent_funds` rather than `env.message.sent_funds`. Just changing the + function signatures of the 3-4 export functions should be enough, then the + compiler will warn you anywhere you use `env.message` + + ```rust + // before + pub fn init( + deps: &mut Extern, + env: Env, + msg: InitMsg, + ) { + deps.storage.set( + CONFIG_KEY, + &to_vec(&State { + verifier: deps.api.canonical_address(&msg.verifier)?, + beneficiary: deps.api.canonical_address(&msg.beneficiary)?, + funder: deps.api.canonical_address(&env.message.sender)?, + })?, + ); + } + + // after + pub fn init( + deps: &mut Extern, + _env: Env, + info: MessageInfo, + msg: InitMsg, + ) { + deps.storage.set( + CONFIG_KEY, + &to_vec(&State { + verifier: deps.api.canonical_address(&msg.verifier)?, + beneficiary: deps.api.canonical_address(&msg.beneficiary)?, + funder: deps.api.canonical_address(&info.sender)?, + })?, + ); + } + ``` + +- Test code now has `mock_info` which takes the same args `mock_env` used to. + You can just pass `mock_env()` directly into the function calls unless you + need to change height/time. +- One more object to pass in for both unit and integration tests. To do this + quickly, I just highlight all copies of `env` and replace them with `info` + (using Ctrl+D in VSCode or Alt+J in IntelliJ). Then I select all `deps, info` + sections and replace that with `deps, mock_env(), info`. This fixes up all + `init` and `handle` calls, then just add an extra `mock_env()` to the query + calls. + + ```rust + // before: unit test + let env = mock_env(creator.as_str(), &[]); + let res = init(&mut deps, env, msg).unwrap(); + + let query_response = query(&deps, QueryMsg::Verifier {}).unwrap(); + + // after: unit test + let info = mock_info(creator.as_str(), &[]); + let res = init(&mut deps, mock_env(), info, msg).unwrap(); + + let query_response = query(&deps, mock_env(), QueryMsg::Verifier {}).unwrap(); + + // before: integration test + let env = mock_env("creator", &coins(1000, "earth")); + let res: InitResponse = init(&mut deps, env, msg).unwrap(); + + let query_response = query(&mut deps, QueryMsg::Verifier {}).unwrap(); + + // after: integration test + let info = mock_info("creator", &coins(1000, "earth")); + let res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); + + let query_response = query(&mut deps, mock_env(), QueryMsg::Verifier {}).unwrap(); + ``` + ## 0.9 -> 0.10 +- Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use): + + ``` + [dependencies] + cosmwasm-std = "0.10.0" + cosmwasm-storage = "0.10.0" + # ... + + [dev-dependencies] + cosmwasm-schema = "0.10.0" + cosmwasm-vm = "0.10.0" + # ... + ``` + Integration tests: - Calls to `Api::human_address` and `Api::canonical_address` now return a pair @@ -42,12 +391,19 @@ Contracts: ## 0.8 -> 0.9 -`dependencies`/`dev-dependencies` in `Cargo.toml`: +- Update CosmWasm dependencies in Cargo.toml (skip the ones you don't use): -- Replace `cosmwasm-schema = "0.8"` with `cosmwasm-schema = "0.9"` -- Replace `cosmwasm-std = "0.8"` with `cosmwasm-std = "0.9"` -- Replace `cosmwasm-storage = "0.8"` with `cosmwasm-storage = "0.9"` -- Replace `cosmwasm-vm = "0.8"` with `cosmwasm-vm = "0.9"` + ``` + [dependencies] + cosmwasm-std = "0.9.0" + cosmwasm-storage = "0.9.0" + # ... + + [dev-dependencies] + cosmwasm-schema = "0.9.0" + cosmwasm-vm = "0.9.0" + # ... + ``` `lib.rs`: diff --git a/README.md b/README.md index f5fc44d88..7653e1694 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,10 @@ The required exports provided by the cosmwasm smart contract are: extern "C" fn allocate(size: usize) -> u32; extern "C" fn deallocate(pointer: u32); -extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32; -extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32; -extern "C" fn query(msg_ptr: u32) -> u32; -extern "C" fn migrate(env_ptr: u32, msg_ptr: u32) -> u32; +extern "C" fn init(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32; +extern "C" fn handle(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32; +extern "C" fn query(env_ptr: u32, msg_ptr: u32) -> u32; +extern "C" fn migrate(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32; ``` `allocate`/`deallocate` allow the host to manage data within the Wasm VM. If @@ -187,11 +187,32 @@ custom `InitMsg` and `HandleMsg` structs for parsing your custom message types (as json): ```rust -pub fn init(store: &mut T, params: Params, msg: Vec -> - StdResult> { } - -pub fn handle(store: &mut T, params: Params, msg: Vec -> - StdResult> { } +pub fn init( + deps: &mut Deps, + env: Env, + info: MessageInfo, + msg: InitMsg, +) -> StdResult {} + +pub fn handle( + deps: &mut Deps, + env: Env, + info: MessageInfo, + msg: HandleMsg, +) -> Result { } + +pub fn query( + deps: &Deps, + env: Env, + msg: QueryMsg, +) -> StdResult { } + +pub fn migrate( + deps: &mut Deps, + env: Env, + info: MessageInfo, + msg: HandleMsg, +) -> Result { } ``` The low-level `c_read` and `c_write` imports are nicely wrapped for you by a @@ -252,15 +273,17 @@ reproducible build step so others can prove the on-chain wasm code was generated from the published rust code. For that, we have a separate repo, -[cosmwasm-opt](https://github.com/CosmWasm/cosmwasm-opt) that provides a -[docker image](https://hub.docker.com/r/CosmWasm/cosmwasm-opt/tags) for +[rust-optimizer](https://github.com/CosmWasm/rust-optimizer) that provides a +[docker image](https://hub.docker.com/r/CosmWasm/rust-optimizer/tags) for building. For more info, look at -[cosmwasm-opt README](https://github.com/CosmWasm/cosmwasm-opt/blob/master/README.md#usage), +[rust-optimizer README](https://github.com/CosmWasm/rust-optimizer/blob/master/README.md#usage), but the quickstart guide is: ```sh -export CODE=/path/to/your/wasm/script -docker run --rm -u $(id -u):$(id -g) -v "${CODE}":/code confio/cosmwasm-opt:1.38 +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.10.3 ``` It will output a highly size-optimized build as `contract.wasm` in `$CODE`. With diff --git a/contracts/README.md b/contracts/README.md index 68f8701b0..436597b50 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -6,41 +6,43 @@ example contracts, see ## Optimized builds -`hackatom`, `reflect` and `queue` are used for testing in other repos, e.g. -[in go-cosmwasm](https://github.com/CosmWasm/go-cosmwasm/tree/master/api/testdata). -`staking` is used by a demo project in CosmWasm JS -(https://github.com/CosmWasm/cosmwasm-js/issues/170). +Those development contracts are used for testing in other repos, e.g. in +[go-cosmwasm](https://github.com/CosmWasm/go-cosmwasm/tree/master/api/testdata) +or +[cosmjs](https://github.com/CosmWasm/cosmjs/tree/master/scripts/wasmd/contracts). -To rebuild all contracts as part of a release use the following commands: +They are [built and deployed](https://github.com/CosmWasm/cosmwasm/releases) by +the CI for every release tag. In case you need to build them manually for some +reason, use the following commands: ```sh 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.9.0 ./contracts/burner + cosmwasm/rust-optimizer:0.10.5 ./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.9.0 ./contracts/hackatom + cosmwasm/rust-optimizer:0.10.5 ./contracts/hackatom 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.9.0 ./contracts/queue + cosmwasm/rust-optimizer:0.10.5 ./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.9.0 ./contracts/reflect + cosmwasm/rust-optimizer:0.10.5 ./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.9.0 ./contracts/staking + cosmwasm/rust-optimizer:0.10.5 ./contracts/staking docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="devcontract_token_tester",target=/code/contracts/token-tester/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.9.0 ./contracts/token-tester + cosmwasm/rust-optimizer:0.10.5 ./contracts/token-tester ``` diff --git a/contracts/burner/.cargo/config b/contracts/burner/.cargo/config index 7c115322a..8d4bc738b 100644 --- a/contracts/burner/.cargo/config +++ b/contracts/burner/.cargo/config @@ -1,6 +1,6 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" +unit-test = "test --lib" integration-test = "test --test integration" schema = "run --example schema" diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index f77a1b5be..87daac531 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -1,14 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "addr2line" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" -dependencies = [ - "gimli 0.21.0", -] - [[package]] name = "arrayref" version = "0.3.6" @@ -27,19 +18,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "backtrace" -version = "0.3.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.11.0" @@ -88,14 +66,13 @@ dependencies = [ [[package]] name = "burner" -version = "0.1.0" +version = "0.12.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vm", "schemars", "serde", - "snafu", ] [[package]] @@ -125,6 +102,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "clru" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6add52b3696a2015ab5bab92e99e75e8a9eb7a10b76b92dbb3a43f25adbd7629" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -133,7 +116,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -141,19 +124,20 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -162,7 +146,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", @@ -195,7 +179,7 @@ dependencies = [ "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.20.0", + "gimli", "log", "smallvec", "target-lexicon", @@ -310,12 +294,6 @@ dependencies = [ "generic-array 0.14.3", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "dynasm" version = "0.5.2" @@ -403,12 +381,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "gimli" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" - [[package]] name = "hermit-abi" version = "0.1.13" @@ -518,12 +490,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -551,9 +517,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" @@ -638,12 +604,6 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "rustc_version" version = "0.2.3" @@ -792,28 +752,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -[[package]] -name = "snafu" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5aed652511f5c9123cf2afbe9c244c29db6effa2abb05c866e965c82405ce" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf8f7d5720104a9df0f7076a8682024e958bba0fe9848767bb44f251f3648e9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "stable_deref_trait" version = "1.1.1" diff --git a/contracts/burner/Cargo.toml b/contracts/burner/Cargo.toml index 29453b18f..fbf8cdacf 100644 --- a/contracts/burner/Cargo.toml +++ b/contracts/burner/Cargo.toml @@ -1,14 +1,9 @@ [package] name = "burner" -version = "0.1.0" +version = "0.12.0" authors = ["Ethan Frey "] edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -38,7 +33,6 @@ singlepass = ["cosmwasm-vm/default-singlepass"] cosmwasm-std = { path = "../../packages/std", features = ["iterator"] } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -snafu = { version = "0.6.3" } [dev-dependencies] cosmwasm-vm = { path = "../../packages/vm", default-features = false, features = ["iterator"] } diff --git a/contracts/burner/src/contract.rs b/contracts/burner/src/contract.rs index c2b01a8a0..cda0c5858 100644 --- a/contracts/burner/src/contract.rs +++ b/contracts/burner/src/contract.rs @@ -1,13 +1,14 @@ use cosmwasm_std::{ - log, Api, BankMsg, Binary, Env, Extern, HandleResponse, InitResponse, MigrateResponse, Order, - Querier, StdError, StdResult, Storage, + attr, BankMsg, Binary, Deps, DepsMut, Env, HandleResponse, InitResponse, MessageInfo, + MigrateResponse, Order, StdError, StdResult, }; use crate::msg::{HandleMsg, InitMsg, MigrateMsg, QueryMsg}; -pub fn init( - _deps: &mut Extern, +pub fn init( + _deps: DepsMut, _env: Env, + _info: MessageInfo, _msg: InitMsg, ) -> StdResult { Err(StdError::generic_err( @@ -15,9 +16,10 @@ pub fn init( )) } -pub fn handle( - _deps: &mut Extern, +pub fn handle( + _deps: DepsMut, _env: Env, + _info: MessageInfo, _msg: HandleMsg, ) -> StdResult { Err(StdError::generic_err( @@ -25,9 +27,10 @@ pub fn handle( )) } -pub fn migrate( - deps: &mut Extern, +pub fn migrate( + deps: DepsMut, env: Env, + _info: MessageInfo, msg: MigrateMsg, ) -> StdResult { // delete all state @@ -53,15 +56,12 @@ pub fn migrate( Ok(MigrateResponse { messages: vec![send.into()], - log: vec![log("action", "burn"), log("payout", msg.payout)], + attributes: vec![attr("action", "burn"), attr("payout", msg.payout)], data: Some(data_msg.into()), }) } -pub fn query( - _deps: &Extern, - _msg: QueryMsg, -) -> StdResult { +pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { Err(StdError::generic_err( "You can only use this contract for migrations", )) @@ -70,17 +70,17 @@ pub fn query( #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{coins, HumanAddr, ReadonlyStorage, StdError}; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MOCK_CONTRACT_ADDR}; + use cosmwasm_std::{coins, HumanAddr, StdError, Storage}; #[test] fn init_fails() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success - let res = init(&mut deps, env, msg); + let res = init(deps.as_mut(), mock_env(), info, msg); match res.unwrap_err() { StdError::GenericErr { msg, .. } => { assert_eq!(msg, "You can only use this contract for migrations") @@ -91,7 +91,7 @@ mod tests { #[test] fn migrate_cleans_up_data() { - let mut deps = mock_dependencies(20, &coins(123456, "gold")); + let mut deps = mock_dependencies(&coins(123456, "gold")); // store some sample data deps.storage.set(b"foo", b"bar"); @@ -105,8 +105,8 @@ mod tests { let msg = MigrateMsg { payout: payout.clone(), }; - let env = mock_env("creator", &[]); - let res = migrate(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &[]); + let res = migrate(deps.as_mut(), mock_env(), info, msg).unwrap(); // check payout assert_eq!(1, res.messages.len()); let msg = res.messages.get(0).expect("no message"); diff --git a/contracts/burner/tests/integration.rs b/contracts/burner/tests/integration.rs index b21b89baa..6ee6b06ee 100644 --- a/contracts/burner/tests/integration.rs +++ b/contracts/burner/tests/integration.rs @@ -17,9 +17,10 @@ //! }); //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) -use cosmwasm_std::{coins, BankMsg, HumanAddr, InitResult, MigrateResponse, Order, StdError}; -use cosmwasm_vm::testing::{init, migrate, mock_env, mock_instance, MOCK_CONTRACT_ADDR}; -use cosmwasm_vm::StorageIterator; +use cosmwasm_std::{ + coins, BankMsg, ContractResult, HumanAddr, InitResponse, MigrateResponse, Order, +}; +use cosmwasm_vm::testing::{init, migrate, mock_env, mock_info, mock_instance, MOCK_CONTRACT_ADDR}; use burner::msg::{InitMsg, MigrateMsg}; use cosmwasm_vm::Storage; @@ -34,15 +35,14 @@ fn init_fails() { let mut deps = mock_instance(WASM, &[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success - let res: InitResult = init(&mut deps, env, msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "You can only use this contract for migrations") - } - _ => panic!("expected migrate error message"), - } + let res: ContractResult = init(&mut deps, mock_env(), info, msg); + let msg = res.unwrap_err(); + assert_eq!( + msg, + "Generic error: You can only use this contract for migrations" + ); } #[test] @@ -54,13 +54,8 @@ fn migrate_cleans_up_data() { storage.set(b"foo", b"bar").0.unwrap(); storage.set(b"key2", b"data2").0.unwrap(); storage.set(b"key3", b"cool stuff").0.unwrap(); - let cnt = storage - .range(None, None, Order::Ascending) - .0 - .unwrap() - .elements() - .unwrap() - .len(); + let iter_id = storage.scan(None, None, Order::Ascending).0.unwrap(); + let cnt = storage.all(iter_id).0.unwrap().len(); assert_eq!(3, cnt); Ok(()) }) @@ -71,8 +66,8 @@ fn migrate_cleans_up_data() { let msg = MigrateMsg { payout: payout.clone(), }; - let env = mock_env("creator", &[]); - let res: MigrateResponse = migrate(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &[]); + let res: MigrateResponse = migrate(&mut deps, mock_env(), info, msg).unwrap(); // check payout assert_eq!(1, res.messages.len()); let msg = res.messages.get(0).expect("no message"); @@ -88,13 +83,8 @@ fn migrate_cleans_up_data() { // check there is no data in storage deps.with_storage(|storage| { - let cnt = storage - .range(None, None, Order::Ascending) - .0 - .unwrap() - .elements() - .unwrap() - .len(); + let iter_id = storage.scan(None, None, Order::Ascending).0.unwrap(); + let cnt = storage.all(iter_id).0.unwrap().len(); assert_eq!(0, cnt); Ok(()) }) diff --git a/contracts/collection-tester/Cargo.lock b/contracts/collection-tester/Cargo.lock index 642fae2ce..7c67468a2 100644 --- a/contracts/collection-tester/Cargo.lock +++ b/contracts/collection-tester/Cargo.lock @@ -1,20 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "addr2line" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" -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 = "arrayref" version = "0.3.6" @@ -33,20 +18,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "backtrace" -version = "0.3.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2baad346b2d4e94a24347adeee9c7a93f412ee94b9cc26e5b59dea23848e9f28" -dependencies = [ - "addr2line", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.11.0" @@ -126,9 +97,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "clru" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e10ee132778350b0390b347b47e4bbb098e23f0ee34ded4a03b078cae19024" + [[package]] name = "collection-tester" -version = "0.1.0" +version = "0.12.0" dependencies = [ "cosmwasm-ext", "cosmwasm-schema", @@ -164,7 +141,7 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -172,18 +149,18 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-std", "serde", @@ -191,8 +168,9 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -201,7 +179,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", @@ -234,7 +212,7 @@ dependencies = [ "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.20.0", + "gimli", "log", "smallvec", "target-lexicon", @@ -442,12 +420,6 @@ dependencies = [ "indexmap", ] -[[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" @@ -535,16 +507,6 @@ 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 = "nix" version = "0.15.0" @@ -568,12 +530,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -601,9 +557,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" @@ -689,12 +645,6 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" -[[package]] -name = "rustc-demangle" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" - [[package]] name = "rustc_version" version = "0.2.3" @@ -849,7 +799,6 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09" dependencies = [ - "backtrace", "doc-comment", "snafu-derive", ] diff --git a/contracts/collection-tester/Cargo.toml b/contracts/collection-tester/Cargo.toml index 6b40a1c64..cfafebece 100644 --- a/contracts/collection-tester/Cargo.toml +++ b/contracts/collection-tester/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "collection-tester" -version = "0.1.0" +version = "0.12.0" authors = ["shiki.tak"] edition = "2018" description = "simple tester for cosmwasm/ext" diff --git a/contracts/collection-tester/src/contract.rs b/contracts/collection-tester/src/contract.rs index 3c61b3c13..39c1ba44a 100644 --- a/contracts/collection-tester/src/contract.rs +++ b/contracts/collection-tester/src/contract.rs @@ -2,8 +2,8 @@ use std::convert::TryFrom; use std::str::FromStr; use cosmwasm_std::{ - log, to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr, - InitResponse, Querier, StdResult, Storage, Uint128, + attr, to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, HandleResponse, HandleResult, + HumanAddr, InitResponse, MessageInfo, StdResult, Uint128, }; use cosmwasm_ext::{ @@ -14,23 +14,20 @@ use cosmwasm_ext::{ use crate::msg::{HandleMsg, InitMsg, QueryMsg}; use crate::state::{config, State}; -pub fn init( - deps: &mut Extern, - env: Env, - _msg: InitMsg, -) -> StdResult { +pub fn init(deps: DepsMut, _env: Env, info: MessageInfo, _msg: InitMsg) -> StdResult { let state = State { - owner: deps.api.canonical_address(&env.message.sender)?, + owner: deps.api.canonical_address(&info.sender)?, }; - config(&mut deps.storage).save(&state)?; + config(deps.storage).save(&state)?; Ok(InitResponse::default()) } -pub fn handle( - deps: &mut Extern, +pub fn handle( + deps: DepsMut, env: Env, + info: MessageInfo, msg: HandleMsg, ) -> HandleResult> { match msg { @@ -39,13 +36,13 @@ pub fn handle( name, meta, base_img_uri, - } => try_create(deps, env, owner, name, meta, base_img_uri), + } => try_create(deps, env, info, owner, name, meta, base_img_uri), HandleMsg::IssueNft { owner, contract_id, name, meta, - } => try_issue_nft(deps, env, owner, contract_id, name, meta), + } => try_issue_nft(deps, env, info, owner, contract_id, name, meta), HandleMsg::IssueFt { owner, contract_id, @@ -58,6 +55,7 @@ pub fn handle( } => try_issue_ft( deps, env, + info, owner, contract_id, to, @@ -72,61 +70,61 @@ pub fn handle( contract_id, to, token_types, - } => try_mint_nft(deps, env, from, contract_id, to, token_types), + } => try_mint_nft(deps, env, info, from, contract_id, to, token_types), HandleMsg::MintFt { from, contract_id, to, tokens, - } => try_mint_ft(deps, env, from, contract_id, to, tokens), + } => try_mint_ft(deps, env, info, from, contract_id, to, tokens), HandleMsg::BurnNft { from, contract_id, token_id, - } => try_burn_nft(deps, env, from, contract_id, token_id), + } => try_burn_nft(deps, env, info, from, contract_id, token_id), HandleMsg::BurnNftFrom { proxy, contract_id, from, token_ids, - } => try_burn_nft_from(deps, env, proxy, contract_id, from, token_ids), + } => try_burn_nft_from(deps, env, info, proxy, contract_id, from, token_ids), HandleMsg::BurnFt { from, contract_id, amounts, - } => try_burn_ft(deps, env, from, contract_id, amounts), + } => try_burn_ft(deps, env, info, from, contract_id, amounts), HandleMsg::BurnFtFrom { proxy, contract_id, from, amounts, - } => try_burn_ft_from(deps, env, proxy, contract_id, from, amounts), + } => try_burn_ft_from(deps, env, info, proxy, contract_id, from, amounts), HandleMsg::TransferNft { from, contract_id, to, token_ids, - } => try_transfer_nft(deps, env, from, contract_id, to, token_ids), + } => try_transfer_nft(deps, env, info, from, contract_id, to, token_ids), HandleMsg::TransferNftFrom { proxy, contract_id, from, to, token_ids, - } => try_transfer_nft_from(deps, env, proxy, contract_id, from, to, token_ids), + } => try_transfer_nft_from(deps, env, info, proxy, contract_id, from, to, token_ids), HandleMsg::TransferFt { from, contract_id, to, tokens, - } => try_transfer_ft(deps, env, from, contract_id, to, tokens), + } => try_transfer_ft(deps, env, info, from, contract_id, to, tokens), HandleMsg::TransferFtFrom { proxy, contract_id, from, to, tokens, - } => try_transfer_ft_from(deps, env, proxy, contract_id, from, to, tokens), + } => try_transfer_ft_from(deps, env, info, proxy, contract_id, from, to, tokens), HandleMsg::Modify { owner, contract_id, @@ -137,6 +135,7 @@ pub fn handle( } => try_modify( deps, env, + info, owner, contract_id, token_type, @@ -148,99 +147,108 @@ pub fn handle( approver, contract_id, proxy, - } => try_approve(deps, env, approver, contract_id, proxy), + } => try_approve(deps, env, info, approver, contract_id, proxy), HandleMsg::Disapprove { approver, contract_id, proxy, - } => try_disapprove(deps, env, approver, contract_id, proxy), + } => try_disapprove(deps, env, info, approver, contract_id, proxy), HandleMsg::GrantPerm { from, contract_id, to, permission, - } => try_grant_perm(deps, env, from, contract_id, to, permission), + } => try_grant_perm(deps, env, info, from, contract_id, to, permission), HandleMsg::RevokePerm { from, contract_id, permission, - } => try_revoke_perm(deps, env, from, contract_id, permission), + } => try_revoke_perm(deps, env, info, from, contract_id, permission), HandleMsg::Attach { from, contract_id, to_token_id, token_id, - } => try_attach(deps, env, from, contract_id, to_token_id, token_id), + } => try_attach(deps, env, info, from, contract_id, to_token_id, token_id), HandleMsg::Detach { from, contract_id, token_id, - } => try_detach(deps, env, from, contract_id, token_id), + } => try_detach(deps, env, info, from, contract_id, token_id), HandleMsg::AttachFrom { proxy, contract_id, from, to_token_id, token_id, - } => try_attach_from(deps, env, proxy, contract_id, from, to_token_id, token_id), + } => try_attach_from( + deps, + env, + info, + proxy, + contract_id, + from, + to_token_id, + token_id, + ), HandleMsg::DetachFrom { proxy, contract_id, from, token_id, - } => try_detach_from(deps, env, proxy, contract_id, from, token_id), + } => try_detach_from(deps, env, info, proxy, contract_id, from, token_id), } } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetCollection { contract_id } => query_collection(deps, contract_id), + QueryMsg::GetCollection { contract_id } => query_collection(deps, env, contract_id), QueryMsg::GetBalance { contract_id, token_id, addr, - } => query_balance(deps, contract_id, token_id, addr), + } => query_balance(deps, env, contract_id, token_id, addr), QueryMsg::GetTokenType { contract_id, token_id, - } => query_token_type(deps, contract_id, token_id), - QueryMsg::GetTokenTypes { contract_id } => query_token_types(deps, contract_id), + } => query_token_type(deps, env, contract_id, token_id), + QueryMsg::GetTokenTypes { contract_id } => query_token_types(deps, env, contract_id), QueryMsg::GetToken { contract_id, token_id, - } => query_token(deps, contract_id, token_id), - QueryMsg::GetTokens { contract_id } => query_tokens(deps, contract_id), + } => query_token(deps, env, contract_id, token_id), + QueryMsg::GetTokens { contract_id } => query_tokens(deps, env, contract_id), QueryMsg::GetNftCount { contract_id, token_id, target, - } => query_nft_count(deps, contract_id, token_id, target), + } => query_nft_count(deps, env, contract_id, token_id, target), QueryMsg::GetTotal { contract_id, token_id, target, - } => query_total(deps, contract_id, token_id, target), + } => query_total(deps, env, contract_id, token_id, target), QueryMsg::GetRootOrParentOrChildren { contract_id, token_id, target, - } => query_root_or_parent_or_children(deps, contract_id, token_id, target), - QueryMsg::GetPerms { contract_id, addr } => query_perms(deps, contract_id, addr), + } => query_root_or_parent_or_children(deps, env, contract_id, token_id, target), + QueryMsg::GetPerms { contract_id, addr } => query_perms(deps, env, contract_id, addr), QueryMsg::GetApproved { contract_id, proxy, approver, - } => query_is_approved(deps, contract_id, proxy, approver), - QueryMsg::GetApprovers { proxy, contract_id } => query_approvers(deps, proxy, contract_id), + } => query_is_approved(deps, env, contract_id, proxy, approver), + QueryMsg::GetApprovers { proxy, contract_id } => { + query_approvers(deps, env, proxy, contract_id) + } } } -pub fn try_create( - _deps: &mut Extern, +pub fn try_create( + _deps: DepsMut, _env: Env, + _info: MessageInfo, owner: HumanAddr, name: String, meta: String, @@ -265,15 +273,16 @@ pub fn try_create( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "create")], + attributes: vec![attr("action", "create")], data: None, }; Ok(res) } -pub fn try_issue_nft( - _deps: &mut Extern, +pub fn try_issue_nft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, owner: HumanAddr, contract_id: String, name: String, @@ -298,16 +307,17 @@ pub fn try_issue_nft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "issue_nft")], + attributes: vec![attr("action", "issue_nft")], data: None, }; Ok(res) } #[allow(clippy::too_many_arguments)] -pub fn try_issue_ft( - _deps: &mut Extern, +pub fn try_issue_ft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, owner: HumanAddr, contract_id: String, to: HumanAddr, @@ -338,15 +348,16 @@ pub fn try_issue_ft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "issue_ft")], + attributes: vec![attr("action", "issue_ft")], data: None, }; Ok(res) } -pub fn try_mint_nft( - _deps: &mut Extern, +pub fn try_mint_nft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -379,15 +390,16 @@ pub fn try_mint_nft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "mint_nft")], + attributes: vec![attr("action", "mint_nft")], data: None, }; Ok(res) } -pub fn try_mint_ft( - _deps: &mut Extern, +pub fn try_mint_ft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -417,15 +429,16 @@ pub fn try_mint_ft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "mint_ft")], + attributes: vec![attr("action", "mint_ft")], data: None, }; Ok(res) } -pub fn try_burn_nft( - _deps: &mut Extern, +pub fn try_burn_nft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, token_id: String, @@ -448,15 +461,16 @@ pub fn try_burn_nft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "burn_nft")], + attributes: vec![attr("action", "burn_nft")], data: None, }; Ok(res) } -pub fn try_burn_nft_from( - _deps: &mut Extern, +pub fn try_burn_nft_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, contract_id: String, from: HumanAddr, @@ -479,15 +493,16 @@ pub fn try_burn_nft_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "burn_nft_from")], + attributes: vec![attr("action", "burn_nft_from")], data: None, }; Ok(res) } -pub fn try_burn_ft( - _deps: &mut Extern, +pub fn try_burn_ft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, tokens: Vec, @@ -515,15 +530,16 @@ pub fn try_burn_ft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "burn_nft")], + attributes: vec![attr("action", "burn_nft")], data: None, }; Ok(res) } -pub fn try_burn_ft_from( - _deps: &mut Extern, +pub fn try_burn_ft_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, contract_id: String, from: HumanAddr, @@ -553,15 +569,16 @@ pub fn try_burn_ft_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "burn_nft_from")], + attributes: vec![attr("action", "burn_nft_from")], data: None, }; Ok(res) } -pub fn try_transfer_nft( - _deps: &mut Extern, +pub fn try_transfer_nft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -584,15 +601,17 @@ pub fn try_transfer_nft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "transfer_nft")], + attributes: vec![attr("action", "transfer_nft")], data: None, }; Ok(res) } -pub fn try_transfer_nft_from( - _deps: &mut Extern, +#[allow(clippy::too_many_arguments)] +pub fn try_transfer_nft_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, contract_id: String, from: HumanAddr, @@ -617,15 +636,16 @@ pub fn try_transfer_nft_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "transfer_nft_from")], + attributes: vec![attr("action", "transfer_nft_from")], data: None, }; Ok(res) } -pub fn try_transfer_ft( - _deps: &mut Extern, +pub fn try_transfer_ft( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -655,15 +675,17 @@ pub fn try_transfer_ft( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "transfer_ft")], + attributes: vec![attr("action", "transfer_ft")], data: None, }; Ok(res) } -pub fn try_transfer_ft_from( - _deps: &mut Extern, +#[allow(clippy::too_many_arguments)] +pub fn try_transfer_ft_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, contract_id: String, from: HumanAddr, @@ -695,16 +717,17 @@ pub fn try_transfer_ft_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "transfer_ft_from")], + attributes: vec![attr("action", "transfer_ft_from")], data: None, }; Ok(res) } #[allow(clippy::too_many_arguments)] -pub fn try_modify( - _deps: &mut Extern, +pub fn try_modify( + _deps: DepsMut, _env: Env, + _info: MessageInfo, owner: HumanAddr, contract_id: String, token_type: String, @@ -730,15 +753,16 @@ pub fn try_modify( .into(); let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "modify_collection")], + attributes: vec![attr("action", "modify_collection")], data: None, }; Ok(res) } -pub fn try_approve( - _deps: &mut Extern, +pub fn try_approve( + _deps: DepsMut, _env: Env, + _info: MessageInfo, approver: HumanAddr, contract_id: String, proxy: HumanAddr, @@ -758,15 +782,16 @@ pub fn try_approve( .into(); let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "approve")], + attributes: vec![attr("action", "approve")], data: None, }; Ok(res) } -pub fn try_disapprove( - _deps: &mut Extern, +pub fn try_disapprove( + _deps: DepsMut, _env: Env, + _info: MessageInfo, approver: HumanAddr, contract_id: String, proxy: HumanAddr, @@ -786,15 +811,16 @@ pub fn try_disapprove( .into(); let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "approve")], + attributes: vec![attr("action", "approve")], data: None, }; Ok(res) } -pub fn try_grant_perm( - _deps: &mut Extern, +pub fn try_grant_perm( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -818,15 +844,16 @@ pub fn try_grant_perm( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "grant_perm")], + attributes: vec![attr("action", "grant_perm")], data: None, }; Ok(res) } -pub fn try_revoke_perm( - _deps: &mut Extern, +pub fn try_revoke_perm( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, perm_str: String, @@ -848,15 +875,16 @@ pub fn try_revoke_perm( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "revoke_perm")], + attributes: vec![attr("action", "revoke_perm")], data: None, }; Ok(res) } -pub fn try_attach( - _deps: &mut Extern, +pub fn try_attach( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to_token_id: String, @@ -879,15 +907,16 @@ pub fn try_attach( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "attach")], + attributes: vec![attr("action", "attach")], data: None, }; Ok(res) } -pub fn try_detach( - _deps: &mut Extern, +pub fn try_detach( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, token_id: String, @@ -908,15 +937,17 @@ pub fn try_detach( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "detach")], + attributes: vec![attr("action", "detach")], data: None, }; Ok(res) } -pub fn try_attach_from( - _deps: &mut Extern, +#[allow(clippy::too_many_arguments)] +pub fn try_attach_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, contract_id: String, from: HumanAddr, @@ -941,15 +972,16 @@ pub fn try_attach_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "attach_from")], + attributes: vec![attr("action", "attach_from")], data: None, }; Ok(res) } -pub fn try_detach_from( - _deps: &mut Extern, +pub fn try_detach_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, contract_id: String, from: HumanAddr, @@ -972,17 +1004,14 @@ pub fn try_detach_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "detach_from")], + attributes: vec![attr("action", "detach_from")], data: None, }; Ok(res) } -fn query_collection( - deps: &Extern, - contract_id: String, -) -> StdResult { - let res = match LinkCollectionQuerier::new(&deps.querier).query_collection(contract_id)? { +fn query_collection(deps: Deps, _env: Env, contract_id: String) -> StdResult { + let res = match LinkCollectionQuerier::new(deps.querier).query_collection(contract_id)? { Some(collection_response) => collection_response, None => return to_binary(&None::>>), }; @@ -990,79 +1019,72 @@ fn query_collection( Ok(out) } -fn query_balance( - deps: &Extern, +fn query_balance( + deps: Deps, + _env: Env, contract_id: String, token_id: String, addr: HumanAddr, ) -> StdResult { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_balance(contract_id, token_id, addr) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_token_type( - deps: &Extern, +fn query_token_type( + deps: Deps, + _env: Env, contract_id: String, token_id: String, ) -> StdResult { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_token_type(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_token_types( - deps: &Extern, - contract_id: String, -) -> StdResult { - let res = LinkCollectionQuerier::new(&deps.querier) +fn query_token_types(deps: Deps, _env: Env, contract_id: String) -> StdResult { + let res = LinkCollectionQuerier::new(deps.querier) .query_token_types(contract_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_token( - deps: &Extern, - contract_id: String, - token_id: String, -) -> StdResult { - let res = LinkCollectionQuerier::new(&deps.querier) +fn query_token(deps: Deps, _env: Env, contract_id: String, token_id: String) -> StdResult { + let res = LinkCollectionQuerier::new(deps.querier) .query_token(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_tokens( - deps: &Extern, - contract_id: String, -) -> StdResult { - let res = LinkCollectionQuerier::new(&deps.querier) +fn query_tokens(deps: Deps, _env: Env, contract_id: String) -> StdResult { + let res = LinkCollectionQuerier::new(deps.querier) .query_tokens(contract_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_nft_count( - deps: &Extern, +fn query_nft_count( + deps: Deps, + _env: Env, contract_id: String, token_id: String, target: String, ) -> StdResult { let res = match &*target { - "count" => LinkCollectionQuerier::new(&deps.querier) + "count" => LinkCollectionQuerier::new(deps.querier) .query_nft_count(contract_id, token_id) .unwrap(), - "mint" => LinkCollectionQuerier::new(&deps.querier) + "mint" => LinkCollectionQuerier::new(deps.querier) .query_nft_mint(contract_id, token_id) .unwrap(), - "burn" => LinkCollectionQuerier::new(&deps.querier) + "burn" => LinkCollectionQuerier::new(deps.querier) .query_nft_burn(contract_id, token_id) .unwrap(), _ => Uint128(0), @@ -1071,27 +1093,28 @@ fn query_nft_count( Ok(out) } -fn query_total( - deps: &Extern, +fn query_total( + deps: Deps, + _env: Env, contract_id: String, token_id: String, target_str: String, ) -> StdResult { let target = Target::from_str(&target_str).unwrap(); if Target::Supply == target { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_supply(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } else if Target::Mint == target { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_mint(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } else { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_burn(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; @@ -1099,26 +1122,27 @@ fn query_total( } } -fn query_root_or_parent_or_children( - deps: &Extern, +fn query_root_or_parent_or_children( + deps: Deps, + _env: Env, contract_id: String, token_id: String, target: String, ) -> StdResult { if target == "root" { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_root(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } else if target == "parent" { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_parent(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } else { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_children(contract_id, token_id) .unwrap(); let out = to_binary(&res)?; @@ -1126,37 +1150,35 @@ fn query_root_or_parent_or_children( } } -fn query_perms( - deps: &Extern, - contract_id: String, - addr: HumanAddr, -) -> StdResult { - let res = LinkCollectionQuerier::new(&deps.querier) +fn query_perms(deps: Deps, _env: Env, contract_id: String, addr: HumanAddr) -> StdResult { + let res = LinkCollectionQuerier::new(deps.querier) .query_perm(contract_id, addr) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_is_approved( - deps: &Extern, +fn query_is_approved( + deps: Deps, + _env: Env, contract_id: String, proxy: HumanAddr, approver: HumanAddr, ) -> StdResult { - let res = LinkCollectionQuerier::new(&deps.querier) + let res = LinkCollectionQuerier::new(deps.querier) .query_is_approved(contract_id, proxy, approver) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_approvers( - deps: &Extern, +fn query_approvers( + deps: Deps, + _env: Env, proxy: HumanAddr, contract_id: String, ) -> StdResult { - let res = match LinkCollectionQuerier::new(&deps.querier).query_approvers(proxy, contract_id)? { + let res = match LinkCollectionQuerier::new(deps.querier).query_approvers(proxy, contract_id)? { Some(approvers) => approvers, None => return to_binary(&None::>>), }; diff --git a/contracts/collection-tester/src/lib.rs b/contracts/collection-tester/src/lib.rs index 5d3bf59e1..37b3108ac 100644 --- a/contracts/collection-tester/src/lib.rs +++ b/contracts/collection-tester/src/lib.rs @@ -3,38 +3,4 @@ pub mod msg; pub mod state; #[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use cosmwasm_std::{ - do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, - }; - - #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::init::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::handle::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -} +cosmwasm_std::create_entry_points!(contract); diff --git a/contracts/collection-tester/src/state.rs b/contracts/collection-tester/src/state.rs index 471b41e25..9005e710f 100644 --- a/contracts/collection-tester/src/state.rs +++ b/contracts/collection-tester/src/state.rs @@ -11,10 +11,10 @@ pub struct State { pub owner: CanonicalAddr, } -pub fn config(storage: &mut S) -> Singleton { +pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } -pub fn config_read(storage: &S) -> ReadonlySingleton { +pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } diff --git a/contracts/hackatom/.cargo/config b/contracts/hackatom/.cargo/config index 7c115322a..8d4bc738b 100644 --- a/contracts/hackatom/.cargo/config +++ b/contracts/hackatom/.cargo/config @@ -1,6 +1,6 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" +unit-test = "test --lib" integration-test = "test --test integration" schema = "run --example schema" diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index d85a3a083..05f9d5a1c 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -18,28 +18,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "backtrace" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" version = "0.11.0" @@ -113,6 +91,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "clru" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6add52b3696a2015ab5bab92e99e75e8a9eb7a10b76b92dbb3a43f25adbd7629" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -121,7 +105,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -129,18 +113,18 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-std", "serde", @@ -148,8 +132,9 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -158,7 +143,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", @@ -306,12 +291,6 @@ dependencies = [ "generic-array 0.14.3", ] -[[package]] -name = "doc-comment" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807e5847c39ad6a11eac66de492ed1406f76a260eb8656e8740cad9eabc69c27" - [[package]] name = "dynasm" version = "0.5.2" @@ -401,7 +380,7 @@ dependencies = [ [[package]] name = "hackatom" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -410,6 +389,7 @@ dependencies = [ "schemars", "serde", "sha2", + "thiserror", ] [[package]] @@ -548,9 +528,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" @@ -635,12 +615,6 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "rustc_version" version = "0.2.3" @@ -789,28 +763,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" -[[package]] -name = "snafu" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1ec0ae2ed980f26e1ad62e717feb01df90731df56887b5391a2c79f9f6805be" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec32ba84a7a86aeb0bc32fd0c46d31b0285599f68ea72e87eff6127889d99e1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "stable_deref_trait" version = "1.1.1" diff --git a/contracts/hackatom/Cargo.toml b/contracts/hackatom/Cargo.toml index 7bc6c08aa..11a8d6b17 100644 --- a/contracts/hackatom/Cargo.toml +++ b/contracts/hackatom/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hackatom" -version = "0.10.0" +version = "0.12.0" authors = ["Ethan Frey "] edition = "2018" publish = false @@ -33,6 +33,7 @@ cosmwasm-std = { path = "../../packages/std" } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive"] } sha2 = "0.9.1" +thiserror = "1.0" [dev-dependencies] cosmwasm-schema = { path = "../../packages/schema" } diff --git a/contracts/hackatom/schema/query_msg.json b/contracts/hackatom/schema/query_msg.json index 12ecb2c86..8d4d8d5bb 100644 --- a/contracts/hackatom/schema/query_msg.json +++ b/contracts/hackatom/schema/query_msg.json @@ -35,7 +35,7 @@ } }, { - "description": "Recurse will execute a query into itself up to depth-times and return Each step of the recursion may perform some extra work to test gas metering (`work` rounds of sha256 on contract). Contract should be the set to be the address of the original contract, we pass it in as query doesn't have access to env.", + "description": "Recurse will execute a query into itself up to depth-times and return Each step of the recursion may perform some extra work to test gas metering (`work` rounds of sha256 on contract). Now that we have Env, we can auto-calculate the address to recurse into", "type": "object", "required": [ "recurse" @@ -44,14 +44,10 @@ "recurse": { "type": "object", "required": [ - "contract", "depth", "work" ], "properties": { - "contract": { - "$ref": "#/definitions/HumanAddr" - }, "depth": { "type": "integer", "format": "uint32", diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 0e7328056..575eb8817 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -5,10 +5,12 @@ use std::convert::TryInto; use cosmwasm_std::{ from_slice, to_binary, to_vec, AllBalanceResponse, Api, BankMsg, Binary, CanonicalAddr, - Context, Env, Extern, HandleResponse, HumanAddr, InitResponse, MigrateResponse, Querier, - QueryRequest, QueryResponse, StdError, StdResult, Storage, WasmQuery, + Context, Deps, DepsMut, Env, HandleResponse, HumanAddr, InitResponse, MessageInfo, + MigrateResponse, QueryRequest, QueryResponse, StdError, StdResult, WasmQuery, }; +use crate::errors::HackError; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InitMsg { pub verifier: HumanAddr, @@ -67,13 +69,8 @@ pub enum QueryMsg { /// Recurse will execute a query into itself up to depth-times and return /// Each step of the recursion may perform some extra work to test gas metering /// (`work` rounds of sha256 on contract). - /// Contract should be the set to be the address of the original contract, - /// we pass it in as query doesn't have access to env. - Recurse { - depth: u32, - work: u32, - contract: HumanAddr, - }, + /// Now that we have Env, we can auto-calculate the address to recurse into + Recurse { depth: u32, work: u32 }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -89,31 +86,35 @@ pub struct RecurseResponse { pub const CONFIG_KEY: &[u8] = b"config"; -pub fn init( - deps: &mut Extern, - env: Env, +pub fn init( + deps: DepsMut, + _env: Env, + info: MessageInfo, msg: InitMsg, -) -> StdResult { +) -> Result { + deps.api.debug("here we go 🚀"); + deps.storage.set( CONFIG_KEY, &to_vec(&State { verifier: deps.api.canonical_address(&msg.verifier)?, beneficiary: deps.api.canonical_address(&msg.beneficiary)?, - funder: deps.api.canonical_address(&env.message.sender)?, + funder: deps.api.canonical_address(&info.sender)?, })?, ); - // This adds some unrelated log for testing purposes + // This adds some unrelated event attribute for testing purposes let mut ctx = Context::new(); - ctx.add_log("Let the", "hacking begin"); - ctx.try_into() + ctx.add_attribute("Let the", "hacking begin"); + Ok(ctx.try_into()?) } -pub fn migrate( - deps: &mut Extern, +pub fn migrate( + deps: DepsMut, _env: Env, + _info: MessageInfo, msg: MigrateMsg, -) -> StdResult { +) -> Result { let data = deps .storage .get(CONFIG_KEY) @@ -125,39 +126,37 @@ pub fn migrate( Ok(MigrateResponse::default()) } -pub fn handle( - deps: &mut Extern, +pub fn handle( + deps: DepsMut, env: Env, + info: MessageInfo, msg: HandleMsg, -) -> StdResult { +) -> Result { match msg { - HandleMsg::Release {} => do_release(deps, env), + HandleMsg::Release {} => do_release(deps, env, info), HandleMsg::CpuLoop {} => do_cpu_loop(), HandleMsg::StorageLoop {} => do_storage_loop(deps), HandleMsg::MemoryLoop {} => do_memory_loop(), HandleMsg::AllocateLargeMemory {} => do_allocate_large_memory(), HandleMsg::Panic {} => do_panic(), - HandleMsg::UserErrorsInApiCalls {} => do_user_errors_in_api_calls(&deps.api), + HandleMsg::UserErrorsInApiCalls {} => do_user_errors_in_api_calls(deps.api), } } -fn do_release( - deps: &mut Extern, - env: Env, -) -> StdResult { +fn do_release(deps: DepsMut, env: Env, info: MessageInfo) -> Result { let data = deps .storage .get(CONFIG_KEY) .ok_or_else(|| StdError::not_found("State"))?; let state: State = from_slice(&data)?; - if deps.api.canonical_address(&env.message.sender)? == state.verifier { + if deps.api.canonical_address(&info.sender)? == state.verifier { let to_addr = deps.api.human_address(&state.beneficiary)?; let balance = deps.querier.query_all_balances(&env.contract.address)?; let mut ctx = Context::new(); - ctx.add_log("action", "release"); - ctx.add_log("destination", &to_addr); + ctx.add_attribute("action", "release"); + ctx.add_attribute("destination", &to_addr); ctx.add_message(BankMsg::Send { from_address: env.contract.address, to_address: to_addr, @@ -166,11 +165,11 @@ fn do_release( ctx.set_data(&[0xF0, 0x0B, 0xAA]); Ok(ctx.into()) } else { - Err(StdError::unauthorized()) + Err(HackError::Unauthorized {}) } } -fn do_cpu_loop() -> StdResult { +fn do_cpu_loop() -> Result { let mut counter = 0u64; loop { counter += 1; @@ -180,9 +179,7 @@ fn do_cpu_loop() -> StdResult { } } -fn do_storage_loop( - deps: &mut Extern, -) -> StdResult { +fn do_storage_loop(deps: DepsMut) -> Result { let mut test_case = 0u64; loop { deps.storage @@ -191,7 +188,7 @@ fn do_storage_loop( } } -fn do_memory_loop() -> StdResult { +fn do_memory_loop() -> Result { let mut data = vec![1usize]; loop { // add one element @@ -199,7 +196,7 @@ fn do_memory_loop() -> StdResult { } } -fn do_allocate_large_memory() -> StdResult { +fn do_allocate_large_memory() -> Result { // We create memory pages explicitely since Rust's default allocator seems to be clever enough // to not grow memory for unused capacity like `Vec::::with_capacity(100 * 1024 * 1024)`. // Even with std::alloc::alloc the memory did now grow beyond 1.5 MiB. @@ -210,20 +207,20 @@ fn do_allocate_large_memory() -> StdResult { let pages = 1_600; // 100 MiB let ptr = wasm32::memory_grow(0, pages); if ptr == usize::max_value() { - return Err(StdError::generic_err("Error in memory.grow instruction")); + return Err(StdError::generic_err("Error in memory.grow instruction").into()); } Ok(HandleResponse::default()) } #[cfg(not(target_arch = "wasm32"))] - Err(StdError::generic_err("Unsupported architecture")) + Err(StdError::generic_err("Unsupported architecture").into()) } -fn do_panic() -> StdResult { +fn do_panic() -> Result { panic!("This page intentionally faulted"); } -fn do_user_errors_in_api_calls(api: &A) -> StdResult { +fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { // Canonicalize let empty = HumanAddr::from(""); @@ -233,7 +230,8 @@ fn do_user_errors_in_api_calls(api: &A) -> StdResult { return Err(StdError::generic_err(format!( "Unexpected error in do_user_errors_in_api_calls: {:?}", err - ))) + )) + .into()) } } @@ -244,7 +242,8 @@ fn do_user_errors_in_api_calls(api: &A) -> StdResult { return Err(StdError::generic_err(format!( "Unexpected error in do_user_errors_in_api_calls: {:?}", err - ))) + )) + .into()) } } @@ -257,7 +256,8 @@ fn do_user_errors_in_api_calls(api: &A) -> StdResult { return Err(StdError::generic_err(format!( "Unexpected error in do_user_errors_in_api_calls: {:?}", err - ))) + )) + .into()) } } @@ -268,7 +268,8 @@ fn do_user_errors_in_api_calls(api: &A) -> StdResult { return Err(StdError::generic_err(format!( "Unexpected error in do_user_errors_in_api_calls: {:?}", err - ))) + )) + .into()) } } @@ -279,31 +280,25 @@ fn do_user_errors_in_api_calls(api: &A) -> StdResult { return Err(StdError::generic_err(format!( "Unexpected error in do_user_errors_in_api_calls: {:?}", err - ))) + )) + .into()) } } Ok(HandleResponse::default()) } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Verifier {} => to_binary(&query_verifier(deps)?), QueryMsg::OtherBalance { address } => to_binary(&query_other_balance(deps, address)?), - QueryMsg::Recurse { - depth, - work, - contract, - } => to_binary(&query_recurse(deps, depth, work, contract)?), + QueryMsg::Recurse { depth, work } => { + to_binary(&query_recurse(deps, depth, work, env.contract.address)?) + } } } -fn query_verifier( - deps: &Extern, -) -> StdResult { +fn query_verifier(deps: Deps) -> StdResult { let data = deps .storage .get(CONFIG_KEY) @@ -313,16 +308,13 @@ fn query_verifier( Ok(VerifierResponse { verifier: addr }) } -fn query_other_balance( - deps: &Extern, - address: HumanAddr, -) -> StdResult { +fn query_other_balance(deps: Deps, address: HumanAddr) -> StdResult { let amount = deps.querier.query_all_balances(address)?; Ok(AllBalanceResponse { amount }) } -fn query_recurse( - deps: &Extern, +fn query_recurse( + deps: Deps, depth: u32, work: u32, contract: HumanAddr, @@ -343,7 +335,6 @@ fn query_recurse( let req = QueryMsg::Recurse { depth: depth - 1, work, - contract: contract.clone(), }; let query = QueryRequest::Wasm(WasmQuery::Smart { contract_addr: contract, @@ -357,14 +348,14 @@ fn query_recurse( mod tests { use super::*; use cosmwasm_std::testing::{ - mock_dependencies, mock_dependencies_with_balances, mock_env, MOCK_CONTRACT_ADDR, + mock_dependencies, mock_dependencies_with_balances, mock_env, mock_info, MOCK_CONTRACT_ADDR, }; // import trait ReadonlyStorage to get access to read - use cosmwasm_std::{coins, log, ReadonlyStorage, StdError}; + use cosmwasm_std::{attr, coins, Storage}; #[test] fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); let verifier = HumanAddr(String::from("verifies")); let beneficiary = HumanAddr(String::from("benefits")); @@ -379,12 +370,12 @@ mod tests { verifier, beneficiary, }; - let env = mock_env(creator.as_str(), &[]); - let res = init(&mut deps, env, msg).unwrap(); + let info = mock_info(creator.as_str(), &[]); + let res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); - assert_eq!(res.log.len(), 1); - assert_eq!(res.log[0].key, "Let the"); - assert_eq!(res.log[0].value, "hacking begin"); + assert_eq!(res.attributes.len(), 1); + assert_eq!(res.attributes[0].key, "Let the"); + assert_eq!(res.attributes[0].value, "hacking begin"); // it worked, let's check the state let data = deps.storage.get(CONFIG_KEY).expect("no data stored"); @@ -394,7 +385,7 @@ mod tests { #[test] fn init_and_query() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); let verifier = HumanAddr(String::from("verifies")); let beneficiary = HumanAddr(String::from("benefits")); @@ -403,18 +394,18 @@ mod tests { verifier: verifier.clone(), beneficiary, }; - let env = mock_env(creator.as_str(), &[]); - let res = init(&mut deps, env, msg).unwrap(); + let info = mock_info(creator.as_str(), &[]); + let res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // now let's query - let query_response = query_verifier(&deps).unwrap(); + let query_response = query_verifier(deps.as_ref()).unwrap(); assert_eq!(query_response.verifier, verifier); } #[test] fn migrate_verifier() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); let verifier = HumanAddr::from("verifies"); let beneficiary = HumanAddr::from("benefits"); @@ -423,12 +414,12 @@ mod tests { verifier: verifier.clone(), beneficiary, }; - let env = mock_env(creator.as_str(), &[]); - let res = init(&mut deps, env, msg).unwrap(); + let info = mock_info(creator.as_str(), &[]); + let res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // check it is 'verifies' - let query_response = query(&deps, QueryMsg::Verifier {}).unwrap(); + let query_response = query(deps.as_ref(), mock_env(), QueryMsg::Verifier {}).unwrap(); assert_eq!(query_response.as_slice(), b"{\"verifier\":\"verifies\"}"); // change the verifier via migrate @@ -436,12 +427,12 @@ mod tests { let msg = MigrateMsg { verifier: new_verifier.clone(), }; - let env = mock_env(creator.as_str(), &[]); - let res = migrate(&mut deps, env, msg).unwrap(); + let info = mock_info(creator.as_str(), &[]); + let res = migrate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // check it is 'someone else' - let query_response = query_verifier(&deps).unwrap(); + let query_response = query_verifier(deps.as_ref()).unwrap(); assert_eq!(query_response.verifier, new_verifier); } @@ -449,20 +440,20 @@ mod tests { fn querier_callbacks_work() { let rich_addr = HumanAddr::from("foobar"); let rich_balance = coins(10000, "gold"); - let deps = mock_dependencies_with_balances(20, &[(&rich_addr, &rich_balance)]); + let deps = mock_dependencies_with_balances(&[(&rich_addr, &rich_balance)]); // querying with balance gets the balance - let bal = query_other_balance(&deps, rich_addr).unwrap(); + let bal = query_other_balance(deps.as_ref(), rich_addr).unwrap(); assert_eq!(bal.amount, rich_balance); // querying other accounts gets none - let bal = query_other_balance(&deps, HumanAddr::from("someone else")).unwrap(); + let bal = query_other_balance(deps.as_ref(), HumanAddr::from("someone else")).unwrap(); assert_eq!(bal.amount, vec![]); } #[test] fn handle_release_works() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); // initialize the store let creator = HumanAddr::from("creator"); @@ -474,17 +465,22 @@ mod tests { beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_env = mock_env(creator.as_str(), &init_amount); - let contract_addr = init_env.contract.address.clone(); - let init_res = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &init_amount); + let init_res = init(deps.as_mut(), mock_env(), init_info, init_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); // balance changed in init - deps.querier.update_balance(&contract_addr, init_amount); + deps.querier.update_balance(MOCK_CONTRACT_ADDR, init_amount); // beneficiary can release it - let handle_env = mock_env(verifier.as_str(), &[]); - let handle_res = handle(&mut deps, handle_env, HandleMsg::Release {}).unwrap(); + let handle_info = mock_info(verifier.as_str(), &[]); + let handle_res = handle( + deps.as_mut(), + mock_env(), + handle_info, + HandleMsg::Release {}, + ) + .unwrap(); assert_eq!(handle_res.messages.len(), 1); let msg = handle_res.messages.get(0).expect("no message"); assert_eq!( @@ -497,15 +493,15 @@ mod tests { .into(), ); assert_eq!( - handle_res.log, - vec![log("action", "release"), log("destination", "benefits"),], + handle_res.attributes, + vec![attr("action", "release"), attr("destination", "benefits")], ); assert_eq!(handle_res.data, Some(vec![0xF0, 0x0B, 0xAA].into())); } #[test] fn handle_release_fails_for_wrong_sender() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); // initialize the store let creator = HumanAddr::from("creator"); @@ -517,19 +513,23 @@ mod tests { beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_env = mock_env(creator.as_str(), &init_amount); - let contract_addr = init_env.contract.address.clone(); - let init_res = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &init_amount); + let init_res = init(deps.as_mut(), mock_env(), init_info, init_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); // balance changed in init - deps.querier.update_balance(&contract_addr, init_amount); + deps.querier.update_balance(MOCK_CONTRACT_ADDR, init_amount); // beneficiary cannot release it - let handle_env = mock_env(beneficiary.as_str(), &[]); - let handle_res = handle(&mut deps, handle_env, HandleMsg::Release {}); + let handle_info = mock_info(beneficiary.as_str(), &[]); + let handle_res = handle( + deps.as_mut(), + mock_env(), + handle_info, + HandleMsg::Release {}, + ); match handle_res.unwrap_err() { - StdError::Unauthorized { .. } => {} + HackError::Unauthorized { .. } => {} _ => panic!("Expect unauthorized error"), } @@ -549,7 +549,7 @@ mod tests { #[test] #[should_panic(expected = "This page intentionally faulted")] fn handle_panic() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); // initialize the store let verifier = HumanAddr(String::from("verifies")); @@ -560,29 +560,35 @@ mod tests { verifier: verifier.clone(), beneficiary: beneficiary.clone(), }; - let init_env = mock_env(creator.as_str(), &coins(1000, "earth")); - let init_res = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &coins(1000, "earth")); + let init_res = init(deps.as_mut(), mock_env(), init_info, init_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let handle_env = mock_env(beneficiary.as_str(), &[]); + let handle_info = mock_info(beneficiary.as_str(), &[]); // this should panic - let _ = handle(&mut deps, handle_env, HandleMsg::Panic {}); + let _ = handle(deps.as_mut(), mock_env(), handle_info, HandleMsg::Panic {}); } #[test] fn handle_user_errors_in_api_calls() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); let init_msg = InitMsg { verifier: HumanAddr::from("verifies"), beneficiary: HumanAddr::from("benefits"), }; - let init_env = mock_env("creator", &coins(1000, "earth")); - let init_res = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info("creator", &coins(1000, "earth")); + let init_res = init(deps.as_mut(), mock_env(), init_info, init_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let handle_env = mock_env("anyone", &[]); - handle(&mut deps, handle_env, HandleMsg::UserErrorsInApiCalls {}).unwrap(); + let handle_info = mock_info("anyone", &[]); + handle( + deps.as_mut(), + mock_env(), + handle_info, + HandleMsg::UserErrorsInApiCalls {}, + ) + .unwrap(); } #[test] @@ -590,12 +596,12 @@ mod tests { // the test framework doesn't handle contracts querying contracts yet, // let's just make sure the last step looks right - let deps = mock_dependencies(20, &[]); + let deps = mock_dependencies(&[]); let contract = HumanAddr::from("my-contract"); let bin_contract: &[u8] = b"my-contract"; // return the unhashed value here - let no_work_query = query_recurse(&deps, 0, 0, contract.clone()).unwrap(); + let no_work_query = query_recurse(deps.as_ref(), 0, 0, contract.clone()).unwrap(); assert_eq!(no_work_query.hashed, Binary::from(bin_contract)); // let's see if 5 hashes are done right @@ -603,7 +609,7 @@ mod tests { for _ in 0..4 { expected_hash = Sha256::digest(&expected_hash); } - let work_query = query_recurse(&deps, 0, 5, contract).unwrap(); - assert_eq!(work_query.hashed, expected_hash.to_vec().into()); + let work_query = query_recurse(deps.as_ref(), 0, 5, contract).unwrap(); + assert_eq!(work_query.hashed, expected_hash.to_vec()); } } diff --git a/contracts/hackatom/src/errors.rs b/contracts/hackatom/src/errors.rs new file mode 100644 index 000000000..f0d24e42b --- /dev/null +++ b/contracts/hackatom/src/errors.rs @@ -0,0 +1,12 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum HackError { + #[error("{0}")] + /// this is needed so we can use `bucket.load(...)?` and have it auto-converted to the custom error + Std(#[from] StdError), + // this is whatever we want + #[error("Unauthorized")] + Unauthorized {}, +} diff --git a/contracts/hackatom/src/lib.rs b/contracts/hackatom/src/lib.rs index adc3b7f20..7acafa4e1 100644 --- a/contracts/hackatom/src/lib.rs +++ b/contracts/hackatom/src/lib.rs @@ -1,4 +1,5 @@ pub mod contract; +mod errors; #[cfg(target_arch = "wasm32")] cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index 9a15e7a81..234336896 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -18,14 +18,14 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coins, from_binary, log, to_vec, AllBalanceResponse, BankMsg, Empty, HandleResponse, - HandleResult, HumanAddr, InitResponse, InitResult, MigrateResponse, StdError, + attr, coins, from_binary, to_vec, AllBalanceResponse, BankMsg, ContractResult, Empty, + HandleResponse, HumanAddr, InitResponse, MigrateResponse, }; use cosmwasm_vm::{ call_handle, from_slice, testing::{ - handle, init, migrate, mock_env, mock_instance, mock_instance_with_balances, query, - test_io, MOCK_CONTRACT_ADDR, + handle, init, migrate, mock_env, mock_info, mock_instance, mock_instance_with_balances, + query, test_io, MOCK_CONTRACT_ADDR, }, Api, Storage, VmError, }; @@ -65,12 +65,12 @@ fn proper_initialization() { verifier, beneficiary, }; - let env = mock_env("creator", &coins(1000, "earth")); - let res: InitResponse = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(1000, "earth")); + let res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); - assert_eq!(res.log.len(), 1); - assert_eq!(res.log[0].key, "Let the"); - assert_eq!(res.log[0].value, "hacking begin"); + assert_eq!(res.attributes.len(), 1); + assert_eq!(res.attributes[0].key, "Let the"); + assert_eq!(res.attributes[0].value, "hacking begin"); // it worked, let's check the state let state: State = deps @@ -97,20 +97,18 @@ fn init_and_query() { verifier: verifier.clone(), beneficiary, }; - let env = mock_env(creator.as_str(), &coins(1000, "earth")); - let res: InitResponse = init(&mut deps, env, msg).unwrap(); + let info = mock_info(creator.as_str(), &coins(1000, "earth")); + let res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // now let's query - let query_response = query(&mut deps, QueryMsg::Verifier {}).unwrap(); + let query_response = query(&mut deps, mock_env(), QueryMsg::Verifier {}).unwrap(); assert_eq!(query_response.as_slice(), b"{\"verifier\":\"verifies\"}"); // bad query returns parse error (pass wrong type - this connection is not enforced) - let qres = query(&mut deps, HandleMsg::Release {}); - match qres.unwrap_err() { - StdError::ParseErr { .. } => {} - _ => panic!("Expected parse error"), - } + let qres = query(&mut deps, mock_env(), HandleMsg::Release {}); + let msg = qres.unwrap_err(); + assert!(msg.contains("Error parsing")); } #[test] @@ -124,24 +122,24 @@ fn migrate_verifier() { verifier: verifier.clone(), beneficiary, }; - let env = mock_env(creator.as_str(), &[]); - let res: InitResponse = init(&mut deps, env, msg).unwrap(); + let info = mock_info(creator.as_str(), &[]); + let res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // check it is 'verifies' - let query_response = query(&mut deps, QueryMsg::Verifier {}).unwrap(); + let query_response = query(&mut deps, mock_env(), QueryMsg::Verifier {}).unwrap(); assert_eq!(query_response.as_slice(), b"{\"verifier\":\"verifies\"}"); // change the verifier via migrate let msg = MigrateMsg { verifier: HumanAddr::from("someone else"), }; - let env = mock_env(creator.as_str(), &[]); - let res: MigrateResponse = migrate(&mut deps, env, msg).unwrap(); + let info = mock_info(creator.as_str(), &[]); + let res: MigrateResponse = migrate(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // check it is 'someone else' - let query_response = query(&mut deps, QueryMsg::Verifier {}).unwrap(); + let query_response = query(&mut deps, mock_env(), QueryMsg::Verifier {}).unwrap(); assert_eq!( query_response.as_slice(), b"{\"verifier\":\"someone else\"}" @@ -156,7 +154,7 @@ fn querier_callbacks_work() { // querying with balance gets the balance let query_msg = QueryMsg::OtherBalance { address: rich_addr }; - let query_response = query(&mut deps, query_msg).unwrap(); + let query_response = query(&mut deps, mock_env(), query_msg).unwrap(); let bal: AllBalanceResponse = from_binary(&query_response).unwrap(); assert_eq!(bal.amount, rich_balance); @@ -164,7 +162,7 @@ fn querier_callbacks_work() { let query_msg = QueryMsg::OtherBalance { address: HumanAddr::from("someone else"), }; - let query_response = query(&mut deps, query_msg).unwrap(); + let query_response = query(&mut deps, mock_env(), query_msg).unwrap(); let bal: AllBalanceResponse = from_binary(&query_response).unwrap(); assert_eq!(bal.amount, vec![]); } @@ -172,13 +170,12 @@ fn querier_callbacks_work() { #[test] fn fails_on_bad_init() { let mut deps = mock_instance(WASM, &[]); - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); // bad init returns parse error (pass wrong type - this connection is not enforced) - let res: InitResult = init(&mut deps, env, HandleMsg::Release {}); - match res.unwrap_err() { - StdError::ParseErr { .. } => {} - _ => panic!("Expected parse error"), - } + let res: ContractResult = + init(&mut deps, mock_env(), info, HandleMsg::Release {}); + let msg = res.unwrap_err(); + assert!(msg.contains("Error parsing")); } #[test] @@ -195,21 +192,21 @@ fn handle_release_works() { beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_env = mock_env(creator.as_str(), &init_amount); - let contract_addr = init_env.contract.address.clone(); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &init_amount); + let init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); // balance changed in init deps.with_querier(|querier| { - querier.update_balance(&contract_addr, init_amount); + querier.update_balance(MOCK_CONTRACT_ADDR, init_amount); Ok(()) }) .unwrap(); // beneficiary can release it - let handle_env = mock_env(verifier.as_str(), &[]); - let handle_res: HandleResponse = handle(&mut deps, handle_env, HandleMsg::Release {}).unwrap(); + let handle_info = mock_info(verifier.as_str(), &[]); + let handle_res: HandleResponse = + handle(&mut deps, mock_env(), handle_info, HandleMsg::Release {}).unwrap(); assert_eq!(handle_res.messages.len(), 1); let msg = handle_res.messages.get(0).expect("no message"); assert_eq!( @@ -222,8 +219,8 @@ fn handle_release_works() { .into(), ); assert_eq!( - handle_res.log, - vec![log("action", "release"), log("destination", "benefits"),], + handle_res.attributes, + vec![attr("action", "release"), attr("destination", "benefits")], ); assert_eq!(handle_res.data, Some(vec![0xF0, 0x0B, 0xAA].into())); } @@ -242,25 +239,23 @@ fn handle_release_fails_for_wrong_sender() { beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_env = mock_env(creator.as_str(), &init_amount); - let contract_addr = init_env.contract.address.clone(); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &init_amount); + let init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); // balance changed in init deps.with_querier(|querier| { - querier.update_balance(&contract_addr, init_amount); + querier.update_balance(MOCK_CONTRACT_ADDR, init_amount); Ok(()) }) .unwrap(); // beneficiary cannot release it - let handle_env = mock_env(beneficiary.as_str(), &[]); - let handle_res: HandleResult = handle(&mut deps, handle_env, HandleMsg::Release {}); - match handle_res.unwrap_err() { - StdError::Unauthorized { .. } => {} - _ => panic!("Expect unauthorized error"), - } + let handle_info = mock_info(beneficiary.as_str(), &[]); + let handle_res: ContractResult = + handle(&mut deps, mock_env(), handle_info, HandleMsg::Release {}); + let msg = handle_res.unwrap_err(); + assert!(msg.contains("Unauthorized")); // state should not change let data = deps @@ -288,16 +283,17 @@ fn handle_panic() { let mut deps = mock_instance(WASM, &[]); let (init_msg, creator) = make_init_msg(); - let init_env = mock_env(creator.as_str(), &[]); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &[]); + let init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let handle_env = mock_env(creator.as_str(), &[]); + let handle_info = mock_info(creator.as_str(), &[]); // panic inside contract should not panic out here // Note: we need to use the production-call, not the testing call (which unwraps any vm error) let handle_res = call_handle::<_, _, _, Empty>( &mut deps, - &handle_env, + &mock_env(), + &handle_info, &to_vec(&HandleMsg::Panic {}).unwrap(), ); match handle_res.unwrap_err() { @@ -312,12 +308,17 @@ fn handle_user_errors_in_api_calls() { let mut deps = mock_instance(WASM, &[]); let (init_msg, creator) = make_init_msg(); - let init_env = mock_env(creator.as_str(), &[]); - let _init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &[]); + let _init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); - let handle_env = mock_env(creator.as_str(), &[]); - let _handle_res: HandleResponse = - handle(&mut deps, handle_env, HandleMsg::UserErrorsInApiCalls {}).unwrap(); + let handle_info = mock_info(creator.as_str(), &[]); + let _handle_res: HandleResponse = handle( + &mut deps, + mock_env(), + handle_info, + HandleMsg::UserErrorsInApiCalls {}, + ) + .unwrap(); } #[test] @@ -335,15 +336,16 @@ mod singlepass_tests { let mut deps = mock_instance(WASM, &[]); let (init_msg, creator) = make_init_msg(); - let init_env = mock_env(creator.as_str(), &[]); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &[]); + let init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let handle_env = mock_env(creator.as_str(), &[]); + let handle_info = mock_info(creator.as_str(), &[]); // Note: we need to use the production-call, not the testing call (which unwraps any vm error) let handle_res = call_handle::<_, _, _, Empty>( &mut deps, - &handle_env, + &mock_env(), + &handle_info, &to_vec(&HandleMsg::CpuLoop {}).unwrap(), ); assert!(handle_res.is_err()); @@ -355,15 +357,16 @@ mod singlepass_tests { let mut deps = mock_instance(WASM, &[]); let (init_msg, creator) = make_init_msg(); - let init_env = mock_env(creator.as_str(), &[]); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &[]); + let init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let handle_env = mock_env(creator.as_str(), &[]); + let handle_info = mock_info(creator.as_str(), &[]); // Note: we need to use the production-call, not the testing call (which unwraps any vm error) let handle_res = call_handle::<_, _, _, Empty>( &mut deps, - &handle_env, + &mock_env(), + &handle_info, &to_vec(&HandleMsg::StorageLoop {}).unwrap(), ); assert!(handle_res.is_err()); @@ -375,15 +378,16 @@ mod singlepass_tests { let mut deps = mock_instance(WASM, &[]); let (init_msg, creator) = make_init_msg(); - let init_env = mock_env(creator.as_str(), &[]); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &[]); + let init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let handle_env = mock_env(creator.as_str(), &[]); + let handle_info = mock_info(creator.as_str(), &[]); // Note: we need to use the production-call, not the testing call (which unwraps any vm error) let handle_res = call_handle::<_, _, _, Empty>( &mut deps, - &handle_env, + &mock_env(), + &handle_info, &to_vec(&HandleMsg::MemoryLoop {}).unwrap(), ); assert!(handle_res.is_err()); @@ -398,16 +402,17 @@ mod singlepass_tests { let mut deps = mock_instance(WASM, &[]); let (init_msg, creator) = make_init_msg(); - let init_env = mock_env(creator.as_str(), &[]); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + let init_info = mock_info(creator.as_str(), &[]); + let init_res: InitResponse = init(&mut deps, mock_env(), init_info, init_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let handle_env = mock_env(creator.as_str(), &[]); + let handle_info = mock_info(creator.as_str(), &[]); let gas_before = deps.get_gas_left(); // Note: we need to use the production-call, not the testing call (which unwraps any vm error) let handle_res = call_handle::<_, _, _, Empty>( &mut deps, - &handle_env, + &mock_env(), + &handle_info, &to_vec(&HandleMsg::AllocateLargeMemory {}).unwrap(), ); let gas_used = gas_before - deps.get_gas_left(); diff --git a/contracts/queue/.cargo/config b/contracts/queue/.cargo/config index 7c115322a..8d4bc738b 100644 --- a/contracts/queue/.cargo/config +++ b/contracts/queue/.cargo/config @@ -1,6 +1,6 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" +unit-test = "test --lib" integration-test = "test --test integration" schema = "run --example schema" diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 5490255a9..0304bef5c 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -14,31 +14,9 @@ checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "backtrace" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" -dependencies = [ - "cc", - "libc", -] +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base64" @@ -48,9 +26,9 @@ checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "bincode" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" +checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" dependencies = [ "byteorder", "serde", @@ -64,9 +42,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "blake3" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68df31bdf2bbb567e5adf8f21ac125dc0e897b1381e7b841f181521f06fc3134" +checksum = "ce4f9586c9a3151c4b49b19e82ba163dd073614dd057e53c969e1a4db5b52720" dependencies = [ "arrayref", "arrayvec", @@ -74,7 +52,7 @@ dependencies = [ "cfg-if", "constant_time_eq", "crypto-mac", - "digest 0.8.1", + "digest 0.9.0", ] [[package]] @@ -83,7 +61,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", ] [[package]] @@ -94,9 +72,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cc" -version = "1.0.50" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" [[package]] name = "cfg-if" @@ -113,6 +91,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "clru" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6add52b3696a2015ab5bab92e99e75e8a9eb7a10b76b92dbb3a43f25adbd7629" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -121,7 +105,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -129,19 +113,20 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -150,7 +135,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", @@ -251,12 +236,13 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" dependencies = [ "cfg-if", "crossbeam-utils", + "maybe-uninit", ] [[package]] @@ -272,11 +258,11 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.12.3", + "generic-array 0.14.4", "subtle", ] @@ -295,15 +281,9 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", ] -[[package]] -name = "doc-comment" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807e5847c39ad6a11eac66de492ed1406f76a260eb8656e8740cad9eabc69c27" - [[package]] name = "dynasm" version = "0.5.2" @@ -331,15 +311,15 @@ dependencies = [ [[package]] name = "either" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" [[package]] name = "errno" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" +checksum = "6eab5ee3df98a279d9b316b1af6ac95422127b1290317e6d18c1743c99418b01" dependencies = [ "errno-dragonfly", "libc", @@ -373,9 +353,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60fb4bb6bba52f78a471264d9a3b7d026cc0af47b22cd2cffbc0b787ca003e63" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", "version_check", @@ -391,11 +371,20 @@ dependencies = [ "indexmap", ] +[[package]] +name = "hashbrown" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" +dependencies = [ + "autocfg", +] + [[package]] name = "hermit-abi" -version = "0.1.8" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" dependencies = [ "libc", ] @@ -408,19 +397,20 @@ checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" [[package]] name = "indexmap" -version = "1.3.2" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9" dependencies = [ "autocfg", + "hashbrown", "serde", ] [[package]] name = "itoa" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "lazy_static" @@ -430,24 +420,24 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" [[package]] name = "lock_api" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" dependencies = [ "scopeguard", ] [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", ] @@ -470,11 +460,11 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" dependencies = [ - "rustc_version", + "autocfg", ] [[package]] @@ -492,9 +482,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", @@ -527,15 +517,15 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" dependencies = [ "lock_api", "parking_lot_core", @@ -543,9 +533,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" dependencies = [ "cfg-if", "cloudabi", @@ -557,16 +547,16 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.9" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" dependencies = [ "unicode-xid", ] [[package]] name = "queue" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -577,9 +567,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.3" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] @@ -597,10 +587,11 @@ dependencies = [ [[package]] name = "rayon" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" +checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" dependencies = [ + "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -608,9 +599,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" +checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" dependencies = [ "crossbeam-deque", "crossbeam-queue", @@ -621,15 +612,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "rustc_version" @@ -642,15 +627,15 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schemars" -version = "0.7.0" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10db1d3c2260e94570e1a492c762e62c11e1aac8869a2c3d869137995e6810d4" +checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61" dependencies = [ "schemars_derive", "serde", @@ -659,9 +644,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.7.0" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "706d022205b93f710ea83037db4fc103358dd24e61a330d287d45d93fe1dc250" +checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d" dependencies = [ "proc-macro2", "quote", @@ -692,9 +677,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.104" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" dependencies = [ "serde_derive", ] @@ -720,18 +705,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325a073952621257820e7a3469f55ba4726d8b28657e7e36653d1c36dc2c84ae" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" dependencies = [ "proc-macro2", "quote", @@ -751,9 +736,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.48" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" dependencies = [ "itoa", "ryu", @@ -775,49 +760,27 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" - -[[package]] -name = "snafu" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1ec0ae2ed980f26e1ad62e717feb01df90731df56887b5391a2c79f9f6805be" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.6.6" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec32ba84a7a86aeb0bc32fd0c46d31b0285599f68ea72e87eff6127889d99e1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" [[package]] name = "stable_deref_trait" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "subtle" -version = "1.0.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" +checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" [[package]] name = "syn" -version = "1.0.16" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" dependencies = [ "proc-macro2", "quote", @@ -832,18 +795,18 @@ checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" [[package]] name = "thiserror" -version = "1.0.11" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.11" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" dependencies = [ "proc-macro2", "quote", @@ -852,15 +815,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.11.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "version_check" @@ -1003,9 +966,9 @@ checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", diff --git a/contracts/queue/Cargo.toml b/contracts/queue/Cargo.toml index eb6bb77cb..ae277cbe9 100644 --- a/contracts/queue/Cargo.toml +++ b/contracts/queue/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "queue" -version = "0.10.0" +version = "0.12.0" authors = ["Ethan Frey "] edition = "2018" publish = false diff --git a/contracts/queue/examples/schema.rs b/contracts/queue/examples/schema.rs index 5bfa27fac..8f8246c4c 100644 --- a/contracts/queue/examples/schema.rs +++ b/contracts/queue/examples/schema.rs @@ -3,7 +3,9 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use queue::contract::{CountResponse, HandleMsg, InitMsg, Item, QueryMsg, SumResponse}; +use queue::contract::{ + CountResponse, HandleMsg, InitMsg, Item, ListResponse, QueryMsg, SumResponse, +}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -17,4 +19,5 @@ fn main() { export_schema(&schema_for!(Item), &out_dir); export_schema(&schema_for!(CountResponse), &out_dir); export_schema(&schema_for!(SumResponse), &out_dir); + export_schema(&schema_for!(ListResponse), &out_dir); } diff --git a/contracts/queue/schema/list_response.json b/contracts/queue/schema/list_response.json new file mode 100644 index 000000000..8ce9047d8 --- /dev/null +++ b/contracts/queue/schema/list_response.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListResponse", + "type": "object", + "required": [ + "early", + "empty", + "late" + ], + "properties": { + "early": { + "description": "List all IDs lower than 0x20", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "empty": { + "description": "List an empty range, both bounded", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "late": { + "description": "List all IDs starting from 0x20", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } +} diff --git a/contracts/queue/schema/query_msg.json b/contracts/queue/schema/query_msg.json index 3313c79c9..4c6d53d6e 100644 --- a/contracts/queue/schema/query_msg.json +++ b/contracts/queue/schema/query_msg.json @@ -34,6 +34,17 @@ "type": "object" } } + }, + { + "type": "object", + "required": [ + "list" + ], + "properties": { + "list": { + "type": "object" + } + } } ] } diff --git a/contracts/queue/src/contract.rs b/contracts/queue/src/contract.rs index 37acbd969..4812dd3dc 100644 --- a/contracts/queue/src/contract.rs +++ b/contracts/queue/src/contract.rs @@ -2,8 +2,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{ - from_slice, to_binary, to_vec, Api, Binary, Env, Extern, HandleResponse, InitResponse, Order, - Querier, QueryResponse, StdResult, Storage, + from_slice, to_binary, to_vec, Binary, Deps, DepsMut, Env, HandleResponse, InitResponse, + MessageInfo, Order, QueryResponse, StdResult, }; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -33,6 +33,7 @@ pub enum QueryMsg { Sum {}, // Reducer holds open two iterators at once Reducer {}, + List {}, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -52,33 +53,41 @@ pub struct ReducerResponse { pub counters: Vec<(i32, i32)>, } +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct ListResponse { + /// List an empty range, both bounded + pub empty: Vec, + /// List all IDs lower than 0x20 + pub early: Vec, + /// List all IDs starting from 0x20 + pub late: Vec, +} + // init is a no-op, just empty data -pub fn init( - _deps: &mut Extern, +pub fn init( + _deps: DepsMut, _env: Env, + _info: MessageInfo, _msg: InitMsg, ) -> StdResult { Ok(InitResponse::default()) } -pub fn handle( - deps: &mut Extern, - env: Env, +pub fn handle( + deps: DepsMut, + _env: Env, + _info: MessageInfo, msg: HandleMsg, ) -> StdResult { match msg { - HandleMsg::Enqueue { value } => enqueue(deps, env, value), - HandleMsg::Dequeue {} => dequeue(deps, env), + HandleMsg::Enqueue { value } => enqueue(deps, value), + HandleMsg::Dequeue {} => dequeue(deps), } } const FIRST_KEY: u8 = 0; -fn enqueue( - deps: &mut Extern, - _env: Env, - value: i32, -) -> StdResult { +fn enqueue(deps: DepsMut, value: i32) -> StdResult { // find the last element in the queue and extract key let last_item = deps.storage.range(None, None, Order::Descending).next(); @@ -94,10 +103,7 @@ fn enqueue( Ok(HandleResponse::default()) } -fn dequeue( - deps: &mut Extern, - _env: Env, -) -> StdResult { +fn dequeue(deps: DepsMut) -> StdResult { // find the first element in the queue and extract value let first = deps.storage.range(None, None, Order::Ascending).next(); @@ -112,23 +118,21 @@ fn dequeue( } } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Count {} => to_binary(&query_count(deps)?), QueryMsg::Sum {} => to_binary(&query_sum(deps)?), QueryMsg::Reducer {} => to_binary(&query_reducer(deps)?), + QueryMsg::List {} => to_binary(&query_list(deps)?), } } -fn query_count(deps: &Extern) -> StdResult { +fn query_count(deps: Deps) -> StdResult { let count = deps.storage.range(None, None, Order::Ascending).count() as u32; Ok(CountResponse { count }) } -fn query_sum(deps: &Extern) -> StdResult { +fn query_sum(deps: Deps) -> StdResult { let values: StdResult> = deps .storage .range(None, None, Order::Ascending) @@ -138,9 +142,7 @@ fn query_sum(deps: &Extern) -> StdResul Ok(SumResponse { sum }) } -fn query_reducer( - deps: &Extern, -) -> StdResult { +fn query_reducer(deps: Deps) -> StdResult { let mut out: Vec<(i32, i32)> = vec![]; // val: StdResult for val in deps @@ -167,79 +169,200 @@ fn query_reducer( Ok(ReducerResponse { counters: out }) } +/// Does a range query with both bounds set. Not really useful but to debug an issue +/// between VM and Wasm: https://github.com/CosmWasm/cosmwasm/issues/508 +fn query_list(deps: Deps) -> StdResult { + let empty: Vec = deps + .storage + .range(Some(b"large"), Some(b"larger"), Order::Ascending) + .map(|(k, _)| k[0] as u32) + .collect(); + let early: Vec = deps + .storage + .range(None, Some(b"\x20"), Order::Ascending) + .map(|(k, _)| k[0] as u32) + .collect(); + let late: Vec = deps + .storage + .range(Some(b"\x20"), None, Order::Ascending) + .map(|(k, _)| k[0] as u32) + .collect(); + Ok(ListResponse { empty, early, late }) +} + #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::coins; - use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::{coins, from_binary, OwnedDeps}; - fn create_contract() -> (Extern, Env) { - let mut deps = mock_dependencies(20, &coins(1000, "earth")); - let env = mock_env("creator", &coins(1000, "earth")); - let res = init(&mut deps, env.clone(), InitMsg {}).unwrap(); + fn create_contract() -> (OwnedDeps, MessageInfo) { + let mut deps = mock_dependencies(&coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); + let res = init(deps.as_mut(), mock_env(), info.clone(), InitMsg {}).unwrap(); assert_eq!(0, res.messages.len()); - (deps, env) + (deps, info) } - fn get_count(deps: &Extern) -> u32 { + fn get_count(deps: Deps) -> u32 { query_count(deps).unwrap().count } - fn get_sum(deps: &Extern) -> i32 { + fn get_sum(deps: Deps) -> i32 { query_sum(deps).unwrap().sum } #[test] fn init_and_query() { let (deps, _) = create_contract(); - assert_eq!(get_count(&deps), 0); - assert_eq!(get_sum(&deps), 0); + assert_eq!(get_count(deps.as_ref()), 0); + assert_eq!(get_sum(deps.as_ref()), 0); } #[test] fn push_and_query() { - let (mut deps, env) = create_contract(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 25 }).unwrap(); - assert_eq!(get_count(&deps), 1); - assert_eq!(get_sum(&deps), 25); + let (mut deps, info) = create_contract(); + handle( + deps.as_mut(), + mock_env(), + info, + HandleMsg::Enqueue { value: 25 }, + ) + .unwrap(); + assert_eq!(get_count(deps.as_ref()), 1); + assert_eq!(get_sum(deps.as_ref()), 25); } #[test] fn multiple_push() { - let (mut deps, env) = create_contract(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 25 }).unwrap(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 35 }).unwrap(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 45 }).unwrap(); - assert_eq!(get_count(&deps), 3); - assert_eq!(get_sum(&deps), 105); + let (mut deps, info) = create_contract(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 25 }, + ) + .unwrap(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 35 }, + ) + .unwrap(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 45 }, + ) + .unwrap(); + assert_eq!(get_count(deps.as_ref()), 3); + assert_eq!(get_sum(deps.as_ref()), 105); } #[test] fn push_and_pop() { - let (mut deps, env) = create_contract(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 25 }).unwrap(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 17 }).unwrap(); - let res = handle(&mut deps, env.clone(), HandleMsg::Dequeue {}).unwrap(); + let (mut deps, info) = create_contract(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 25 }, + ) + .unwrap(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 17 }, + ) + .unwrap(); + let res = handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Dequeue {}, + ) + .unwrap(); // ensure we popped properly assert!(res.data.is_some()); let data = res.data.unwrap(); let state: Item = from_slice(data.as_slice()).unwrap(); assert_eq!(state.value, 25); - assert_eq!(get_count(&deps), 1); - assert_eq!(get_sum(&deps), 17); + assert_eq!(get_count(deps.as_ref()), 1); + assert_eq!(get_sum(deps.as_ref()), 17); } #[test] fn push_and_reduce() { - let (mut deps, env) = create_contract(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 40 }).unwrap(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 15 }).unwrap(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 85 }).unwrap(); - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: -10 }).unwrap(); - assert_eq!(get_count(&deps), 4); - assert_eq!(get_sum(&deps), 130); - let counters = query_reducer(&deps).unwrap().counters; + let (mut deps, info) = create_contract(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 40 }, + ) + .unwrap(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 15 }, + ) + .unwrap(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 85 }, + ) + .unwrap(); + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: -10 }, + ) + .unwrap(); + assert_eq!(get_count(deps.as_ref()), 4); + assert_eq!(get_sum(deps.as_ref()), 130); + let counters = query_reducer(deps.as_ref()).unwrap().counters; assert_eq!(counters, vec![(40, 85), (15, 125), (85, 0), (-10, 140)]); } + + #[test] + fn query_list() { + let (mut deps, info) = create_contract(); + for _ in 0..0x25 { + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 40 }, + ) + .unwrap(); + } + for _ in 0..0x19 { + handle( + deps.as_mut(), + mock_env(), + info.clone(), + HandleMsg::Dequeue {}, + ) + .unwrap(); + } + // we add 0x25 items and then remove the first 0x19, leaving [0x19, 0x1a, 0x1b, ..., 0x24] + // since we count up to 0x20 in early, we get early and late both with data + + let query_msg = QueryMsg::List {}; + let ids: ListResponse = + from_binary(&query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + assert_eq!(ids.empty, Vec::::new()); + assert_eq!(ids.early, vec![0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f]); + assert_eq!(ids.late, vec![0x20, 0x21, 0x22, 0x23, 0x24]); + } } diff --git a/contracts/queue/tests/integration.rs b/contracts/queue/tests/integration.rs index 33001396d..ae4fcd6bd 100644 --- a/contracts/queue/tests/integration.rs +++ b/contracts/queue/tests/integration.rs @@ -17,35 +17,37 @@ //! }); //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) -use cosmwasm_std::{from_binary, from_slice, Env, HandleResponse, HumanAddr, InitResponse}; +use cosmwasm_std::{from_binary, from_slice, HandleResponse, HumanAddr, InitResponse, MessageInfo}; use cosmwasm_vm::{ - testing::{handle, init, mock_env, mock_instance, query, MockApi, MockQuerier, MockStorage}, + testing::{ + handle, init, mock_env, mock_info, mock_instance, query, MockApi, MockQuerier, MockStorage, + }, Instance, }; use queue::contract::{ - CountResponse, HandleMsg, InitMsg, Item, QueryMsg, ReducerResponse, SumResponse, + CountResponse, HandleMsg, InitMsg, Item, ListResponse, QueryMsg, ReducerResponse, SumResponse, }; static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/queue.wasm"); -fn create_contract() -> (Instance, Env) { +fn create_contract() -> (Instance, MessageInfo) { let mut deps = mock_instance(WASM, &[]); let creator = HumanAddr(String::from("creator")); - let env = mock_env(creator.as_str(), &[]); - let res: InitResponse = init(&mut deps, env.clone(), InitMsg {}).unwrap(); + let info = mock_info(creator.as_str(), &[]); + let res: InitResponse = init(&mut deps, mock_env(), info.clone(), InitMsg {}).unwrap(); assert_eq!(0, res.messages.len()); - (deps, env) + (deps, info) } fn get_count(deps: &mut Instance) -> u32 { - let data = query(deps, QueryMsg::Count {}).unwrap(); + let data = query(deps, mock_env(), QueryMsg::Count {}).unwrap(); let res: CountResponse = from_binary(&data).unwrap(); res.count } fn get_sum(deps: &mut Instance) -> i32 { - let data = query(deps, QueryMsg::Sum {}).unwrap(); + let data = query(deps, mock_env(), QueryMsg::Sum {}).unwrap(); let res: SumResponse = from_binary(&data).unwrap(); res.sum } @@ -59,34 +61,65 @@ fn init_and_query() { #[test] fn push_and_query() { - let (mut deps, env) = create_contract(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 25 }).unwrap(); + let (mut deps, info) = create_contract(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 25 }, + ) + .unwrap(); assert_eq!(get_count(&mut deps), 1); assert_eq!(get_sum(&mut deps), 25); } #[test] fn multiple_push() { - let (mut deps, env) = create_contract(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 25 }).unwrap(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 35 }).unwrap(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 45 }).unwrap(); + let (mut deps, info) = create_contract(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 25 }, + ) + .unwrap(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 35 }, + ) + .unwrap(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 45 }, + ) + .unwrap(); assert_eq!(get_count(&mut deps), 3); assert_eq!(get_sum(&mut deps), 105); } #[test] fn push_and_pop() { - let (mut deps, env) = create_contract(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 25 }).unwrap(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 17 }).unwrap(); - let res: HandleResponse = handle(&mut deps, env.clone(), HandleMsg::Dequeue {}).unwrap(); + let (mut deps, info) = create_contract(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 25 }, + ) + .unwrap(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 17 }, + ) + .unwrap(); + let res: HandleResponse = + handle(&mut deps, mock_env(), info.clone(), HandleMsg::Dequeue {}).unwrap(); // ensure we popped properly assert!(res.data.is_some()); let data = res.data.unwrap(); @@ -99,18 +132,65 @@ fn push_and_pop() { #[test] fn push_and_reduce() { - let (mut deps, env) = create_contract(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 40 }).unwrap(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 15 }).unwrap(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: 85 }).unwrap(); - let _: HandleResponse = - handle(&mut deps, env.clone(), HandleMsg::Enqueue { value: -10 }).unwrap(); + let (mut deps, info) = create_contract(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 40 }, + ) + .unwrap(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 15 }, + ) + .unwrap(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 85 }, + ) + .unwrap(); + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: -10 }, + ) + .unwrap(); assert_eq!(get_count(&mut deps), 4); assert_eq!(get_sum(&mut deps), 130); - let data = query(&mut deps, QueryMsg::Reducer {}).unwrap(); + let data = query(&mut deps, mock_env(), QueryMsg::Reducer {}).unwrap(); let counters = from_binary::(&data).unwrap().counters; assert_eq!(counters, vec![(40, 85), (15, 125), (85, 0), (-10, 140)]); } + +#[test] +fn query_list() { + let (mut deps, info) = create_contract(); + + for _ in 0..0x25 { + let _: HandleResponse = handle( + &mut deps, + mock_env(), + info.clone(), + HandleMsg::Enqueue { value: 40 }, + ) + .unwrap(); + } + for _ in 0..0x19 { + let _: HandleResponse = + handle(&mut deps, mock_env(), info.clone(), HandleMsg::Dequeue {}).unwrap(); + } + // we add 0x25 items and then remove the first 0x19, leaving [0x19, 0x1a, 0x1b, ..., 0x24] + // since we count up to 0x20 in early, we get early and late both with data + + let query_msg = QueryMsg::List {}; + let ids: ListResponse = from_binary(&query(&mut deps, mock_env(), query_msg).unwrap()).unwrap(); + assert_eq!(ids.empty, Vec::::new()); + assert_eq!(ids.early, vec![0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f]); + assert_eq!(ids.late, vec![0x20, 0x21, 0x22, 0x23, 0x24]); +} diff --git a/contracts/reflect/.cargo/config b/contracts/reflect/.cargo/config index 7c115322a..8d4bc738b 100644 --- a/contracts/reflect/.cargo/config +++ b/contracts/reflect/.cargo/config @@ -1,6 +1,6 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" +unit-test = "test --lib" integration-test = "test --test integration" schema = "run --example schema" diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index 15c7db71c..fea4db366 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -18,28 +18,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "backtrace" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" version = "0.11.0" @@ -113,6 +91,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "clru" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6add52b3696a2015ab5bab92e99e75e8a9eb7a10b76b92dbb3a43f25adbd7629" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -121,7 +105,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -129,18 +113,18 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-std", "serde", @@ -148,8 +132,9 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -158,7 +143,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", @@ -306,12 +291,6 @@ dependencies = [ "generic-array 0.14.3", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "dynasm" version = "0.5.2" @@ -535,9 +514,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" @@ -624,7 +603,7 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "reflect" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -632,14 +611,9 @@ dependencies = [ "cosmwasm-vm", "schemars", "serde", + "thiserror", ] -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "rustc_version" version = "0.2.3" @@ -788,28 +762,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" -[[package]] -name = "snafu" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1ec0ae2ed980f26e1ad62e717feb01df90731df56887b5391a2c79f9f6805be" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec32ba84a7a86aeb0bc32fd0c46d31b0285599f68ea72e87eff6127889d99e1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "stable_deref_trait" version = "1.1.1" diff --git a/contracts/reflect/Cargo.toml b/contracts/reflect/Cargo.toml index 878aa0722..3792b344f 100644 --- a/contracts/reflect/Cargo.toml +++ b/contracts/reflect/Cargo.toml @@ -1,18 +1,12 @@ [package] name = "reflect" -version = "0.10.0" +version = "0.12.0" authors = ["Ethan Frey "] -publish = false edition = "2018" +publish = false description = "Reflect messages to use for test cases - based on cw-mask" license = "Apache-2.0" -exclude = [ - # Those files are cosmwasm-opt artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] @@ -42,6 +36,7 @@ cosmwasm-std = { path = "../../packages/std", features = ["staking"] } 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 } diff --git a/contracts/reflect/examples/schema.rs b/contracts/reflect/examples/schema.rs index 30b077178..01cf146a5 100644 --- a/contracts/reflect/examples/schema.rs +++ b/contracts/reflect/examples/schema.rs @@ -4,7 +4,10 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; use cosmwasm_std::HandleResponse; -use reflect::msg::{CustomMsg, HandleMsg, InitMsg, OwnerResponse, QueryMsg}; +use reflect::msg::{ + CapitalizedResponse, ChainResponse, CustomMsg, HandleMsg, InitMsg, OwnerResponse, QueryMsg, + RawResponse, +}; use reflect::state::State; fn main() { @@ -19,5 +22,10 @@ fn main() { export_schema(&schema_for!(HandleResponse), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); export_schema(&schema_for!(State), &out_dir); + + // The possible return types for QueryMsg cases export_schema(&schema_for!(OwnerResponse), &out_dir); + export_schema(&schema_for!(CapitalizedResponse), &out_dir); + export_schema(&schema_for!(ChainResponse), &out_dir); + export_schema(&schema_for!(RawResponse), &out_dir); } diff --git a/contracts/reflect/schema/capitalized_response.json b/contracts/reflect/schema/capitalized_response.json new file mode 100644 index 000000000..9940dba3e --- /dev/null +++ b/contracts/reflect/schema/capitalized_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapitalizedResponse", + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string" + } + } +} diff --git a/contracts/reflect/schema/chain_response.json b/contracts/reflect/schema/chain_response.json new file mode 100644 index 000000000..a836c62bd --- /dev/null +++ b/contracts/reflect/schema/chain_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ChainResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/Binary" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + } + } +} diff --git a/contracts/reflect/schema/handle_response_for__custom_msg.json b/contracts/reflect/schema/handle_response_for__custom_msg.json index 3fc19ddf4..269e4e1a9 100644 --- a/contracts/reflect/schema/handle_response_for__custom_msg.json +++ b/contracts/reflect/schema/handle_response_for__custom_msg.json @@ -3,10 +3,17 @@ "title": "HandleResponse_for_CustomMsg", "type": "object", "required": [ - "log", + "attributes", "messages" ], "properties": { + "attributes": { + "description": "The attributes that will be emitted as part of a \"wasm\" event", + "type": "array", + "items": { + "$ref": "#/definitions/Attribute" + } + }, "data": { "anyOf": [ { @@ -17,12 +24,6 @@ } ] }, - "log": { - "type": "array", - "items": { - "$ref": "#/definitions/LogAttribute" - } - }, "messages": { "type": "array", "items": { @@ -31,6 +32,22 @@ } }, "definitions": { + "Attribute": { + "description": "An key value pair that is used in the context of event attributes in logs", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "BankMsg": { "anyOf": [ { @@ -162,21 +179,6 @@ "HumanAddr": { "type": "string" }, - "LogAttribute": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "StakingMsg": { "anyOf": [ { diff --git a/contracts/reflect/schema/query_msg.json b/contracts/reflect/schema/query_msg.json index 56e3a4fee..d8cde67a9 100644 --- a/contracts/reflect/schema/query_msg.json +++ b/contracts/reflect/schema/query_msg.json @@ -14,12 +14,13 @@ } }, { + "description": "This will call out to SpecialQuery::Capitalized", "type": "object", "required": [ - "reflect_custom" + "capitalized" ], "properties": { - "reflect_custom": { + "capitalized": { "type": "object", "required": [ "text" @@ -31,6 +32,324 @@ } } } + }, + { + "description": "Queries the blockchain and returns the result untouched", + "type": "object", + "required": [ + "chain" + ], + "properties": { + "chain": { + "type": "object", + "required": [ + "request" + ], + "properties": { + "request": { + "$ref": "#/definitions/QueryRequest_for_SpecialQuery" + } + } + } + } + }, + { + "description": "Queries another contract and returns the data", + "type": "object", + "required": [ + "raw" + ], + "properties": { + "raw": { + "type": "object", + "required": [ + "contract", + "key" + ], + "properties": { + "contract": { + "$ref": "#/definitions/HumanAddr" + }, + "key": { + "$ref": "#/definitions/Binary" + } + } + } + } + } + ], + "definitions": { + "BankQuery": { + "anyOf": [ + { + "description": "This calls into the native bank module for one denomination Return value is BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "address", + "denom" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "denom": { + "type": "string" + } + } + } + } + }, + { + "description": "This calls into the native bank module for all denominations. Note that this may be much more expensive than Balance and should be avoided if possible. Return value is AllBalanceResponse.", + "type": "object", + "required": [ + "all_balances" + ], + "properties": { + "all_balances": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "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" + }, + "HumanAddr": { + "type": "string" + }, + "QueryRequest_for_SpecialQuery": { + "anyOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankQuery" + } + } + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/SpecialQuery" + } + } + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingQuery" + } + } + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmQuery" + } + } + } + ] + }, + "SpecialQuery": { + "description": "An implementation of QueryRequest::Custom to show this works and can be extended in the contract", + "anyOf": [ + { + "type": "object", + "required": [ + "ping" + ], + "properties": { + "ping": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "capitalized" + ], + "properties": { + "capitalized": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string" + } + } + } + } + } + ] + }, + "StakingQuery": { + "anyOf": [ + { + "description": "Returns the denomination that can be bonded (if there are multiple native tokens on the chain)", + "type": "object", + "required": [ + "bonded_denom" + ], + "properties": { + "bonded_denom": { + "type": "object" + } + } + }, + { + "description": "AllDelegations will return all delegations by the delegator", + "type": "object", + "required": [ + "all_delegations" + ], + "properties": { + "all_delegations": { + "type": "object", + "required": [ + "delegator" + ], + "properties": { + "delegator": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Delegation will return more detailed info on a particular delegation, defined by delegator/validator pair", + "type": "object", + "required": [ + "delegation" + ], + "properties": { + "delegation": { + "type": "object", + "required": [ + "delegator", + "validator" + ], + "properties": { + "delegator": { + "$ref": "#/definitions/HumanAddr" + }, + "validator": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Returns all registered Validators on the system", + "type": "object", + "required": [ + "validators" + ], + "properties": { + "validators": { + "type": "object" + } + } + } + ] + }, + "WasmQuery": { + "anyOf": [ + { + "description": "this queries the public API of another contract at a known address (with known ABI) return value is whatever the contract returns (caller should know)", + "type": "object", + "required": [ + "smart" + ], + "properties": { + "smart": { + "type": "object", + "required": [ + "contract_addr", + "msg" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/HumanAddr" + }, + "msg": { + "description": "msg is the json-encoded QueryMsg struct", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + } + }, + { + "description": "this queries the raw kv-store of the contract. returns the raw, unparsed data stored at that key, which may be an empty vector if not present", + "type": "object", + "required": [ + "raw" + ], + "properties": { + "raw": { + "type": "object", + "required": [ + "contract_addr", + "key" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/HumanAddr" + }, + "key": { + "description": "Key is the raw key used in the contracts Storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + } + } + ] } - ] + } } diff --git a/contracts/reflect/schema/raw_response.json b/contracts/reflect/schema/raw_response.json new file mode 100644 index 000000000..06d1400f3 --- /dev/null +++ b/contracts/reflect/schema/raw_response.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RawResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "description": "The returned value of the raw query. Empty data can be the result of a non-existent key or an empty value. We cannot differentiate those two cases in cross contract queries.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + } + } +} diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index d83f760a6..9b57656e7 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -1,133 +1,172 @@ use cosmwasm_std::{ - log, to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HumanAddr, InitResponse, - Querier, StdError, StdResult, Storage, + attr, to_binary, to_vec, Binary, ContractResult, CosmosMsg, Deps, DepsMut, Env, HandleResponse, + HumanAddr, InitResponse, MessageInfo, QueryRequest, StdError, StdResult, SystemResult, }; +use crate::errors::ReflectError; use crate::msg::{ - CustomMsg, CustomQuery, CustomResponse, HandleMsg, InitMsg, OwnerResponse, QueryMsg, + CapitalizedResponse, ChainResponse, CustomMsg, HandleMsg, InitMsg, OwnerResponse, QueryMsg, + RawResponse, SpecialQuery, SpecialResponse, }; use crate::state::{config, config_read, State}; -pub fn init( - deps: &mut Extern, - env: Env, +pub fn init( + deps: DepsMut, + _env: Env, + info: MessageInfo, _msg: InitMsg, ) -> StdResult> { let state = State { - owner: deps.api.canonical_address(&env.message.sender)?, + owner: deps.api.canonical_address(&info.sender)?, }; - config(&mut deps.storage).save(&state)?; - + config(deps.storage).save(&state)?; Ok(InitResponse::default()) } -pub fn handle( - deps: &mut Extern, +pub fn handle( + deps: DepsMut, env: Env, + info: MessageInfo, msg: HandleMsg, -) -> StdResult> { +) -> Result, ReflectError> { match msg { - HandleMsg::ReflectMsg { msgs } => try_reflect(deps, env, msgs), - HandleMsg::ChangeOwner { owner } => try_change_owner(deps, env, owner), + HandleMsg::ReflectMsg { msgs } => try_reflect(deps, env, info, msgs), + HandleMsg::ChangeOwner { owner } => try_change_owner(deps, env, info, owner), } } -pub fn try_reflect( - deps: &mut Extern, - env: Env, +pub fn try_reflect( + deps: DepsMut, + _env: Env, + info: MessageInfo, msgs: Vec>, -) -> StdResult> { - let state = config(&mut deps.storage).load()?; - if deps.api.canonical_address(&env.message.sender)? != state.owner { - return Err(StdError::unauthorized()); +) -> Result, ReflectError> { + let state = config(deps.storage).load()?; + + let sender = deps.api.canonical_address(&info.sender)?; + if sender != state.owner { + return Err(ReflectError::NotCurrentOwner { + expected: state.owner, + actual: sender, + }); } + if msgs.is_empty() { - return Err(StdError::generic_err("Must reflect at least one message")); + return Err(ReflectError::MessagesEmpty); } let res = HandleResponse { messages: msgs, - log: vec![log("action", "reflect")], + attributes: vec![attr("action", "reflect")], data: None, }; Ok(res) } -pub fn try_change_owner( - deps: &mut Extern, - env: Env, +pub fn try_change_owner( + deps: DepsMut, + _env: Env, + info: MessageInfo, owner: HumanAddr, -) -> StdResult> { +) -> Result, ReflectError> { let api = deps.api; - config(&mut deps.storage).update(|mut state| { - if api.canonical_address(&env.message.sender)? != state.owner { - return Err(StdError::unauthorized()); + config(deps.storage).update(|mut state| { + let sender = api.canonical_address(&info.sender)?; + if sender != state.owner { + return Err(ReflectError::NotCurrentOwner { + expected: state.owner, + actual: sender, + }); } state.owner = api.canonical_address(&owner)?; Ok(state) })?; Ok(HandleResponse { - log: vec![log("action", "change_owner"), log("owner", owner)], + attributes: vec![attr("action", "change_owner"), attr("owner", owner)], ..HandleResponse::default() }) } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Owner {} => to_binary(&query_owner(deps)?), - QueryMsg::ReflectCustom { text } => to_binary(&query_reflect(deps, text)?), + QueryMsg::Capitalized { text } => to_binary(&query_capitalized(deps, text)?), + QueryMsg::Chain { request } => to_binary(&query_chain(deps, &request)?), + QueryMsg::Raw { contract, key } => to_binary(&query_raw(deps, contract, key)?), } } -fn query_owner(deps: &Extern) -> StdResult { - let state = config_read(&deps.storage).load()?; +fn query_owner(deps: Deps) -> StdResult { + let state = config_read(deps.storage).load()?; let resp = OwnerResponse { owner: deps.api.human_address(&state.owner)?, }; Ok(resp) } -fn query_reflect( - deps: &Extern, - text: String, -) -> StdResult { - let req = CustomQuery::Capital { text }.into(); - deps.querier.custom_query(&req) +fn query_capitalized(deps: Deps, text: String) -> StdResult { + let req = SpecialQuery::Capitalized { text }.into(); + let response: SpecialResponse = deps.querier.custom_query(&req)?; + Ok(CapitalizedResponse { text: response.msg }) +} + +fn query_chain(deps: Deps, request: &QueryRequest) -> StdResult { + let raw = to_vec(request).map_err(|serialize_err| { + StdError::generic_err(format!("Serializing QueryRequest: {}", serialize_err)) + })?; + match deps.querier.raw_query(&raw) { + SystemResult::Err(system_err) => Err(StdError::generic_err(format!( + "Querier system error: {}", + system_err + ))), + SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::generic_err(format!( + "Querier contract error: {}", + contract_err + ))), + SystemResult::Ok(ContractResult::Ok(value)) => Ok(ChainResponse { data: value }), + } +} + +fn query_raw(deps: Deps, contract: HumanAddr, key: Binary) -> StdResult { + let response: Option> = deps.querier.query_wasm_raw(contract, key)?; + Ok(RawResponse { + data: response.unwrap_or_default().into(), + }) } #[cfg(test)] mod tests { use super::*; use crate::testing::mock_dependencies_with_custom_querier; - use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{coin, coins, BankMsg, Binary, StakingMsg, StdError}; + use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; + use cosmwasm_std::{ + coin, coins, from_binary, AllBalanceResponse, Api, BankMsg, BankQuery, Binary, StakingMsg, + StdError, + }; #[test] fn proper_initialization() { - let mut deps = mock_dependencies_with_custom_querier(20, &[]); + let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success - let res = init(&mut deps, env, msg).unwrap(); + let res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // it worked, let's query the state - let value = query_owner(&deps).unwrap(); + let value = query_owner(deps.as_ref()).unwrap(); assert_eq!("creator", value.owner.as_str()); } #[test] fn reflect() { - let mut deps = mock_dependencies_with_custom_querier(20, &[]); + let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); let payload = vec![BankMsg::Send { from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), @@ -139,18 +178,18 @@ mod tests { let msg = HandleMsg::ReflectMsg { msgs: payload.clone(), }; - let env = mock_env("creator", &[]); - let res = handle(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &[]); + let res = handle(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(payload, res.messages); } #[test] fn reflect_requires_owner() { - let mut deps = mock_dependencies_with_custom_querier(20, &[]); + let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); // signer is not owner let payload = vec![BankMsg::Send { @@ -163,44 +202,42 @@ mod tests { msgs: payload.clone(), }; - let env = mock_env("random", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), + let info = mock_info("random", &[]); + let res = handle(deps.as_mut(), mock_env(), info, msg); + match res.unwrap_err() { + ReflectError::NotCurrentOwner { .. } => {} + err => panic!("Unexpected error: {:?}", err), } } #[test] fn reflect_reject_empty_msgs() { - let mut deps = mock_dependencies_with_custom_querier(20, &[]); + let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); - let env = mock_env("creator", &[]); + let info = mock_info("creator", &[]); let payload = vec![]; let msg = HandleMsg::ReflectMsg { msgs: payload.clone(), }; - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "Must reflect at least one message") - } - _ => panic!("Must return contract error"), + let res = handle(deps.as_mut(), mock_env(), info, msg); + match res.unwrap_err() { + ReflectError::MessagesEmpty => {} + err => panic!("Unexpected error: {:?}", err), } } #[test] fn reflect_multiple_messages() { - let mut deps = mock_dependencies_with_custom_querier(20, &[]); + let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); let payload = vec![ BankMsg::Send { @@ -222,59 +259,115 @@ mod tests { let msg = HandleMsg::ReflectMsg { msgs: payload.clone(), }; - let env = mock_env("creator", &[]); - let res = handle(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &[]); + let res = handle(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(payload, res.messages); } #[test] - fn transfer() { - let mut deps = mock_dependencies_with_custom_querier(20, &[]); + fn change_owner_works() { + let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); - let env = mock_env("creator", &[]); + let info = mock_info("creator", &[]); let new_owner = HumanAddr::from("friend"); let msg = HandleMsg::ChangeOwner { owner: new_owner.clone(), }; - let res = handle(&mut deps, env, msg).unwrap(); + let res = handle(deps.as_mut(), mock_env(), info, msg).unwrap(); // should change state assert_eq!(0, res.messages.len()); - let value = query_owner(&deps).unwrap(); + let value = query_owner(deps.as_ref()).unwrap(); assert_eq!("friend", value.owner.as_str()); } #[test] - fn transfer_requires_owner() { - let mut deps = mock_dependencies_with_custom_querier(20, &[]); + fn change_owner_requires_current_owner_as_sender() { + let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res = init(&mut deps, env, msg).unwrap(); + let creator = HumanAddr::from("creator"); + let info = mock_info(&creator, &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); - let env = mock_env("random", &[]); + let random = HumanAddr::from("random"); + let info = mock_info(&random, &[]); let new_owner = HumanAddr::from("friend"); let msg = HandleMsg::ChangeOwner { owner: new_owner.clone(), }; - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), + let res = handle(deps.as_mut(), mock_env(), info, msg); + match res.unwrap_err() { + ReflectError::NotCurrentOwner { expected, actual } => { + assert_eq!(expected, deps.api.canonical_address(&creator).unwrap()); + assert_eq!(actual, deps.api.canonical_address(&random).unwrap()); + } + err => panic!("Unexpected error: {:?}", err), + } + } + + #[test] + fn change_owner_errors_for_invalid_new_address() { + let mut deps = mock_dependencies_with_custom_querier(&[]); + let creator = HumanAddr::from("creator"); + + let msg = InitMsg {}; + let info = mock_info(&creator, &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let info = mock_info(&creator, &[]); + let msg = HandleMsg::ChangeOwner { + owner: HumanAddr::from("x"), + }; + let res = handle(deps.as_mut(), mock_env(), info, msg); + match res.unwrap_err() { + ReflectError::Std(StdError::GenericErr { msg, .. }) => { + assert!(msg.contains("human address too short")) + } + err => panic!("Unexpected error: {:?}", err), } } #[test] - fn dispatch_custom_query() { - let deps = mock_dependencies_with_custom_querier(20, &[]); + fn capitalized_query_works() { + let deps = mock_dependencies_with_custom_querier(&[]); - // we don't even initialize, just trigger a query - let value = query_reflect(&deps, "demo one".to_string()).unwrap(); - assert_eq!(value.msg, "DEMO ONE"); + let msg = QueryMsg::Capitalized { + text: "demo one".to_string(), + }; + let response = query(deps.as_ref(), mock_env(), msg).unwrap(); + let value: CapitalizedResponse = from_binary(&response).unwrap(); + assert_eq!(value.text, "DEMO ONE"); + } + + #[test] + fn chain_query_works() { + let deps = mock_dependencies_with_custom_querier(&coins(123, "ucosm")); + + // with bank query + let msg = QueryMsg::Chain { + request: BankQuery::AllBalances { + address: HumanAddr::from(MOCK_CONTRACT_ADDR), + } + .into(), + }; + let response = query(deps.as_ref(), mock_env(), msg).unwrap(); + let outer: ChainResponse = from_binary(&response).unwrap(); + let inner: AllBalanceResponse = from_binary(&outer.data).unwrap(); + assert_eq!(inner.amount, coins(123, "ucosm")); + + // with custom query + let msg = QueryMsg::Chain { + request: SpecialQuery::Ping {}.into(), + }; + let response = query(deps.as_ref(), mock_env(), msg).unwrap(); + let outer: ChainResponse = from_binary(&response).unwrap(); + let inner: SpecialResponse = from_binary(&outer.data).unwrap(); + assert_eq!(inner.msg, "pong"); } } diff --git a/contracts/reflect/src/errors.rs b/contracts/reflect/src/errors.rs new file mode 100644 index 000000000..986e93914 --- /dev/null +++ b/contracts/reflect/src/errors.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::{CanonicalAddr, StdError}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ReflectError { + #[error("{0}")] + // let thiserror implement From for you + Std(#[from] StdError), + // this is whatever we want + #[error("Permission denied: the sender is not the current owner")] + NotCurrentOwner { + expected: CanonicalAddr, + actual: CanonicalAddr, + }, + #[error("Messages empty. Must reflect at least one message")] + MessagesEmpty, +} diff --git a/contracts/reflect/src/lib.rs b/contracts/reflect/src/lib.rs index bbfad1a2c..12ad05f39 100644 --- a/contracts/reflect/src/lib.rs +++ b/contracts/reflect/src/lib.rs @@ -1,4 +1,5 @@ pub mod contract; +mod errors; pub mod msg; pub mod state; diff --git a/contracts/reflect/src/msg.rs b/contracts/reflect/src/msg.rs index f6c7f91a9..c16bc3f64 100644 --- a/contracts/reflect/src/msg.rs +++ b/contracts/reflect/src/msg.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, CosmosMsg, HumanAddr, QueryRequest}; +use cosmwasm_std::{Binary, CosmosMsg, CustomQuery, HumanAddr, QueryRequest}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InitMsg {} @@ -17,16 +17,49 @@ pub enum HandleMsg { #[serde(rename_all = "snake_case")] pub enum QueryMsg { Owner {}, - // this will call out to CustomQuery::Capitalize - ReflectCustom { text: String }, + /// This will call out to SpecialQuery::Capitalized + Capitalized { + text: String, + }, + /// Queries the blockchain and returns the result untouched + Chain { + request: QueryRequest, + }, + /// Queries another contract and returns the data + Raw { + contract: HumanAddr, + key: Binary, + }, } // We define a custom struct for each query response + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct OwnerResponse { pub owner: HumanAddr, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CapitalizedResponse { + pub text: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ChainResponse { + pub data: Binary, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct RawResponse { + /// The returned value of the raw query. Empty data can be the + /// result of a non-existent key or an empty value. We cannot + /// differentiate those two cases in cross contract queries. + pub data: Binary, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] /// CustomMsg is an override of CosmosMsg::Custom to show this works and can be extended in the contract @@ -43,22 +76,17 @@ impl Into> for CustomMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -/// CustomQuery is an override of QueryRequest::Custom to show this works and can be extended in the contract -pub enum CustomQuery { +/// An implementation of QueryRequest::Custom to show this works and can be extended in the contract +pub enum SpecialQuery { Ping {}, - Capital { text: String }, + Capitalized { text: String }, } -// TODO: do we want to standardize this somehow for all? -impl Into> for CustomQuery { - fn into(self) -> QueryRequest { - QueryRequest::Custom(self) - } -} +impl CustomQuery for SpecialQuery {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -// All return values of CustomQuery are CustomResponse -pub struct CustomResponse { +/// The response data for all `SpecialQuery`s +pub struct SpecialResponse { pub msg: String, } diff --git a/contracts/reflect/src/state.rs b/contracts/reflect/src/state.rs index 8bbade309..5c7f5a506 100644 --- a/contracts/reflect/src/state.rs +++ b/contracts/reflect/src/state.rs @@ -11,10 +11,10 @@ pub struct State { pub owner: CanonicalAddr, } -pub fn config(storage: &mut S) -> Singleton { +pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } -pub fn config_read(storage: &S) -> ReadonlySingleton { +pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } diff --git a/contracts/reflect/src/testing.rs b/contracts/reflect/src/testing.rs index c5189bb35..09542caea 100644 --- a/contracts/reflect/src/testing.rs +++ b/contracts/reflect/src/testing.rs @@ -1,63 +1,63 @@ -use crate::msg::{CustomQuery, CustomResponse}; +use crate::msg::{SpecialQuery, SpecialResponse}; use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{to_binary, Binary, Coin, Extern, HumanAddr, StdResult}; +use cosmwasm_std::{to_binary, Binary, Coin, ContractResult, HumanAddr, OwnedDeps, SystemResult}; /// A drop-in replacement for cosmwasm_std::testing::mock_dependencies /// this uses our CustomQuerier. pub fn mock_dependencies_with_custom_querier( - canonical_length: usize, contract_balance: &[Coin], -) -> Extern> { +) -> OwnedDeps> { let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: MockQuerier = + let custom_querier: MockQuerier = MockQuerier::new(&[(&contract_addr, contract_balance)]) - .with_custom_handler(|query| Ok(custom_query_execute(&query))); - Extern { + .with_custom_handler(|query| SystemResult::Ok(custom_query_execute(&query))); + OwnedDeps { storage: MockStorage::default(), - api: MockApi::new(canonical_length), + api: MockApi::default(), querier: custom_querier, } } -pub fn custom_query_execute(query: &CustomQuery) -> StdResult { +pub fn custom_query_execute(query: &SpecialQuery) -> ContractResult { let msg = match query { - CustomQuery::Ping {} => "pong".to_string(), - CustomQuery::Capital { text } => text.to_uppercase(), + SpecialQuery::Ping {} => "pong".to_string(), + SpecialQuery::Capitalized { text } => text.to_uppercase(), }; - to_binary(&CustomResponse { msg }) + to_binary(&SpecialResponse { msg }).into() } #[cfg(test)] mod test { use super::*; - use cosmwasm_std::{from_binary, Querier, QueryRequest}; + use cosmwasm_std::{from_binary, QuerierWrapper, QueryRequest}; #[test] fn custom_query_execute_ping() { - let res = custom_query_execute(&CustomQuery::Ping {}).unwrap(); - let response: CustomResponse = from_binary(&res).unwrap(); + let res = custom_query_execute(&SpecialQuery::Ping {}).unwrap(); + let response: SpecialResponse = from_binary(&res).unwrap(); assert_eq!(response.msg, "pong"); } #[test] fn custom_query_execute_capitalize() { - let res = custom_query_execute(&CustomQuery::Capital { + let res = custom_query_execute(&SpecialQuery::Capitalized { text: "fOObaR".to_string(), }) .unwrap(); - let response: CustomResponse = from_binary(&res).unwrap(); + let response: SpecialResponse = from_binary(&res).unwrap(); assert_eq!(response.msg, "FOOBAR"); } #[test] fn custom_querier() { - let deps = mock_dependencies_with_custom_querier(20, &[]); - let req: QueryRequest<_> = CustomQuery::Capital { + let deps = mock_dependencies_with_custom_querier(&[]); + let req: QueryRequest<_> = SpecialQuery::Capitalized { text: "food".to_string(), } .into(); - let response: CustomResponse = deps.querier.custom_query(&req).unwrap(); + let wrapper = QuerierWrapper::new(&deps.querier); + let response: SpecialResponse = wrapper.custom_query(&req).unwrap(); assert_eq!(response.msg, "FOOD"); } } diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs index 328e71c4f..a1b9492a6 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -18,19 +18,19 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coin, coins, from_binary, BankMsg, Binary, Coin, HandleResponse, HandleResult, HumanAddr, - InitResponse, StakingMsg, StdError, + coin, coins, from_binary, BankMsg, Binary, Coin, ContractResult, HandleResponse, HumanAddr, + InitResponse, StakingMsg, SystemResult, }; use cosmwasm_vm::{ testing::{ - handle, init, mock_env, mock_instance, query, MockApi, MockQuerier, MockStorage, - MOCK_CONTRACT_ADDR, + handle, init, mock_env, mock_info, mock_instance, mock_instance_options, query, MockApi, + MockQuerier, MockStorage, MOCK_CONTRACT_ADDR, }, - Extern, Instance, + Backend, Instance, }; use reflect::msg::{ - CustomMsg, CustomQuery, CustomResponse, HandleMsg, InitMsg, OwnerResponse, QueryMsg, + CapitalizedResponse, CustomMsg, HandleMsg, InitMsg, OwnerResponse, QueryMsg, SpecialQuery, }; use reflect::testing::custom_query_execute; @@ -40,19 +40,18 @@ static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/re // static WASM: &[u8] = include_bytes!("../contract.wasm"); /// A drop-in replacement for cosmwasm_vm::testing::mock_dependencies -/// that supports CustomQuery. +/// that supports SpecialQuery. pub fn mock_dependencies_with_custom_querier( - canonical_length: usize, contract_balance: &[Coin], -) -> Extern> { +) -> Backend> { let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: MockQuerier = + let custom_querier: MockQuerier = MockQuerier::new(&[(&contract_addr, contract_balance)]) - .with_custom_handler(|query| Ok(custom_query_execute(query))); + .with_custom_handler(|query| SystemResult::Ok(custom_query_execute(query))); - Extern { + Backend { storage: MockStorage::default(), - api: MockApi::new(canonical_length), + api: MockApi::default(), querier: custom_querier, } } @@ -62,14 +61,14 @@ fn proper_initialization() { let mut deps = mock_instance(WASM, &[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success - let res: InitResponse = init(&mut deps, env, msg).unwrap(); + let res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // it worked, let's query the state - let res = query(&mut deps, QueryMsg::Owner {}).unwrap(); + let res = query(&mut deps, mock_env(), QueryMsg::Owner {}).unwrap(); let value: OwnerResponse = from_binary(&res).unwrap(); assert_eq!("creator", value.owner.as_str()); } @@ -79,8 +78,8 @@ fn reflect() { let mut deps = mock_instance(WASM, &[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); let payload = vec![ BankMsg::Send { @@ -101,8 +100,8 @@ fn reflect() { let msg = HandleMsg::ReflectMsg { msgs: payload.clone(), }; - let env = mock_env("creator", &[]); - let res: HandleResponse = handle(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &[]); + let res: HandleResponse = handle(&mut deps, mock_env(), info, msg).unwrap(); // should return payload assert_eq!(payload, res.messages); @@ -113,8 +112,8 @@ fn reflect_requires_owner() { let mut deps = mock_instance(WASM, &[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); // signer is not owner let payload = vec![BankMsg::Send { @@ -127,12 +126,10 @@ fn reflect_requires_owner() { msgs: payload.clone(), }; - let env = mock_env("someone", &[]); - let res: HandleResult = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } + let info = mock_info("someone", &[]); + let res: ContractResult> = handle(&mut deps, mock_env(), info, msg); + let msg = res.unwrap_err(); + assert!(msg.contains("Permission denied: the sender is not the current owner")); } #[test] @@ -140,19 +137,19 @@ fn transfer() { let mut deps = mock_instance(WASM, &[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); - let env = mock_env("creator", &[]); + let info = mock_info("creator", &[]); let new_owner = HumanAddr::from("friend"); let msg = HandleMsg::ChangeOwner { owner: new_owner.clone(), }; - let res: HandleResponse = handle(&mut deps, env, msg).unwrap(); + let res: HandleResponse = handle(&mut deps, mock_env(), info, msg).unwrap(); // should change state assert_eq!(0, res.messages.len()); - let res = query(&mut deps, QueryMsg::Owner {}).unwrap(); + let res = query(&mut deps, mock_env(), QueryMsg::Owner {}).unwrap(); let value: OwnerResponse = from_binary(&res).unwrap(); assert_eq!("friend", value.owner.as_str()); } @@ -162,37 +159,36 @@ fn transfer_requires_owner() { let mut deps = mock_instance(WASM, &[]); let msg = InitMsg {}; - let env = mock_env("creator", &coins(2, "token")); - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); + let info = mock_info("creator", &coins(2, "token")); + let _res: InitResponse = init(&mut deps, mock_env(), info, msg).unwrap(); - let env = mock_env("random", &[]); + let info = mock_info("random", &[]); let new_owner = HumanAddr::from("friend"); let msg = HandleMsg::ChangeOwner { owner: new_owner.clone(), }; - let res: HandleResult = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } + let res: ContractResult = handle(&mut deps, mock_env(), info, msg); + let msg = res.unwrap_err(); + assert!(msg.contains("Permission denied: the sender is not the current owner")); } #[test] fn dispatch_custom_query() { // stub gives us defaults. Consume it and override... - let custom = mock_dependencies_with_custom_querier(20, &[]); + let custom = mock_dependencies_with_custom_querier(&[]); // we cannot use mock_instance, so we just copy and modify code from cosmwasm_vm::testing - let mut deps = Instance::from_code(WASM, custom, 500_000).unwrap(); + let mut deps = Instance::from_code(WASM, custom, mock_instance_options()).unwrap(); // we don't even initialize, just trigger a query let res = query( &mut deps, - QueryMsg::ReflectCustom { + mock_env(), + QueryMsg::Capitalized { text: "demo one".to_string(), }, ) .unwrap(); - let value: CustomResponse = from_binary(&res).unwrap(); - assert_eq!(value.msg, "DEMO ONE"); + let value: CapitalizedResponse = from_binary(&res).unwrap(); + assert_eq!(value.text, "DEMO ONE"); } diff --git a/contracts/staking/.cargo/config b/contracts/staking/.cargo/config index 7c115322a..8d4bc738b 100644 --- a/contracts/staking/.cargo/config +++ b/contracts/staking/.cargo/config @@ -1,6 +1,6 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" +unit-test = "test --lib" integration-test = "test --test integration" schema = "run --example schema" diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index c981d3624..22438bba9 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -18,28 +18,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "backtrace" -version = "0.3.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fbebbe1c9d1f383a9cc7e8ccdb471b91c8d024ee9c2ca5b5346121fe8b4399" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" version = "0.11.0" @@ -113,6 +91,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "clru" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6add52b3696a2015ab5bab92e99e75e8a9eb7a10b76b92dbb3a43f25adbd7629" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -121,7 +105,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -129,18 +113,18 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-std", "serde", @@ -148,8 +132,9 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -158,7 +143,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", @@ -535,9 +520,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" @@ -622,12 +607,6 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "rustc_version" version = "0.2.3" @@ -782,7 +761,6 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a46f872f9a2ee1ad10a6d18003ab012a235c8f5bb90fa986202231a62e570575" dependencies = [ - "backtrace", "doc-comment", "snafu-derive", ] @@ -806,7 +784,7 @@ checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" [[package]] name = "staking" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/staking/Cargo.toml b/contracts/staking/Cargo.toml index be5cfde47..c5b588440 100644 --- a/contracts/staking/Cargo.toml +++ b/contracts/staking/Cargo.toml @@ -1,14 +1,9 @@ [package] name = "staking" -version = "0.10.0" +version = "0.12.0" authors = ["Ethan Frey "] edition = "2018" - -exclude = [ - # Those files are cosmwasm-opt artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -39,7 +34,7 @@ cosmwasm-std = { path = "../../packages/std", features = ["staking"] } cosmwasm-storage = { path = "../../packages/storage" } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -snafu = { version = "0.6.3" } +snafu = "0.6" [dev-dependencies] cosmwasm-vm = { path = "../../packages/vm", default-features = false, features = ["staking"] } diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 1333bc4ad..21d01c621 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -1,8 +1,9 @@ use cosmwasm_std::{ - coin, log, to_binary, Api, BankMsg, Binary, Decimal, Env, Extern, HandleResponse, HumanAddr, - InitResponse, Querier, StakingMsg, StdError, StdResult, Storage, Uint128, WasmMsg, + attr, coin, to_binary, BankMsg, Binary, Decimal, Deps, DepsMut, Env, HandleResponse, HumanAddr, + InitResponse, MessageInfo, QuerierWrapper, StakingMsg, StdError, StdResult, Uint128, WasmMsg, }; +use crate::errors::{StakingError, Unauthorized}; use crate::msg::{ BalanceResponse, ClaimsResponse, HandleMsg, InitMsg, InvestmentResponse, QueryMsg, TokenInfoResponse, @@ -14,11 +15,7 @@ use crate::state::{ const FALLBACK_RATIO: Decimal = Decimal::one(); -pub fn init( - deps: &mut Extern, - env: Env, - msg: InitMsg, -) -> StdResult { +pub fn init(deps: DepsMut, _env: Env, info: MessageInfo, msg: InitMsg) -> StdResult { // ensure the validator is registered let vals = deps.querier.query_validators()?; if !vals.iter().any(|v| v.address == msg.validator) { @@ -33,64 +30,68 @@ pub fn init( symbol: msg.symbol, decimals: msg.decimals, }; - token_info(&mut deps.storage).save(&token)?; + token_info(deps.storage).save(&token)?; let denom = deps.querier.query_bonded_denom()?; let invest = InvestmentInfo { - owner: deps.api.canonical_address(&env.message.sender)?, + owner: deps.api.canonical_address(&info.sender)?, exit_tax: msg.exit_tax, bond_denom: denom, validator: msg.validator, min_withdrawal: msg.min_withdrawal, }; - invest_info(&mut deps.storage).save(&invest)?; + invest_info(deps.storage).save(&invest)?; // set supply to 0 let supply = Supply::default(); - total_supply(&mut deps.storage).save(&supply)?; + total_supply(deps.storage).save(&supply)?; Ok(InitResponse::default()) } -pub fn handle( - deps: &mut Extern, +pub fn handle( + deps: DepsMut, env: Env, + info: MessageInfo, msg: HandleMsg, -) -> StdResult { +) -> Result { match msg { - HandleMsg::Transfer { recipient, amount } => transfer(deps, env, recipient, amount), - HandleMsg::Bond {} => bond(deps, env), - HandleMsg::Unbond { amount } => unbond(deps, env, amount), - HandleMsg::Claim {} => claim(deps, env), - HandleMsg::Reinvest {} => reinvest(deps, env), - HandleMsg::_BondAllTokens {} => _bond_all_tokens(deps, env), + HandleMsg::Transfer { recipient, amount } => { + Ok(transfer(deps, env, info, recipient, amount)?) + } + HandleMsg::Bond {} => Ok(bond(deps, env, info)?), + HandleMsg::Unbond { amount } => Ok(unbond(deps, env, info, amount)?), + HandleMsg::Claim {} => Ok(claim(deps, env, info)?), + HandleMsg::Reinvest {} => Ok(reinvest(deps, env, info)?), + HandleMsg::_BondAllTokens {} => _bond_all_tokens(deps, env, info), } } -pub fn transfer( - deps: &mut Extern, - env: Env, +pub fn transfer( + deps: DepsMut, + _env: Env, + info: MessageInfo, recipient: HumanAddr, send: Uint128, ) -> StdResult { let rcpt_raw = deps.api.canonical_address(&recipient)?; - let sender_raw = deps.api.canonical_address(&env.message.sender)?; + let sender_raw = deps.api.canonical_address(&info.sender)?; - let mut accounts = balances(&mut deps.storage); - accounts.update(sender_raw.as_slice(), |balance: Option| { + let mut accounts = balances(deps.storage); + accounts.update(&sender_raw, |balance: Option| { balance.unwrap_or_default() - send })?; - accounts.update(rcpt_raw.as_slice(), |balance: Option| { + accounts.update(&rcpt_raw, |balance: Option| -> StdResult<_> { Ok(balance.unwrap_or_default() + send) })?; let res = HandleResponse { messages: vec![], - log: vec![ - log("action", "transfer"), - log("from", env.message.sender), - log("to", recipient), - log("amount", send), + attributes: vec![ + attr("action", "transfer"), + attr("from", info.sender), + attr("to", recipient), + attr("amount", send), ], data: None, }; @@ -99,7 +100,7 @@ pub fn transfer( // get_bonded returns the total amount of delegations from contract // it ensures they are all the same denom -fn get_bonded(querier: &Q, contract: &HumanAddr) -> StdResult { +fn get_bonded(querier: &QuerierWrapper, contract: &HumanAddr) -> StdResult { let bonds = querier.query_all_delegations(contract)?; if bonds.is_empty() { return Ok(Uint128(0)); @@ -129,17 +130,13 @@ fn assert_bonds(supply: &Supply, bonded: Uint128) -> StdResult<()> { } } -pub fn bond( - deps: &mut Extern, - env: Env, -) -> StdResult { - let sender_raw = deps.api.canonical_address(&env.message.sender)?; +pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { + let sender_raw = deps.api.canonical_address(&info.sender)?; // ensure we have the proper denom - let invest = invest_info_read(&deps.storage).load()?; + let invest = invest_info_read(deps.storage).load()?; // payment finds the proper coin (or throws an error) - let payment = env - .message + let payment = info .sent_funds .iter() .find(|x| x.denom == invest.bond_denom) @@ -149,7 +146,7 @@ pub fn bond( let bonded = get_bonded(&deps.querier, &env.contract.address)?; // calculate to_mint and update total supply - let mut totals = total_supply(&mut deps.storage); + let mut totals = total_supply(deps.storage); let mut supply = totals.load()?; // TODO: this is just temporary check - we should use dynamic query or have a way to recover assert_bonds(&supply, bonded)?; @@ -163,7 +160,7 @@ pub fn bond( totals.save(&supply)?; // update the balance of the sender - balances(&mut deps.storage).update(sender_raw.as_slice(), |balance| { + balances(deps.storage).update(&sender_raw, |balance| -> StdResult<_> { Ok(balance.unwrap_or_default() + to_mint) })?; @@ -174,25 +171,26 @@ pub fn bond( amount: payment.clone(), } .into()], - log: vec![ - log("action", "bond"), - log("from", env.message.sender), - log("bonded", payment.amount), - log("minted", to_mint), + attributes: vec![ + attr("action", "bond"), + attr("from", info.sender), + attr("bonded", payment.amount), + attr("minted", to_mint), ], data: None, }; Ok(res) } -pub fn unbond( - deps: &mut Extern, +pub fn unbond( + deps: DepsMut, env: Env, + info: MessageInfo, amount: Uint128, ) -> StdResult { - let sender_raw = deps.api.canonical_address(&env.message.sender)?; + let sender_raw = deps.api.canonical_address(&info.sender)?; - let invest = invest_info_read(&deps.storage).load()?; + let invest = invest_info_read(deps.storage).load()?; // ensure it is big enough to care if amount < invest.min_withdrawal { return Err(StdError::generic_err(format!( @@ -204,13 +202,13 @@ pub fn unbond( let tax = amount * invest.exit_tax; // deduct all from the account - let mut accounts = balances(&mut deps.storage); - accounts.update(sender_raw.as_slice(), |balance| { + let mut accounts = balances(deps.storage); + accounts.update(&sender_raw, |balance| -> StdResult<_> { balance.unwrap_or_default() - amount })?; if tax > Uint128(0) { // add tax to the owner - accounts.update(invest.owner.as_slice(), |balance: Option| { + accounts.update(&invest.owner, |balance: Option| -> StdResult<_> { Ok(balance.unwrap_or_default() + tax) })?; } @@ -221,7 +219,7 @@ pub fn unbond( // calculate how many native tokens this is worth and update supply let remainder = (amount - tax)?; - let mut totals = total_supply(&mut deps.storage); + let mut totals = total_supply(deps.storage); let mut supply = totals.load()?; // TODO: this is just temporary check - we should use dynamic query or have a way to recover assert_bonds(&supply, bonded)?; @@ -232,7 +230,7 @@ pub fn unbond( totals.save(&supply)?; // add a claim to this user to get their tokens after the unbonding period - claims(&mut deps.storage).update(sender_raw.as_slice(), |claim| { + claims(deps.storage).update(&sender_raw, |claim| -> StdResult<_> { Ok(claim.unwrap_or_default() + unbond) })?; @@ -243,23 +241,20 @@ pub fn unbond( amount: coin(unbond.u128(), &invest.bond_denom), } .into()], - log: vec![ - log("action", "unbond"), - log("to", env.message.sender), - log("unbonded", unbond), - log("burnt", amount), + attributes: vec![ + attr("action", "unbond"), + attr("to", info.sender), + attr("unbonded", unbond), + attr("burnt", amount), ], data: None, }; Ok(res) } -pub fn claim( - deps: &mut Extern, - env: Env, -) -> StdResult { +pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { // find how many tokens the contract has - let invest = invest_info_read(&deps.storage).load()?; + let invest = invest_info_read(deps.storage).load()?; let mut balance = deps .querier .query_balance(&env.contract.address, &invest.bond_denom)?; @@ -270,16 +265,16 @@ pub fn claim( } // check how much to send - min(balance, claims[sender]), and reduce the claim - let sender_raw = deps.api.canonical_address(&env.message.sender)?; + let sender_raw = deps.api.canonical_address(&info.sender)?; let mut to_send = balance.amount; - claims(&mut deps.storage).update(sender_raw.as_slice(), |claim| { + claims(deps.storage).update(sender_raw.as_slice(), |claim| { let claim = claim.ok_or_else(|| StdError::generic_err("no claim for this address"))?; to_send = to_send.min(claim); claim - to_send })?; // update total supply (lower claim) - total_supply(&mut deps.storage).update(|mut supply| { + total_supply(deps.storage).update(|mut supply| -> StdResult<_> { supply.claims = (supply.claims - to_send)?; Ok(supply) })?; @@ -289,14 +284,14 @@ pub fn claim( let res = HandleResponse { messages: vec![BankMsg::Send { from_address: env.contract.address, - to_address: env.message.sender.clone(), + to_address: info.sender.clone(), amount: vec![balance], } .into()], - log: vec![ - log("action", "claim"), - log("from", env.message.sender), - log("amount", to_send), + attributes: vec![ + attr("action", "claim"), + attr("from", info.sender), + attr("amount", to_send), ], data: None, }; @@ -306,12 +301,9 @@ pub fn claim( /// reinvest will withdraw all pending rewards, /// then issue a callback to itself via _bond_all_tokens /// to reinvest the new earnings (and anything else that accumulated) -pub fn reinvest( - deps: &mut Extern, - env: Env, -) -> StdResult { +pub fn reinvest(deps: DepsMut, env: Env, _info: MessageInfo) -> StdResult { let contract_addr = env.contract.address; - let invest = invest_info_read(&deps.storage).load()?; + let invest = invest_info_read(deps.storage).load()?; let msg = to_binary(&HandleMsg::_BondAllTokens {})?; // and bond them to the validator @@ -329,30 +321,31 @@ pub fn reinvest( } .into(), ], - log: vec![], + attributes: vec![], data: None, }; Ok(res) } -pub fn _bond_all_tokens( - deps: &mut Extern, +pub fn _bond_all_tokens( + deps: DepsMut, env: Env, -) -> StdResult { + info: MessageInfo, +) -> Result { // this is just meant as a call-back to ourself - if env.message.sender != env.contract.address { - return Err(StdError::unauthorized()); + if info.sender != env.contract.address { + return Err(Unauthorized {}.build()); } // find how many tokens we have to bond - let invest = invest_info_read(&deps.storage).load()?; + let invest = invest_info_read(deps.storage).load()?; let mut balance = deps .querier .query_balance(&env.contract.address, &invest.bond_denom)?; // we deduct pending claims from our account balance before reinvesting. // if there is not enough funds, we just return a no-op - match total_supply(&mut deps.storage).update(|mut supply| { + match total_supply(deps.storage).update(|mut supply| { balance.amount = (balance.amount - supply.claims)?; // this just triggers the "no op" case if we don't have min_withdrawal left to reinvest (balance.amount - invest.min_withdrawal)?; @@ -362,7 +355,7 @@ pub fn _bond_all_tokens( Ok(_) => {} // if it is below the minimum, we do a no-op (do not revert other state from withdrawal) Err(StdError::Underflow { .. }) => return Ok(HandleResponse::default()), - Err(e) => return Err(e), + Err(e) => return Err(e.into()), } // and bond them to the validator @@ -372,16 +365,13 @@ pub fn _bond_all_tokens( amount: balance.clone(), } .into()], - log: vec![log("action", "reinvest"), log("bonded", balance.amount)], + attributes: vec![attr("action", "reinvest"), attr("bonded", balance.amount)], data: None, }; Ok(res) } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?), QueryMsg::Investment {} => to_binary(&query_investment(deps)?), @@ -390,39 +380,29 @@ pub fn query( } } -pub fn query_token_info( - deps: &Extern, -) -> StdResult { - token_info_read(&deps.storage).load() +pub fn query_token_info(deps: Deps) -> StdResult { + token_info_read(deps.storage).load() } -pub fn query_balance( - deps: &Extern, - address: HumanAddr, -) -> StdResult { +pub fn query_balance(deps: Deps, address: HumanAddr) -> StdResult { let address_raw = deps.api.canonical_address(&address)?; - let balance = balances_read(&deps.storage) + let balance = balances_read(deps.storage) .may_load(address_raw.as_slice())? .unwrap_or_default(); Ok(BalanceResponse { balance }) } -pub fn query_claims( - deps: &Extern, - address: HumanAddr, -) -> StdResult { +pub fn query_claims(deps: Deps, address: HumanAddr) -> StdResult { let address_raw = deps.api.canonical_address(&address)?; - let claims = claims_read(&deps.storage) + let claims = claims_read(deps.storage) .may_load(address_raw.as_slice())? .unwrap_or_default(); Ok(ClaimsResponse { claims }) } -pub fn query_investment( - deps: &Extern, -) -> StdResult { - let invest = invest_info_read(&deps.storage).load()?; - let supply = total_supply_read(&deps.storage).load()?; +pub fn query_investment(deps: Deps) -> StdResult { + let invest = invest_info_read(deps.storage).load()?; + let supply = total_supply_read(deps.storage).load()?; let res = InvestmentResponse { owner: deps.api.human_address(&invest.owner)?, @@ -443,7 +423,9 @@ pub fn query_investment( #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, MockQuerier, MOCK_CONTRACT_ADDR}; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockQuerier, MOCK_CONTRACT_ADDR, + }; use cosmwasm_std::{coins, Coin, CosmosMsg, Decimal, FullDelegation, Validator}; use std::str::FromStr; @@ -458,13 +440,12 @@ mod tests { fn sample_delegation>(addr: U, amount: Coin) -> FullDelegation { let can_redelegate = amount.clone(); - let accumulated_rewards = coin(0, &amount.denom); FullDelegation { validator: addr.into(), delegator: HumanAddr::from(MOCK_CONTRACT_ADDR), amount, can_redelegate, - accumulated_rewards, + accumulated_rewards: Vec::new(), } } @@ -493,23 +474,17 @@ mod tests { } } - fn get_balance>( - deps: &Extern, - addr: U, - ) -> Uint128 { - query_balance(&deps, addr.into()).unwrap().balance + fn get_balance>(deps: Deps, addr: U) -> Uint128 { + query_balance(deps, addr.into()).unwrap().balance } - fn get_claims>( - deps: &Extern, - addr: U, - ) -> Uint128 { - query_claims(&deps, addr.into()).unwrap().claims + fn get_claims>(deps: Deps, addr: U) -> Uint128 { + query_claims(deps, addr.into()).unwrap().claims } #[test] fn initialization_with_missing_validator() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); deps.querier .update_staking("ustake", &[sample_validator("john")], &[]); @@ -522,10 +497,10 @@ mod tests { exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res = init(&mut deps, env, msg.clone()); + let res = init(deps.as_mut(), mock_env(), info, msg.clone()); match res.unwrap_err() { StdError::GenericErr { msg, .. } => { assert_eq!(msg, "my-validator is not in the current validator set") @@ -536,7 +511,7 @@ mod tests { #[test] fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); deps.querier.update_staking( "ustake", &[ @@ -556,25 +531,25 @@ mod tests { exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res = init(&mut deps, env, msg.clone()).unwrap(); + let res = init(deps.as_mut(), mock_env(), info, msg.clone()).unwrap(); assert_eq!(0, res.messages.len()); // token info is proper - let token = query_token_info(&deps).unwrap(); + let token = query_token_info(deps.as_ref()).unwrap(); assert_eq!(&token.name, &msg.name); assert_eq!(&token.symbol, &msg.symbol); assert_eq!(token.decimals, msg.decimals); // no balance - assert_eq!(get_balance(&deps, &creator), Uint128(0)); + assert_eq!(get_balance(deps.as_ref(), &creator), Uint128(0)); // no claims - assert_eq!(get_claims(&deps, &creator), Uint128(0)); + assert_eq!(get_claims(deps.as_ref(), &creator), Uint128(0)); // investment info correct - let invest = query_investment(&deps).unwrap(); + let invest = query_investment(deps.as_ref()).unwrap(); assert_eq!(&invest.owner, &creator); assert_eq!(&invest.validator, &msg.validator); assert_eq!(invest.exit_tax, msg.exit_tax); @@ -587,24 +562,24 @@ mod tests { #[test] fn bonding_issues_tokens() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); let creator = HumanAddr::from("creator"); let init_msg = default_init(2, 50); - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res = init(&mut deps, env, init_msg).unwrap(); + let res = init(deps.as_mut(), mock_env(), info, init_msg).unwrap(); assert_eq!(0, res.messages.len()); // let's bond some tokens now let bob = HumanAddr::from("bob"); let bond_msg = HandleMsg::Bond {}; - let env = mock_env(&bob, &[coin(10, "random"), coin(1000, "ustake")]); + let info = mock_info(&bob, &[coin(10, "random"), coin(1000, "ustake")]); // try to bond and make sure we trigger delegation - let res = handle(&mut deps, env, bond_msg).unwrap(); + let res = handle(deps.as_mut(), mock_env(), info, bond_msg).unwrap(); assert_eq!(1, res.messages.len()); let delegate = &res.messages[0]; match delegate { @@ -616,10 +591,10 @@ mod tests { } // bob got 1000 DRV for 1000 stake at a 1.0 ratio - assert_eq!(get_balance(&deps, &bob), Uint128(1000)); + assert_eq!(get_balance(deps.as_ref(), &bob), Uint128(1000)); // investment info correct (updated supply) - let invest = query_investment(&deps).unwrap(); + let invest = query_investment(deps.as_ref()).unwrap(); assert_eq!(invest.token_supply, Uint128(1000)); assert_eq!(invest.staked_tokens, coin(1000, "ustake")); assert_eq!(invest.nominal_value, Decimal::one()); @@ -627,23 +602,22 @@ mod tests { #[test] fn rebonding_changes_pricing() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); let creator = HumanAddr::from("creator"); let init_msg = default_init(2, 50); - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res = init(&mut deps, env, init_msg).unwrap(); + let res = init(deps.as_mut(), mock_env(), info, init_msg).unwrap(); assert_eq!(0, res.messages.len()); // let's bond some tokens now let bob = HumanAddr::from("bob"); let bond_msg = HandleMsg::Bond {}; - let env = mock_env(&bob, &[coin(10, "random"), coin(1000, "ustake")]); - let contract_addr = env.contract.address.clone(); - let res = handle(&mut deps, env, bond_msg).unwrap(); + let info = mock_info(&bob, &[coin(10, "random"), coin(1000, "ustake")]); + let res = handle(deps.as_mut(), mock_env(), info, bond_msg).unwrap(); assert_eq!(1, res.messages.len()); // update the querier with new bond @@ -651,16 +625,16 @@ mod tests { // fake a reinvestment (this must be sent by the contract itself) let rebond_msg = HandleMsg::_BondAllTokens {}; - let env = mock_env(&contract_addr, &[]); + let info = mock_info(MOCK_CONTRACT_ADDR, &[]); deps.querier - .update_balance(&contract_addr, coins(500, "ustake")); - let _ = handle(&mut deps, env, rebond_msg).unwrap(); + .update_balance(MOCK_CONTRACT_ADDR, coins(500, "ustake")); + let _ = handle(deps.as_mut(), mock_env(), info, rebond_msg).unwrap(); // update the querier with new bond set_delegation(&mut deps.querier, 1500, "ustake"); // we should now see 1000 issues and 1500 bonded (and a price of 1.5) - let invest = query_investment(&deps).unwrap(); + let invest = query_investment(deps.as_ref()).unwrap(); assert_eq!(invest.token_supply, Uint128(1000)); assert_eq!(invest.staked_tokens, coin(1500, "ustake")); let ratio = Decimal::from_str("1.5").unwrap(); @@ -669,17 +643,17 @@ mod tests { // we bond some other tokens and get a different issuance price (maintaining the ratio) let alice = HumanAddr::from("alice"); let bond_msg = HandleMsg::Bond {}; - let env = mock_env(&alice, &[coin(3000, "ustake")]); - let res = handle(&mut deps, env, bond_msg).unwrap(); + let info = mock_info(&alice, &[coin(3000, "ustake")]); + let res = handle(deps.as_mut(), mock_env(), info, bond_msg).unwrap(); assert_eq!(1, res.messages.len()); // update the querier with new bond set_delegation(&mut deps.querier, 3000, "ustake"); // alice should have gotten 2000 DRV for the 3000 stake, keeping the ratio at 1.5 - assert_eq!(get_balance(&deps, &alice), Uint128(2000)); + assert_eq!(get_balance(deps.as_ref(), &alice), Uint128(2000)); - let invest = query_investment(&deps).unwrap(); + let invest = query_investment(deps.as_ref()).unwrap(); assert_eq!(invest.token_supply, Uint128(3000)); assert_eq!(invest.staked_tokens, coin(4500, "ustake")); assert_eq!(invest.nominal_value, ratio); @@ -687,49 +661,50 @@ mod tests { #[test] fn bonding_fails_with_wrong_denom() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); let creator = HumanAddr::from("creator"); let init_msg = default_init(2, 50); - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res = init(&mut deps, env, init_msg).unwrap(); + let res = init(deps.as_mut(), mock_env(), info, init_msg).unwrap(); assert_eq!(0, res.messages.len()); // let's bond some tokens now let bob = HumanAddr::from("bob"); let bond_msg = HandleMsg::Bond {}; - let env = mock_env(&bob, &[coin(500, "photon")]); + let info = mock_info(&bob, &[coin(500, "photon")]); // try to bond and make sure we trigger delegation - let res = handle(&mut deps, env, bond_msg); + let res = handle(deps.as_mut(), mock_env(), info, bond_msg); match res.unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "No ustake tokens sent"), - e => panic!("Expected wrong denom error, got: {:?}", e), + StakingError::Std { + original: StdError::GenericErr { msg, .. }, + } => assert_eq!(msg, "No ustake tokens sent"), + err => panic!("Unexpected error: {:?}", err), }; } #[test] fn unbonding_maintains_price_ratio() { - let mut deps = mock_dependencies(20, &[]); + let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); let creator = HumanAddr::from("creator"); let init_msg = default_init(10, 50); - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res = init(&mut deps, env, init_msg).unwrap(); + let res = init(deps.as_mut(), mock_env(), info, init_msg).unwrap(); assert_eq!(0, res.messages.len()); // let's bond some tokens now let bob = HumanAddr::from("bob"); let bond_msg = HandleMsg::Bond {}; - let env = mock_env(&bob, &[coin(10, "random"), coin(1000, "ustake")]); - let contract_addr = env.contract.address.clone(); - let res = handle(&mut deps, env, bond_msg).unwrap(); + let info = mock_info(&bob, &[coin(10, "random"), coin(1000, "ustake")]); + let res = handle(deps.as_mut(), mock_env(), info, bond_msg).unwrap(); assert_eq!(1, res.messages.len()); // update the querier with new bond @@ -738,24 +713,26 @@ mod tests { // fake a reinvestment (this must be sent by the contract itself) // after this, we see 1000 issues and 1500 bonded (and a price of 1.5) let rebond_msg = HandleMsg::_BondAllTokens {}; - let env = mock_env(&contract_addr, &[]); + let info = mock_info(MOCK_CONTRACT_ADDR, &[]); deps.querier - .update_balance(&contract_addr, coins(500, "ustake")); - let _ = handle(&mut deps, env, rebond_msg).unwrap(); + .update_balance(MOCK_CONTRACT_ADDR, coins(500, "ustake")); + let _ = handle(deps.as_mut(), mock_env(), info, rebond_msg).unwrap(); // update the querier with new bond, lower balance set_delegation(&mut deps.querier, 1500, "ustake"); - deps.querier.update_balance(&contract_addr, vec![]); + deps.querier.update_balance(MOCK_CONTRACT_ADDR, vec![]); // creator now tries to unbond these tokens - this must fail let unbond_msg = HandleMsg::Unbond { amount: Uint128(600), }; - let env = mock_env(&creator, &[]); - let res = handle(&mut deps, env, unbond_msg); + let info = mock_info(&creator, &[]); + let res = handle(deps.as_mut(), mock_env(), info, unbond_msg); match res.unwrap_err() { - StdError::Underflow { .. } => {} - e => panic!("unexpected error: {}", e), + StakingError::Std { + original: StdError::Underflow { .. }, + } => {} + err => panic!("Unexpected error: {:?}", err), } // bob unbonds 600 tokens at 10% tax... @@ -767,8 +744,8 @@ mod tests { let owner_cut = Uint128(60); let bobs_claim = Uint128(810); let bobs_balance = Uint128(400); - let env = mock_env(&bob, &[]); - let res = handle(&mut deps, env, unbond_msg).unwrap(); + let info = mock_info(&bob, &[]); + let res = handle(deps.as_mut(), mock_env(), info, unbond_msg).unwrap(); assert_eq!(1, res.messages.len()); let delegate = &res.messages[0]; match delegate { @@ -783,15 +760,15 @@ mod tests { set_delegation(&mut deps.querier, 690, "ustake"); // check balances - assert_eq!(get_balance(&deps, &bob), bobs_balance); - assert_eq!(get_balance(&deps, &creator), owner_cut); + assert_eq!(get_balance(deps.as_ref(), &bob), bobs_balance); + assert_eq!(get_balance(deps.as_ref(), &creator), owner_cut); // proper claims - assert_eq!(get_claims(&deps, &bob), bobs_claim); + assert_eq!(get_claims(deps.as_ref(), &bob), bobs_claim); // supplies updated, ratio the same (1.5) let ratio = Decimal::from_str("1.5").unwrap(); - let invest = query_investment(&deps).unwrap(); + let invest = query_investment(deps.as_ref()).unwrap(); assert_eq!(invest.token_supply, bobs_balance + owner_cut); assert_eq!(invest.staked_tokens, coin(690, "ustake")); // 1500 - 810 assert_eq!(invest.nominal_value, ratio); diff --git a/contracts/staking/src/errors.rs b/contracts/staking/src/errors.rs new file mode 100644 index 000000000..9c55a497f --- /dev/null +++ b/contracts/staking/src/errors.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::StdError; +use snafu::Snafu; + +#[derive(Snafu, Debug)] +#[snafu(visibility = "pub(crate)")] +pub enum StakingError { + /// this is needed so we can use `bucket.load(...)?` and have it auto-converted to the custom error + #[snafu(display("StdError: {}", original))] + Std { original: StdError }, + #[snafu(display("Unauthorized"))] + Unauthorized { backtrace: Option }, +} + +impl From for StakingError { + fn from(original: StdError) -> Self { + Std { original }.build() + } +} diff --git a/contracts/staking/src/lib.rs b/contracts/staking/src/lib.rs index 37b3108ac..17b42da53 100644 --- a/contracts/staking/src/lib.rs +++ b/contracts/staking/src/lib.rs @@ -1,4 +1,5 @@ pub mod contract; +mod errors; pub mod msg; pub mod state; diff --git a/contracts/staking/src/state.rs b/contracts/staking/src/state.rs index 320037c13..d868af45a 100644 --- a/contracts/staking/src/state.rs +++ b/contracts/staking/src/state.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{CanonicalAddr, Decimal, HumanAddr, ReadonlyStorage, Storage, Uint128}; +use cosmwasm_std::{CanonicalAddr, Decimal, HumanAddr, Storage, Uint128}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, @@ -17,21 +17,21 @@ pub const PREFIX_BALANCE: &[u8] = b"balance"; pub const PREFIX_CLAIMS: &[u8] = b"claim"; /// balances are state of the erc20 tokens -pub fn balances(storage: &mut S) -> Bucket { - bucket(PREFIX_BALANCE, storage) +pub fn balances(storage: &mut dyn Storage) -> Bucket { + bucket(storage, PREFIX_BALANCE) } -pub fn balances_read(storage: &S) -> ReadonlyBucket { - bucket_read(PREFIX_BALANCE, storage) +pub fn balances_read(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, PREFIX_BALANCE) } /// claims are the claims to money being unbonded -pub fn claims(storage: &mut S) -> Bucket { - bucket(PREFIX_CLAIMS, storage) +pub fn claims(storage: &mut dyn Storage) -> Bucket { + bucket(storage, PREFIX_CLAIMS) } -pub fn claims_read(storage: &S) -> ReadonlyBucket { - bucket_read(PREFIX_CLAIMS, storage) +pub fn claims_read(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, PREFIX_CLAIMS) } /// Investment info is fixed at initialization, and is used to control the function of the contract @@ -62,26 +62,26 @@ pub struct Supply { pub claims: Uint128, } -pub fn invest_info(storage: &mut S) -> Singleton { +pub fn invest_info(storage: &mut dyn Storage) -> Singleton { singleton(storage, KEY_INVESTMENT) } -pub fn invest_info_read(storage: &S) -> ReadonlySingleton { +pub fn invest_info_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, KEY_INVESTMENT) } -pub fn token_info(storage: &mut S) -> Singleton { +pub fn token_info(storage: &mut dyn Storage) -> Singleton { singleton(storage, KEY_TOKEN_INFO) } -pub fn token_info_read(storage: &S) -> ReadonlySingleton { +pub fn token_info_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, KEY_TOKEN_INFO) } -pub fn total_supply(storage: &mut S) -> Singleton { +pub fn total_supply(storage: &mut dyn Storage) -> Singleton { singleton(storage, KEY_TOTAL_SUPPLY) } -pub fn total_supply_read(storage: &S) -> ReadonlySingleton { +pub fn total_supply_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, KEY_TOTAL_SUPPLY) } diff --git a/contracts/staking/tests/integration.rs b/contracts/staking/tests/integration.rs index 965538acd..fc6fdf619 100644 --- a/contracts/staking/tests/integration.rs +++ b/contracts/staking/tests/integration.rs @@ -18,9 +18,9 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coin, from_binary, Decimal, HumanAddr, InitResponse, StdError, StdResult, Uint128, Validator, + coin, from_binary, ContractResult, Decimal, HumanAddr, InitResponse, Uint128, Validator, }; -use cosmwasm_vm::testing::{init, mock_dependencies, mock_env, query}; +use cosmwasm_vm::testing::{init, mock_backend, mock_env, mock_info, mock_instance_options, query}; use cosmwasm_vm::Instance; use staking::msg::{ @@ -43,10 +43,11 @@ fn sample_validator>(addr: U) -> Validator { #[test] fn initialization_with_missing_validator() { - let mut ext = mock_dependencies(20, &[]); - ext.querier + let mut backend = mock_backend(&[]); + backend + .querier .update_staking("ustake", &[sample_validator("john")], &[]); - let mut deps = Instance::from_code(WASM, ext, 500_000).unwrap(); + let mut deps = Instance::from_code(WASM, backend, mock_instance_options()).unwrap(); let creator = HumanAddr::from("creator"); let msg = InitMsg { @@ -57,23 +58,22 @@ fn initialization_with_missing_validator() { exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res: StdResult = init(&mut deps, env, msg.clone()); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "my-validator is not in the current validator set") - } - _ => panic!("expected unregistered validator error"), - } + let res: ContractResult = init(&mut deps, mock_env(), info, msg.clone()); + let msg = res.unwrap_err(); + assert_eq!( + msg, + "Generic error: my-validator is not in the current validator set" + ); } #[test] fn proper_initialization() { // we need to use the verbose approach here to customize the querier with staking info - let mut ext = mock_dependencies(20, &[]); - ext.querier.update_staking( + let mut backend = mock_backend(&[]); + backend.querier.update_staking( "ustake", &[ sample_validator("john"), @@ -82,7 +82,7 @@ fn proper_initialization() { ], &[], ); - let mut deps = Instance::from_code(WASM, ext, 500_000).unwrap(); + let mut deps = Instance::from_code(WASM, backend, mock_instance_options()).unwrap(); assert_eq!(deps.required_features.len(), 1); assert!(deps.required_features.contains("staking")); @@ -95,14 +95,14 @@ fn proper_initialization() { exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; - let env = mock_env(&creator, &[]); + let info = mock_info(&creator, &[]); // make sure we can init with this - let res: InitResponse = init(&mut deps, env, msg.clone()).unwrap(); + let res: InitResponse = init(&mut deps, mock_env(), info, msg.clone()).unwrap(); assert_eq!(0, res.messages.len()); // token info is proper - let res = query(&mut deps, QueryMsg::TokenInfo {}).unwrap(); + let res = query(&mut deps, mock_env(), QueryMsg::TokenInfo {}).unwrap(); let token: TokenInfoResponse = from_binary(&res).unwrap(); assert_eq!(&token.name, &msg.name); assert_eq!(&token.symbol, &msg.symbol); @@ -111,6 +111,7 @@ fn proper_initialization() { // no balance let res = query( &mut deps, + mock_env(), QueryMsg::Balance { address: creator.clone(), }, @@ -122,6 +123,7 @@ fn proper_initialization() { // no claims let res = query( &mut deps, + mock_env(), QueryMsg::Claims { address: creator.clone(), }, @@ -131,7 +133,7 @@ fn proper_initialization() { assert_eq!(claim.claims, Uint128(0)); // investment info correct - let res = query(&mut deps, QueryMsg::Investment {}).unwrap(); + let res = query(&mut deps, mock_env(), QueryMsg::Investment {}).unwrap(); let invest: InvestmentResponse = from_binary(&res).unwrap(); assert_eq!(&invest.owner, &creator); assert_eq!(&invest.validator, &msg.validator); diff --git a/contracts/token-tester/Cargo.lock b/contracts/token-tester/Cargo.lock index b45d2f85f..28e689dae 100644 --- a/contracts/token-tester/Cargo.lock +++ b/contracts/token-tester/Cargo.lock @@ -1,20 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "addr2line" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" -dependencies = [ - "gimli 0.22.0", -] - -[[package]] -name = "adler" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" - [[package]] name = "arrayref" version = "0.3.6" @@ -33,20 +18,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "backtrace" -version = "0.3.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e" -dependencies = [ - "addr2line", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.11.0" @@ -126,6 +97,12 @@ dependencies = [ "bitflags", ] +[[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.2" @@ -150,7 +127,7 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" dependencies = [ "schemars", "serde_json", @@ -158,18 +135,18 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" dependencies = [ "base64", "schemars", "serde", "serde-json-wasm", - "snafu", + "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "0.10.0" +version = "0.12.0" dependencies = [ "cosmwasm-std", "serde", @@ -177,8 +154,9 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" dependencies = [ + "clru", "cosmwasm-std", "hex", "memmap", @@ -187,7 +165,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "snafu", + "thiserror", "wasmer-clif-backend", "wasmer-middleware-common", "wasmer-runtime-core", @@ -220,7 +198,7 @@ dependencies = [ "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.20.0", + "gimli", "log", "smallvec", "target-lexicon", @@ -428,12 +406,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "gimli" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" - [[package]] name = "hashbrown" version = "0.9.1" @@ -521,16 +493,6 @@ 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 = "nix" version = "0.15.0" @@ -554,12 +516,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -587,9 +543,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" +checksum = "d17797de36b94bc5f73edad736fd0a77ce5ab64dd622f809c1eead8c91fa6564" [[package]] name = "parking_lot" @@ -675,12 +631,6 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" -[[package]] -name = "rustc-demangle" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" - [[package]] name = "rustc_version" version = "0.2.3" @@ -835,7 +785,6 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09" dependencies = [ - "backtrace", "doc-comment", "snafu-derive", ] @@ -902,7 +851,7 @@ dependencies = [ [[package]] name = "token-tester" -version = "0.1.0" +version = "0.12.0" dependencies = [ "cosmwasm-ext", "cosmwasm-schema", diff --git a/contracts/token-tester/Cargo.toml b/contracts/token-tester/Cargo.toml index 955f454af..fafec5e16 100644 --- a/contracts/token-tester/Cargo.toml +++ b/contracts/token-tester/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "token-tester" -version = "0.1.0" +version = "0.12.0" authors = ["shiki.tak"] edition = "2018" description = "simple tester for cosmwasm/ext" diff --git a/contracts/token-tester/src/contract.rs b/contracts/token-tester/src/contract.rs index b9801e729..7249721c2 100644 --- a/contracts/token-tester/src/contract.rs +++ b/contracts/token-tester/src/contract.rs @@ -1,8 +1,8 @@ use std::str::FromStr; use cosmwasm_std::{ - log, to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr, - InitResponse, Querier, StdResult, Storage, Uint128, + attr, to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, HandleResponse, HandleResult, + HumanAddr, InitResponse, MessageInfo, StdResult, Uint128, }; use cosmwasm_ext::{ @@ -13,23 +13,20 @@ use cosmwasm_ext::{ use crate::msg::{HandleMsg, InitMsg, QueryMsg}; use crate::state::{config, config_read, State}; -pub fn init( - deps: &mut Extern, - env: Env, - _msg: InitMsg, -) -> StdResult { +pub fn init(deps: DepsMut, _env: Env, info: MessageInfo, _msg: InitMsg) -> StdResult { let state = State { - owner: deps.api.canonical_address(&env.message.sender)?, + owner: deps.api.canonical_address(&info.sender)?, }; - config(&mut deps.storage).save(&state)?; + config(deps.storage).save(&state)?; Ok(InitResponse::default()) } -pub fn handle( - deps: &mut Extern, +pub fn handle( + deps: DepsMut, env: Env, + info: MessageInfo, msg: HandleMsg, ) -> HandleResult> { match msg { @@ -44,94 +41,94 @@ pub fn handle( mintable, decimals, } => try_issue( - deps, env, owner, to, name, symbol, img_uri, meta, amount, mintable, decimals, + deps, env, info, owner, to, name, symbol, img_uri, meta, amount, mintable, decimals, ), HandleMsg::Transfer { from, contract_id, to, amount, - } => try_transfer(deps, env, from, contract_id, to, amount), + } => try_transfer(deps, env, info, from, contract_id, to, amount), HandleMsg::TransferFrom { proxy, from, contract_id, to, amount, - } => try_transfer_from(deps, env, proxy, from, contract_id, to, amount), + } => try_transfer_from(deps, env, info, proxy, from, contract_id, to, amount), HandleMsg::Mint { from, contract_id, to, amount, - } => try_mint(deps, env, from, contract_id, to, amount), + } => try_mint(deps, env, info, from, contract_id, to, amount), HandleMsg::Burn { from, contract_id, amount, - } => try_burn(deps, env, from, contract_id, amount), + } => try_burn(deps, env, info, from, contract_id, amount), HandleMsg::BurnFrom { proxy, from, contract_id, amount, - } => try_burn_from(deps, env, proxy, from, contract_id, amount), + } => try_burn_from(deps, env, info, proxy, from, contract_id, amount), HandleMsg::GrantPerm { from, contract_id, to, permission, - } => try_grant_perm(deps, env, from, contract_id, to, permission), + } => try_grant_perm(deps, env, info, from, contract_id, to, permission), HandleMsg::RevokePerm { from, contract_id, permission, - } => try_revoke_perm(deps, env, from, contract_id, permission), + } => try_revoke_perm(deps, env, info, from, contract_id, permission), HandleMsg::Modify { owner, contract_id, key, value, - } => try_modify(deps, env, owner, contract_id, key, value), + } => try_modify(deps, env, info, owner, contract_id, key, value), HandleMsg::Approve { approver, contract_id, proxy, - } => try_approve(deps, env, approver, contract_id, proxy), + } => try_approve(deps, env, info, approver, contract_id, proxy), } } -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetToken { contract_id } => query_token(deps, contract_id), + QueryMsg::GetToken { contract_id } => query_token(deps, env, contract_id), QueryMsg::GetBalance { contract_id, address, - } => query_balance(deps, contract_id, address), + } => query_balance(deps, env, contract_id, address), QueryMsg::GetTotal { contract_id, target, - } => query_total(deps, contract_id, target), + } => query_total(deps, env, contract_id, target), QueryMsg::GetPerm { contract_id, address, - } => query_perm(deps, contract_id, address), + } => query_perm(deps, env, contract_id, address), QueryMsg::GetIsApproved { proxy, contract_id, approver, - } => query_is_approved(deps, proxy, contract_id, approver), - QueryMsg::GetApprovers { proxy, contract_id } => query_approvers(deps, proxy, contract_id), + } => query_is_approved(deps, env, proxy, contract_id, approver), + QueryMsg::GetApprovers { proxy, contract_id } => { + query_approvers(deps, env, proxy, contract_id) + } } } #[allow(clippy::too_many_arguments)] -pub fn try_issue( - _deps: &mut Extern, +pub fn try_issue( + _deps: DepsMut, _env: Env, + _info: MessageInfo, owner: HumanAddr, to: HumanAddr, name: String, @@ -164,15 +161,16 @@ pub fn try_issue( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "issue")], + attributes: vec![attr("action", "issue")], data: None, }; Ok(res) } -pub fn try_transfer( - _deps: &mut Extern, +pub fn try_transfer( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -196,15 +194,17 @@ pub fn try_transfer( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "transfer")], + attributes: vec![attr("action", "transfer")], data: None, }; Ok(res) } -pub fn try_transfer_from( - _deps: &mut Extern, +#[allow(clippy::too_many_arguments)] +pub fn try_transfer_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, from: HumanAddr, contract_id: String, @@ -230,15 +230,16 @@ pub fn try_transfer_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "transfer_from")], + attributes: vec![attr("action", "transfer_from")], data: None, }; Ok(res) } -pub fn try_mint( - _deps: &mut Extern, +pub fn try_mint( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -260,15 +261,16 @@ pub fn try_mint( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "mint")], + attributes: vec![attr("action", "mint")], data: None, }; Ok(res) } -pub fn try_burn( - _deps: &mut Extern, +pub fn try_burn( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, amount: Uint128, @@ -288,15 +290,16 @@ pub fn try_burn( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "burn")], + attributes: vec![attr("action", "burn")], data: None, }; Ok(res) } -pub fn try_burn_from( - _deps: &mut Extern, +pub fn try_burn_from( + _deps: DepsMut, _env: Env, + _info: MessageInfo, proxy: HumanAddr, from: HumanAddr, contract_id: String, @@ -318,15 +321,16 @@ pub fn try_burn_from( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "burn_from")], + attributes: vec![attr("action", "burn_from")], data: None, }; Ok(res) } -pub fn try_grant_perm( - _deps: &mut Extern, +pub fn try_grant_perm( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, to: HumanAddr, @@ -349,15 +353,16 @@ pub fn try_grant_perm( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "grant_perm")], + attributes: vec![attr("action", "grant_perm")], data: None, }; Ok(res) } -pub fn try_revoke_perm( - _deps: &mut Extern, +pub fn try_revoke_perm( + _deps: DepsMut, _env: Env, + _info: MessageInfo, from: HumanAddr, contract_id: String, perm_str: String, @@ -378,15 +383,16 @@ pub fn try_revoke_perm( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "revoke_perm")], + attributes: vec![attr("action", "revoke_perm")], data: None, }; Ok(res) } -pub fn try_modify( - _deps: &mut Extern, +pub fn try_modify( + _deps: DepsMut, _env: Env, + _info: MessageInfo, owner: HumanAddr, contract_id: String, key: String, @@ -407,15 +413,16 @@ pub fn try_modify( .into(); let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "modify")], + attributes: vec![attr("action", "modify")], data: None, }; Ok(res) } -pub fn try_approve( - _deps: &mut Extern, +pub fn try_approve( + _deps: DepsMut, _env: Env, + _info: MessageInfo, approver: HumanAddr, contract_id: String, proxy: HumanAddr, @@ -435,17 +442,14 @@ pub fn try_approve( let res = HandleResponse { messages: vec![msg], - log: vec![log("action", "approve")], + attributes: vec![attr("action", "approve")], data: None, }; Ok(res) } -fn query_token( - deps: &Extern, - contract_id: String, -) -> StdResult { - let res = match LinkTokenQuerier::new(&deps.querier).query_token(contract_id)? { +fn query_token(deps: Deps, _env: Env, contract_id: String) -> StdResult { + let res = match LinkTokenQuerier::new(deps.querier).query_token(contract_id)? { Some(token_response) => token_response, None => return to_binary(&None::>>), }; @@ -454,38 +458,40 @@ fn query_token( Ok(out) } -fn query_balance( - deps: &Extern, +fn query_balance( + deps: Deps, + _env: Env, contract_id: String, address: HumanAddr, ) -> StdResult { - let res = LinkTokenQuerier::new(&deps.querier) + let res = LinkTokenQuerier::new(deps.querier) .query_balance(contract_id, address) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_total( - deps: &Extern, +fn query_total( + deps: Deps, + _env: Env, contract_id: String, target_str: String, ) -> StdResult { let target = Target::from_str(&target_str).unwrap(); if Target::Supply == target { - let res = LinkTokenQuerier::new(&deps.querier) + let res = LinkTokenQuerier::new(deps.querier) .query_supply(contract_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } else if Target::Mint == target { - let res = LinkTokenQuerier::new(&deps.querier) + let res = LinkTokenQuerier::new(deps.querier) .query_mint(contract_id) .unwrap(); let out = to_binary(&res)?; Ok(out) } else { - let res = LinkTokenQuerier::new(&deps.querier) + let res = LinkTokenQuerier::new(deps.querier) .query_burn(contract_id) .unwrap(); let out = to_binary(&res)?; @@ -493,12 +499,8 @@ fn query_total( } } -fn query_perm( - deps: &Extern, - contract_id: String, - address: HumanAddr, -) -> StdResult { - let res = match LinkTokenQuerier::new(&deps.querier).query_perm(contract_id, address)? { +fn query_perm(deps: Deps, _env: Env, contract_id: String, address: HumanAddr) -> StdResult { + let res = match LinkTokenQuerier::new(deps.querier).query_perm(contract_id, address)? { Some(permissions) => permissions, None => return to_binary(&None::>>), }; @@ -506,25 +508,27 @@ fn query_perm( Ok(out) } -fn query_is_approved( - deps: &Extern, +fn query_is_approved( + deps: Deps, + _env: Env, proxy: HumanAddr, contract_id: String, approver: HumanAddr, ) -> StdResult { - let res = LinkTokenQuerier::new(&deps.querier) + let res = LinkTokenQuerier::new(deps.querier) .query_is_approved(proxy, contract_id, approver) .unwrap(); let out = to_binary(&res)?; Ok(out) } -fn query_approvers( - deps: &Extern, +fn query_approvers( + deps: Deps, + _env: Env, proxy: HumanAddr, contract_id: String, ) -> StdResult { - let res = match LinkTokenQuerier::new(&deps.querier).query_approvers(proxy, contract_id)? { + let res = match LinkTokenQuerier::new(deps.querier).query_approvers(proxy, contract_id)? { Some(approvers) => approvers, None => return to_binary(&None::>>), }; @@ -532,21 +536,24 @@ fn query_approvers( Ok(out) } -fn _query_owner(deps: &Extern) -> StdResult { - let state = config_read(&deps.storage).load()?; +fn _query_owner(deps: Deps, _env: Env) -> StdResult { + let state = config_read(deps.storage).load()?; Ok(deps.api.human_address(&state.owner)?) } #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; - use cosmwasm_std::{coins, Env}; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::{coins, Env, OwnedDeps}; - fn create_contract(owner: String) -> (Extern, Env) { - let mut deps = mock_dependencies(20, &coins(1000, "cony")); - let env = mock_env(owner, &coins(1000, "cony")); - let res = init(&mut deps, env.clone(), InitMsg {}).unwrap(); + fn create_contract(owner: String) -> (OwnedDeps, Env) { + let mut deps = mock_dependencies(&coins(1000, "cony")); + let env = mock_env(); + let info = mock_info(owner, &coins(1000, "cony")); + let res = init(deps.as_mut(), env.clone(), info.clone(), InitMsg {}).unwrap(); assert_eq!(0, res.messages.len()); (deps, env) } @@ -556,7 +563,8 @@ mod tests { let addr = "creator"; let (deps, _) = create_contract(addr.to_string()); - let value = _query_owner(&deps).unwrap(); - assert_eq!("creator", value.as_str()); + let env = mock_env(); + let value = _query_owner(deps.as_ref(), env).unwrap(); + assert_eq!(addr, value.as_str()); } } diff --git a/contracts/token-tester/src/lib.rs b/contracts/token-tester/src/lib.rs index 5d3bf59e1..37b3108ac 100644 --- a/contracts/token-tester/src/lib.rs +++ b/contracts/token-tester/src/lib.rs @@ -3,38 +3,4 @@ pub mod msg; pub mod state; #[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use cosmwasm_std::{ - do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, - }; - - #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::init::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::handle::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -} +cosmwasm_std::create_entry_points!(contract); diff --git a/contracts/token-tester/src/state.rs b/contracts/token-tester/src/state.rs index 471b41e25..9005e710f 100644 --- a/contracts/token-tester/src/state.rs +++ b/contracts/token-tester/src/state.rs @@ -11,10 +11,10 @@ pub struct State { pub owner: CanonicalAddr, } -pub fn config(storage: &mut S) -> Singleton { +pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } -pub fn config_read(storage: &S) -> ReadonlySingleton { +pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } diff --git a/devtools/set_version.sh b/devtools/set_version.sh index d4385cbe9..5237b4f5a 100755 --- a/devtools/set_version.sh +++ b/devtools/set_version.sh @@ -2,6 +2,8 @@ set -o errexit -o nounset -o pipefail command -v shellcheck > /dev/null && shellcheck "$0" +gnused="$(command -v gsed || echo sed)" + function print_usage() { echo "Usage: $0 NEW_VERSION" echo "" @@ -29,14 +31,14 @@ if [[ -n "$CHANGES_IN_REPO" ]]; then fi NEW="$1" -OLD=$(sed -n -e 's/^version[[:space:]]*=[[:space:]]*"\(.*\)"/\1/p' packages/std/Cargo.toml) +OLD=$("$gnused" -n -e 's/^version[[:space:]]*=[[:space:]]*"\(.*\)"/\1/p' packages/std/Cargo.toml) echo "Updating old version $OLD to new version $NEW ..." FILES_MODIFIED=() for package_dir in packages/*/; do CARGO_TOML="$package_dir/Cargo.toml" - sed -i '' -e "s/version[[:space:]]*=[[:space:]]*\"$OLD\"/version = \"$NEW\"/" "$CARGO_TOML" + "$gnused" -i -e "s/version[[:space:]]*=[[:space:]]*\"$OLD\"/version = \"$NEW\"/" "$CARGO_TOML" FILES_MODIFIED+=("$CARGO_TOML") done @@ -47,7 +49,7 @@ for contract_dir in contracts/*/; do CARGO_TOML="$contract_dir/Cargo.toml" CARGO_LOCK="$contract_dir/Cargo.lock" - sed -i '' -e "s/version[[:space:]]*=[[:space:]]*\"$OLD\"/version = \"$NEW\"/" "$CARGO_TOML" + "$gnused" -i -e "s/version[[:space:]]*=[[:space:]]*\"$OLD\"/version = \"$NEW\"/" "$CARGO_TOML" (cd "$contract_dir" && cargo build) FILES_MODIFIED+=("$CARGO_TOML" "$CARGO_LOCK") diff --git a/docs/simulate_riffle_shuffle.py b/docs/simulate_riffle_shuffle.py new file mode 100644 index 000000000..c6a33c135 --- /dev/null +++ b/docs/simulate_riffle_shuffle.py @@ -0,0 +1,57 @@ +import functools + +# Create a funtion that executed f recusively n times, i.e. f**n +def power(f, n): + functions = [f for _ in range(n)] + def compose2(f, g): + return lambda x: f(g(x)) + return functools.reduce(compose2, functions, lambda x: x) + +# Rotate input to the left by n positions +def rotate_left(input, n): + return input[n:] + input[0:n] + +def riffle_shuffle(input): + left = input[0:len(input)//2] + right = input[len(input)//2:] + i = 0 + out = "" + while i < len(input)//2: + out += right[i] + left[i] + i += 1 + return out + +values = [ + "alice123----------------", # 0 + "alice485----------------", # 1 + "aliceimwunderland521----", # 2 + "bob1--------------------", # 3 + "bob123------------------", # 4 + "bob485------------------", # 5 + "bob511------------------", # 6 + "creator-----------------", # 7 +] + +def digit_sum(input): + def value(char): + if char == "-": + return 0 + else: + return ord(char) + return sum([value(c) for c in input]) + +shuffle = power(riffle_shuffle, 18) +rotated = [rotate_left(v, digit_sum(v) % 24) for v in values] +rotated_shuffled = [shuffle(r) for r in rotated] +shuffled = [shuffle(v) for v in values] + +print("Original:\n" + "\n".join(sorted(values))) +print() +# digit_sums = [str(digit_sum(v) % 24) for v in values] +# print("Digit sums:\n" + "\n".join(digit_sums)) +# print() +print("Rotated:\n" + "\n".join(sorted(rotated))) +print() +print("Shuffled:\n" + "\n".join(sorted(shuffled))) +print() +print("Rotated+Shuffled:\n" + "\n".join(sorted(rotated_shuffled))) diff --git a/packages/ext/Cargo.toml b/packages/ext/Cargo.toml index 6014becef..b149eb53b 100644 --- a/packages/ext/Cargo.toml +++ b/packages/ext/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cosmwasm-std = { path = "../std", version = "0.10.0" } +cosmwasm-std = { path = "../std", version = "0.12.0" } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } serde_json = "1.0" diff --git a/packages/ext/src/querier_collection.rs b/packages/ext/src/querier_collection.rs index eb503a0a9..fe54dd85b 100644 --- a/packages/ext/src/querier_collection.rs +++ b/packages/ext/src/querier_collection.rs @@ -1,13 +1,13 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{HumanAddr, Querier, StdResult, Uint128}; +use cosmwasm_std::{HumanAddr, QuerierWrapper, StdResult, Uint128}; use crate::collection::{Collection, CollectionPerm, Token, TokenType}; use crate::query::{LinkQueryWrapper, Module, QueryData, Response}; -pub struct LinkCollectionQuerier<'a, Q: Querier> { - querier: &'a Q, +pub struct LinkCollectionQuerier<'a> { + querier: QuerierWrapper<'a>, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -82,8 +82,8 @@ pub enum CollectionQuery { }, } -impl<'a, Q: Querier> LinkCollectionQuerier<'a, Q> { - pub fn new(querier: &'a Q) -> Self { +impl<'a> LinkCollectionQuerier<'a> { + pub fn new(querier: QuerierWrapper<'a>) -> Self { LinkCollectionQuerier { querier } } diff --git a/packages/ext/src/querier_token.rs b/packages/ext/src/querier_token.rs index 71f7cb7e0..410b21b56 100644 --- a/packages/ext/src/querier_token.rs +++ b/packages/ext/src/querier_token.rs @@ -1,13 +1,13 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{HumanAddr, Querier, StdResult, Uint128}; +use cosmwasm_std::{HumanAddr, QuerierWrapper, StdResult, Uint128}; use crate::query::{LinkQueryWrapper, Module, QueryData, Response}; use crate::token::{Token, TokenPerm}; -pub struct LinkTokenQuerier<'a, Q: Querier> { - querier: &'a Q, +pub struct LinkTokenQuerier<'a> { + querier: QuerierWrapper<'a>, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -51,8 +51,8 @@ pub enum TokenQuery { }, } -impl<'a, Q: Querier> LinkTokenQuerier<'a, Q> { - pub fn new(querier: &'a Q) -> Self { +impl<'a> LinkTokenQuerier<'a> { + pub fn new(querier: QuerierWrapper<'a>) -> Self { LinkTokenQuerier { querier } } diff --git a/packages/ext/src/query.rs b/packages/ext/src/query.rs index 8c5c1de12..d59c2481a 100644 --- a/packages/ext/src/query.rs +++ b/packages/ext/src/query.rs @@ -2,7 +2,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::str::FromStr; -use cosmwasm_std::QueryRequest; +use cosmwasm_std::CustomQuery; use crate::querier_collection::{CollectionQuery, CollectionQueryRoute}; use crate::querier_token::{TokenQuery, TokenQueryRoute}; @@ -28,21 +28,9 @@ pub struct QueryData { pub data: D, } -impl Into>> - for LinkQueryWrapper -{ - fn into(self) -> QueryRequest> { - QueryRequest::Custom(self) - } -} +impl CustomQuery for LinkQueryWrapper {} -impl Into>> - for LinkQueryWrapper -{ - fn into(self) -> QueryRequest> { - QueryRequest::Custom(self) - } -} +impl CustomQuery for LinkQueryWrapper {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/packages/schema/Cargo.toml b/packages/schema/Cargo.toml index 08bb81c43..b8905247d 100644 --- a/packages/schema/Cargo.toml +++ b/packages/schema/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmwasm-schema" -version = "0.10.0" +version = "0.12.0" authors = ["Simon Warta ", "Ethan Frey "] edition = "2018" description = "A dev-dependency for CosmWasm contracts to generate JSON Schema files." diff --git a/packages/std/.cargo/config b/packages/std/.cargo/config index 8c7cbe465..7d1a066c8 100644 --- a/packages/std/.cargo/config +++ b/packages/std/.cargo/config @@ -1,5 +1,5 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" +unit-test = "test --lib" schema = "run --example schema" diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml index bd1e8ae2a..d64da8de4 100644 --- a/packages/std/Cargo.toml +++ b/packages/std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmwasm-std" -version = "0.10.0" +version = "0.12.0" authors = ["Ethan Frey "] edition = "2018" description = "Standard library for Wasm based smart contracts on Cosmos blockchains" @@ -25,14 +25,17 @@ iterator = [] staking = [] # backtraces provides much better context at runtime errors (in non-wasm code) # at the cost of a bit of code size and performance. -backtraces = ["snafu/backtraces"] +# This feature requires Rust nightly because it depends on the unstable backtrace feature. +backtraces = [] [dependencies] base64 = "0.11.0" serde-json-wasm = { version = "0.2.1" } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } -snafu = { version = "0.6.6" } +thiserror = "1.0" [dev-dependencies] cosmwasm-schema = { path = "../schema" } +# The chrono dependency is only used in an example, which Rust compiles for us. If this causes trouble, remove it. +chrono = "0.4" diff --git a/packages/std/examples/schema.rs b/packages/std/examples/schema.rs index 71f4254ad..0c1eb1ca9 100644 --- a/packages/std/examples/schema.rs +++ b/packages/std/examples/schema.rs @@ -2,7 +2,7 @@ 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 cosmwasm_std::{CosmosMsg, Env, HandleResult, InitResult, MigrateResult, QueryResult}; +use cosmwasm_std::{CosmosMsg, Env, MessageInfo}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -11,9 +11,6 @@ fn main() { remove_schemas(&out_dir).unwrap(); export_schema(&schema_for!(Env), &out_dir); - export_schema(&schema_for!(CosmosMsg), &out_dir); - export_schema_with_title(&mut schema_for!(InitResult), &out_dir, "InitResult"); - export_schema_with_title(&mut schema_for!(HandleResult), &out_dir, "HandleResult"); - export_schema_with_title(&mut schema_for!(MigrateResult), &out_dir, "MigrateResult"); - export_schema_with_title(&mut schema_for!(QueryResult), &out_dir, "QueryResult"); + export_schema(&schema_for!(MessageInfo), &out_dir); + export_schema_with_title(&mut schema_for!(CosmosMsg), &out_dir, "CosmosMsg"); } diff --git a/packages/std/schema/cosmos_msg_for__empty.json b/packages/std/schema/cosmos_msg.json similarity index 99% rename from packages/std/schema/cosmos_msg_for__empty.json rename to packages/std/schema/cosmos_msg.json index 758de9270..60bcad93f 100644 --- a/packages/std/schema/cosmos_msg_for__empty.json +++ b/packages/std/schema/cosmos_msg.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CosmosMsg_for_Empty", + "title": "CosmosMsg", "anyOf": [ { "type": "object", diff --git a/packages/std/schema/env.json b/packages/std/schema/env.json index 9690533e7..5a44676ef 100644 --- a/packages/std/schema/env.json +++ b/packages/std/schema/env.json @@ -4,8 +4,7 @@ "type": "object", "required": [ "block", - "contract", - "message" + "contract" ], "properties": { "block": { @@ -13,9 +12,6 @@ }, "contract": { "$ref": "#/definitions/ContractInfo" - }, - "message": { - "$ref": "#/definitions/MessageInfo" } }, "definitions": { @@ -24,7 +20,8 @@ "required": [ "chain_id", "height", - "time" + "time", + "time_nanos" ], "properties": { "chain_id": { @@ -36,24 +33,16 @@ "minimum": 0.0 }, "time": { + "description": "Absolute time of the block creation in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC).\n\nThe source of this is the [BFT Time in Tendermint](https://docs.tendermint.com/master/spec/consensus/bft-time.html), converted from nanoseconds to second precision by truncating the fractioal part.", "type": "integer", "format": "uint64", "minimum": 0.0 - } - } - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" }, - "denom": { - "type": "string" + "time_nanos": { + "description": "The fractional part of the block time in nanoseconds since `time` (0 to 999999999). Add this to `time` if you need a high precision block time.\n\n# Examples\n\nUsing chrono:\n\n``` # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; # let env = Env { # block: BlockInfo { # height: 12_345, # time: 1_571_797_419, # time_nanos: 879305533, # chain_id: \"cosmos-testnet-14002\".to_string(), # }, # contract: ContractInfo { # address: HumanAddr::from(\"contract\"), # }, # }; # extern crate chrono; use chrono::NaiveDateTime; let dt = NaiveDateTime::from_timestamp(env.block.time as i64, env.block.time_nanos as u32); ```\n\nCreating a simple millisecond-precision timestamp (as used in JavaScript):\n\n``` # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; # let env = Env { # block: BlockInfo { # height: 12_345, # time: 1_571_797_419, # time_nanos: 879305533, # chain_id: \"cosmos-testnet-14002\".to_string(), # }, # contract: ContractInfo { # address: HumanAddr::from(\"contract\"), # }, # }; let millis = (env.block.time * 1_000) + (env.block.time_nanos / 1_000_000); ```", + "type": "integer", + "format": "uint64", + "minimum": 0.0 } } }, @@ -70,32 +59,6 @@ }, "HumanAddr": { "type": "string" - }, - "MessageInfo": { - "type": "object", - "required": [ - "sender", - "sent_funds" - ], - "properties": { - "sender": { - "description": "The `sender` field from the wasm/store-code, wasm/instantiate or wasm/execute message. You can think of this as the address that initiated the action (i.e. the message). What that means exactly heavily depends on the application.\n\nThe x/wasm module ensures that the sender address signed the transaction. Additional signers of the transaction that are either needed for other messages or contain unnecessary signatures are not propagated into the contract.\n\nThere is a discussion to open up this field to multiple initiators, which you're welcome to join if you have a specific need for that feature: https://github.com/CosmWasm/cosmwasm/issues/293", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] - }, - "sent_funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - }, - "Uint128": { - "type": "string" } } } diff --git a/packages/std/schema/handle_result.json b/packages/std/schema/handle_result.json deleted file mode 100644 index 238600d05..000000000 --- a/packages/std/schema/handle_result.json +++ /dev/null @@ -1,544 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleResult", - "oneOf": [ - { - "type": "object", - "required": [ - "Ok" - ], - "properties": { - "Ok": { - "$ref": "#/definitions/HandleResponse_for_Empty" - } - } - }, - { - "type": "object", - "required": [ - "Err" - ], - "properties": { - "Err": { - "$ref": "#/definitions/StdError" - } - } - } - ], - "definitions": { - "BankMsg": { - "anyOf": [ - { - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "from_address", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "from_address": { - "$ref": "#/definitions/HumanAddr" - }, - "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" - } - } - }, - { - "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" - }, - "HandleResponse_for_Empty": { - "type": "object", - "required": [ - "log", - "messages" - ], - "properties": { - "data": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "log": { - "type": "array", - "items": { - "$ref": "#/definitions/LogAttribute" - } - }, - "messages": { - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - } - } - }, - "HumanAddr": { - "type": "string" - }, - "LogAttribute": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "StakingMsg": { - "anyOf": [ - { - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "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" - } - } - } - } - }, - { - "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" - } - } - } - } - } - ] - }, - "StdError": { - "description": "Structured error type for init, handle and query.\n\nThis can be serialized and passed over the Wasm/VM boundary, which allows us to use structured error types in e.g. integration tests. In that process backtraces are stripped off.\n\nThe prefix \"Std\" means \"the standard error within the standard library\". This is not the only result/error type in cosmwasm-std.\n\nWhen new cases are added, they should describe the problem rather than what was attempted (e.g. InvalidBase64 is preferred over Base64DecodingErr). In the long run this allows us to get rid of the duplication in \"StdError::FooErr\".\n\nChecklist for adding a new error: - Add enum case - Add to PartialEq implementation - Add serialize/deserialize test - Add creator function in std_error_helpers.rs - Regenerate schemas", - "anyOf": [ - { - "description": "Whenever there is no specific error type available", - "type": "object", - "required": [ - "generic_err" - ], - "properties": { - "generic_err": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "invalid_base64" - ], - "properties": { - "invalid_base64": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "description": "Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8.", - "type": "object", - "required": [ - "invalid_utf8" - ], - "properties": { - "invalid_utf8": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "not_found" - ], - "properties": { - "not_found": { - "type": "object", - "required": [ - "kind" - ], - "properties": { - "kind": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "parse_err" - ], - "properties": { - "parse_err": { - "type": "object", - "required": [ - "msg", - "target" - ], - "properties": { - "msg": { - "type": "string" - }, - "target": { - "description": "the target type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "serialize_err" - ], - "properties": { - "serialize_err": { - "type": "object", - "required": [ - "msg", - "source" - ], - "properties": { - "msg": { - "type": "string" - }, - "source": { - "description": "the source type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unauthorized" - ], - "properties": { - "unauthorized": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "underflow" - ], - "properties": { - "underflow": { - "type": "object", - "required": [ - "minuend", - "subtrahend" - ], - "properties": { - "minuend": { - "type": "string" - }, - "subtrahend": { - "type": "string" - } - } - } - } - } - ] - }, - "Uint128": { - "type": "string" - }, - "WasmMsg": { - "anyOf": [ - { - "description": "this dispatches a call to another contract at a known address (with known ABI)", - "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": "this instantiates a new contracts from previously uploaded wasm code", - "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/packages/std/schema/init_result.json b/packages/std/schema/init_result.json deleted file mode 100644 index 9b41e87e3..000000000 --- a/packages/std/schema/init_result.json +++ /dev/null @@ -1,534 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitResult", - "oneOf": [ - { - "type": "object", - "required": [ - "Ok" - ], - "properties": { - "Ok": { - "$ref": "#/definitions/InitResponse_for_Empty" - } - } - }, - { - "type": "object", - "required": [ - "Err" - ], - "properties": { - "Err": { - "$ref": "#/definitions/StdError" - } - } - } - ], - "definitions": { - "BankMsg": { - "anyOf": [ - { - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "from_address", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "from_address": { - "$ref": "#/definitions/HumanAddr" - }, - "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" - } - } - }, - { - "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" - }, - "InitResponse_for_Empty": { - "type": "object", - "required": [ - "log", - "messages" - ], - "properties": { - "log": { - "type": "array", - "items": { - "$ref": "#/definitions/LogAttribute" - } - }, - "messages": { - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - } - } - }, - "LogAttribute": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "StakingMsg": { - "anyOf": [ - { - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "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" - } - } - } - } - }, - { - "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" - } - } - } - } - } - ] - }, - "StdError": { - "description": "Structured error type for init, handle and query.\n\nThis can be serialized and passed over the Wasm/VM boundary, which allows us to use structured error types in e.g. integration tests. In that process backtraces are stripped off.\n\nThe prefix \"Std\" means \"the standard error within the standard library\". This is not the only result/error type in cosmwasm-std.\n\nWhen new cases are added, they should describe the problem rather than what was attempted (e.g. InvalidBase64 is preferred over Base64DecodingErr). In the long run this allows us to get rid of the duplication in \"StdError::FooErr\".\n\nChecklist for adding a new error: - Add enum case - Add to PartialEq implementation - Add serialize/deserialize test - Add creator function in std_error_helpers.rs - Regenerate schemas", - "anyOf": [ - { - "description": "Whenever there is no specific error type available", - "type": "object", - "required": [ - "generic_err" - ], - "properties": { - "generic_err": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "invalid_base64" - ], - "properties": { - "invalid_base64": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "description": "Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8.", - "type": "object", - "required": [ - "invalid_utf8" - ], - "properties": { - "invalid_utf8": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "not_found" - ], - "properties": { - "not_found": { - "type": "object", - "required": [ - "kind" - ], - "properties": { - "kind": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "parse_err" - ], - "properties": { - "parse_err": { - "type": "object", - "required": [ - "msg", - "target" - ], - "properties": { - "msg": { - "type": "string" - }, - "target": { - "description": "the target type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "serialize_err" - ], - "properties": { - "serialize_err": { - "type": "object", - "required": [ - "msg", - "source" - ], - "properties": { - "msg": { - "type": "string" - }, - "source": { - "description": "the source type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unauthorized" - ], - "properties": { - "unauthorized": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "underflow" - ], - "properties": { - "underflow": { - "type": "object", - "required": [ - "minuend", - "subtrahend" - ], - "properties": { - "minuend": { - "type": "string" - }, - "subtrahend": { - "type": "string" - } - } - } - } - } - ] - }, - "Uint128": { - "type": "string" - }, - "WasmMsg": { - "anyOf": [ - { - "description": "this dispatches a call to another contract at a known address (with known ABI)", - "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": "this instantiates a new contracts from previously uploaded wasm code", - "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/packages/std/schema/message_info.json b/packages/std/schema/message_info.json new file mode 100644 index 000000000..3251ebf04 --- /dev/null +++ b/packages/std/schema/message_info.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MessageInfo", + "description": "MessageInfo is sent with `init`, `handle`, and `migrate` calls, but not with queries. It contains the essential info for authorization - identity of the call, and payment", + "type": "object", + "required": [ + "sender", + "sent_funds" + ], + "properties": { + "sender": { + "description": "The `sender` field from the `wasm/MsgStoreCode`, `wasm/MsgInstantiateContract`, `wasm/MsgMigrateContract` or `wasm/MsgExecuteContract` message. You can think of this as the address that initiated the action (i.e. the message). What that means exactly heavily depends on the application.\n\nThe x/wasm module ensures that the sender address signed the transaction. Additional signers of the transaction that are either needed for other messages or contain unnecessary signatures are not propagated into the contract.\n\nThere is a discussion to open up this field to multiple initiators, which you're welcome to join if you have a specific need for that feature: https://github.com/CosmWasm/cosmwasm/issues/293", + "allOf": [ + { + "$ref": "#/definitions/HumanAddr" + } + ] + }, + "sent_funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/std/schema/migrate_result.json b/packages/std/schema/migrate_result.json deleted file mode 100644 index 714e8990b..000000000 --- a/packages/std/schema/migrate_result.json +++ /dev/null @@ -1,544 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateResult", - "oneOf": [ - { - "type": "object", - "required": [ - "Ok" - ], - "properties": { - "Ok": { - "$ref": "#/definitions/MigrateResponse_for_Empty" - } - } - }, - { - "type": "object", - "required": [ - "Err" - ], - "properties": { - "Err": { - "$ref": "#/definitions/StdError" - } - } - } - ], - "definitions": { - "BankMsg": { - "anyOf": [ - { - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "from_address", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "from_address": { - "$ref": "#/definitions/HumanAddr" - }, - "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" - } - } - }, - { - "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" - }, - "LogAttribute": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "MigrateResponse_for_Empty": { - "type": "object", - "required": [ - "log", - "messages" - ], - "properties": { - "data": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "log": { - "type": "array", - "items": { - "$ref": "#/definitions/LogAttribute" - } - }, - "messages": { - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - } - } - }, - "StakingMsg": { - "anyOf": [ - { - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "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" - } - } - } - } - }, - { - "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" - } - } - } - } - } - ] - }, - "StdError": { - "description": "Structured error type for init, handle and query.\n\nThis can be serialized and passed over the Wasm/VM boundary, which allows us to use structured error types in e.g. integration tests. In that process backtraces are stripped off.\n\nThe prefix \"Std\" means \"the standard error within the standard library\". This is not the only result/error type in cosmwasm-std.\n\nWhen new cases are added, they should describe the problem rather than what was attempted (e.g. InvalidBase64 is preferred over Base64DecodingErr). In the long run this allows us to get rid of the duplication in \"StdError::FooErr\".\n\nChecklist for adding a new error: - Add enum case - Add to PartialEq implementation - Add serialize/deserialize test - Add creator function in std_error_helpers.rs - Regenerate schemas", - "anyOf": [ - { - "description": "Whenever there is no specific error type available", - "type": "object", - "required": [ - "generic_err" - ], - "properties": { - "generic_err": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "invalid_base64" - ], - "properties": { - "invalid_base64": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "description": "Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8.", - "type": "object", - "required": [ - "invalid_utf8" - ], - "properties": { - "invalid_utf8": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "not_found" - ], - "properties": { - "not_found": { - "type": "object", - "required": [ - "kind" - ], - "properties": { - "kind": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "parse_err" - ], - "properties": { - "parse_err": { - "type": "object", - "required": [ - "msg", - "target" - ], - "properties": { - "msg": { - "type": "string" - }, - "target": { - "description": "the target type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "serialize_err" - ], - "properties": { - "serialize_err": { - "type": "object", - "required": [ - "msg", - "source" - ], - "properties": { - "msg": { - "type": "string" - }, - "source": { - "description": "the source type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unauthorized" - ], - "properties": { - "unauthorized": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "underflow" - ], - "properties": { - "underflow": { - "type": "object", - "required": [ - "minuend", - "subtrahend" - ], - "properties": { - "minuend": { - "type": "string" - }, - "subtrahend": { - "type": "string" - } - } - } - } - } - ] - }, - "Uint128": { - "type": "string" - }, - "WasmMsg": { - "anyOf": [ - { - "description": "this dispatches a call to another contract at a known address (with known ABI)", - "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": "this instantiates a new contracts from previously uploaded wasm code", - "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/packages/std/schema/query_result.json b/packages/std/schema/query_result.json deleted file mode 100644 index e03bd6888..000000000 --- a/packages/std/schema/query_result.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryResult", - "oneOf": [ - { - "type": "object", - "required": [ - "Ok" - ], - "properties": { - "Ok": { - "$ref": "#/definitions/Binary" - } - } - }, - { - "type": "object", - "required": [ - "Err" - ], - "properties": { - "Err": { - "$ref": "#/definitions/StdError" - } - } - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "StdError": { - "description": "Structured error type for init, handle and query.\n\nThis can be serialized and passed over the Wasm/VM boundary, which allows us to use structured error types in e.g. integration tests. In that process backtraces are stripped off.\n\nThe prefix \"Std\" means \"the standard error within the standard library\". This is not the only result/error type in cosmwasm-std.\n\nWhen new cases are added, they should describe the problem rather than what was attempted (e.g. InvalidBase64 is preferred over Base64DecodingErr). In the long run this allows us to get rid of the duplication in \"StdError::FooErr\".\n\nChecklist for adding a new error: - Add enum case - Add to PartialEq implementation - Add serialize/deserialize test - Add creator function in std_error_helpers.rs - Regenerate schemas", - "anyOf": [ - { - "description": "Whenever there is no specific error type available", - "type": "object", - "required": [ - "generic_err" - ], - "properties": { - "generic_err": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "invalid_base64" - ], - "properties": { - "invalid_base64": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "description": "Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8.", - "type": "object", - "required": [ - "invalid_utf8" - ], - "properties": { - "invalid_utf8": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "not_found" - ], - "properties": { - "not_found": { - "type": "object", - "required": [ - "kind" - ], - "properties": { - "kind": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "parse_err" - ], - "properties": { - "parse_err": { - "type": "object", - "required": [ - "msg", - "target" - ], - "properties": { - "msg": { - "type": "string" - }, - "target": { - "description": "the target type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "serialize_err" - ], - "properties": { - "serialize_err": { - "type": "object", - "required": [ - "msg", - "source" - ], - "properties": { - "msg": { - "type": "string" - }, - "source": { - "description": "the source type that was attempted", - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unauthorized" - ], - "properties": { - "unauthorized": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "underflow" - ], - "properties": { - "underflow": { - "type": "object", - "required": [ - "minuend", - "subtrahend" - ], - "properties": { - "minuend": { - "type": "string" - }, - "subtrahend": { - "type": "string" - } - } - } - } - } - ] - } - } -} diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index cba8fc089..1cf6db276 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -1,25 +1,17 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt; +use std::ops::Deref; -use crate::encoding::Binary; +use crate::binary::Binary; -// Added Eq and Hash to allow this to be a key in a HashMap (MockQuerier) -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema, Hash)] +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, JsonSchema)] pub struct HumanAddr(pub String); impl HumanAddr { pub fn as_str(&self) -> &str { &self.0 } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } } impl fmt::Display for HumanAddr { @@ -52,7 +44,51 @@ impl From for HumanAddr { } } -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] +/// Just like String, HumanAddr is a smart pointer to str. +/// This implements `*human_address` for us, which is not very valuable directly +/// because str has no known size and cannot be stored in variables. But it allows us to +/// do `&*human_address`, returning a `&str` from a `&HumanAddr`. +/// With [deref coercions](https://doc.rust-lang.org/1.22.1/book/first-edition/deref-coercions.html#deref-coercions), +/// this allows us to use `&human_address` whenever a `&str` is required. +impl Deref for HumanAddr { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +/// Implement `HumanAddr == str`, which gives us `&HumanAddr == &str`. +/// Do we really need &HumanAddr comparisons? +impl PartialEq for HumanAddr { + fn eq(&self, rhs: &str) -> bool { + self.0 == rhs + } +} + +/// Implement `str == HumanAddr`, which gives us `&str == &HumanAddr`. +/// Do we really need &HumanAddr comparisons? +impl PartialEq for str { + fn eq(&self, rhs: &HumanAddr) -> bool { + self == rhs.0 + } +} + +/// Implement `HumanAddr == &str` +impl PartialEq<&str> for HumanAddr { + fn eq(&self, rhs: &&str) -> bool { + self.0 == *rhs + } +} + +/// Implement `&str == HumanAddr` +impl PartialEq for &str { + fn eq(&self, rhs: &HumanAddr) -> bool { + *self == rhs.0 + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, JsonSchema)] pub struct CanonicalAddr(pub Binary); impl From<&[u8]> for CanonicalAddr { @@ -67,22 +103,297 @@ impl From> for CanonicalAddr { } } -impl CanonicalAddr { - pub fn as_slice(&self) -> &[u8] { - &self.0.as_slice() +impl From for Vec { + fn from(source: CanonicalAddr) -> Vec { + source.0.into() } +} + +/// Just like Vec, CanonicalAddr is a smart pointer to [u8]. +/// This implements `*canonical_address` for us and allows us to +/// do `&*canonical_address`, returning a `&[u8]` from a `&CanonicalAddr`. +/// With [deref coercions](https://doc.rust-lang.org/1.22.1/book/first-edition/deref-coercions.html#deref-coercions), +/// this allows us to use `&canonical_address` whenever a `&[u8]` is required. +impl Deref for CanonicalAddr { + type Target = [u8]; - pub fn len(&self) -> usize { - self.0.len() + fn deref(&self) -> &Self::Target { + self.as_slice() } +} - pub fn is_empty(&self) -> bool { - self.0.is_empty() +impl CanonicalAddr { + pub fn as_slice(&self) -> &[u8] { + &self.0.as_slice() } } impl fmt::Display for CanonicalAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + for byte in self.0.as_slice() { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::collections::hash_map::DefaultHasher; + use std::collections::HashSet; + use std::hash::{Hash, Hasher}; + use std::iter::FromIterator; + + // Test HumanAddr as_str() for each HumanAddr::from input type + #[test] + fn human_addr_as_str() { + // literal string + let human_addr_from_literal_string = HumanAddr::from("literal-string"); + assert_eq!("literal-string", human_addr_from_literal_string.as_str()); + + // String + let addr = String::from("Hello, world!"); + let human_addr_from_string = HumanAddr::from(addr); + assert_eq!("Hello, world!", human_addr_from_string.as_str()); + + // &HumanAddr + let human_addr_from_borrow = HumanAddr::from(&human_addr_from_string); + assert_eq!( + human_addr_from_borrow.as_str(), + human_addr_from_string.as_str() + ); + + // &&HumanAddr + let human_addr_from_borrow_2 = HumanAddr::from(&&human_addr_from_string); + assert_eq!( + human_addr_from_borrow_2.as_str(), + human_addr_from_string.as_str() + ); + } + + #[test] + fn human_addr_implements_display() { + let human_addr = HumanAddr::from("cos934gh9034hg04g0h134"); + let embedded = format!("Address: {}", human_addr); + assert_eq!(embedded, "Address: cos934gh9034hg04g0h134"); + assert_eq!(human_addr.to_string(), "cos934gh9034hg04g0h134"); + } + + #[test] + fn human_addr_implements_deref() { + // We cannot test *human_addr directly since the resulting type str has no known size + let human_addr = HumanAddr::from("cos934gh9034hg04g0h134"); + assert_eq!(&*human_addr, "cos934gh9034hg04g0h134"); + + // This checks deref coercions from &HumanAddr to &str works + let human_addr = HumanAddr::from("cos934gh9034hg04g0h134"); + assert_eq!(human_addr.len(), 22); + let human_addr_str: &str = &human_addr; + assert_eq!(human_addr_str, "cos934gh9034hg04g0h134"); + } + + #[test] + fn human_addr_implements_partial_eq_with_str() { + let addr = HumanAddr::from("cos934gh9034hg04g0h134"); + + // Owned HumanAddr + assert_eq!(addr, "cos934gh9034hg04g0h134"); + assert_eq!("cos934gh9034hg04g0h134", addr); + assert_ne!(addr, "mos973z7z"); + assert_ne!("mos973z7z", addr); + + // HumanAddr reference (do we really need those?) + assert_eq!(&addr, "cos934gh9034hg04g0h134"); + assert_eq!("cos934gh9034hg04g0h134", &addr); + assert_ne!(&addr, "mos973z7z"); + assert_ne!("mos973z7z", &addr); + } + + #[test] + fn human_addr_implements_hash() { + let alice1 = HumanAddr::from("alice"); + let mut hasher = DefaultHasher::new(); + alice1.hash(&mut hasher); + let alice1_hash = hasher.finish(); + + let alice2 = HumanAddr::from("alice"); + let mut hasher = DefaultHasher::new(); + alice2.hash(&mut hasher); + let alice2_hash = hasher.finish(); + + let bob = HumanAddr::from("bob"); + let mut hasher = DefaultHasher::new(); + bob.hash(&mut hasher); + let bob_hash = hasher.finish(); + + assert_eq!(alice1_hash, alice2_hash); + assert_ne!(alice1_hash, bob_hash); + } + + /// This requires Hash and Eq to be implemented + #[test] + fn human_addr_can_be_used_in_hash_set() { + let alice1 = HumanAddr::from("alice"); + let alice2 = HumanAddr::from("alice"); + let bob = HumanAddr::from("bob"); + + let mut set = HashSet::new(); + set.insert(alice1.clone()); + set.insert(alice2.clone()); + set.insert(bob.clone()); + assert_eq!(set.len(), 2); + + let set1 = HashSet::::from_iter(vec![bob.clone(), alice1.clone()]); + let set2 = HashSet::from_iter(vec![alice1.clone(), alice2.clone(), bob.clone()]); + assert_eq!(set1, set2); + } + + #[test] + fn human_addr_len() { + let addr = "Hello, world!"; + let human_addr = HumanAddr::from(addr); + assert_eq!(addr.len(), human_addr.len()); + } + + #[test] + fn human_addr_is_empty() { + let human_addr = HumanAddr::from("Hello, world!"); + assert_eq!(false, human_addr.is_empty()); + let empty_human_addr = HumanAddr::from(""); + assert_eq!(true, empty_human_addr.is_empty()); + } + + // Test CanonicalAddr as_slice() for each CanonicalAddr::from input type + #[test] + fn canonical_addr_from_slice() { + // slice + let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0]; + let canonical_addr_slice = CanonicalAddr::from(bytes); + assert_eq!(canonical_addr_slice.as_slice(), &[0u8, 187, 61, 11, 250, 0]); + + // Vector + let bytes: Vec = vec![0u8, 187, 61, 11, 250, 0]; + let canonical_addr_vec = CanonicalAddr::from(bytes); + assert_eq!(canonical_addr_vec.as_slice(), &[0u8, 187, 61, 11, 250, 0]); + } + + #[test] + fn canonical_addr_from_vec_works() { + // Into for Vec + let original = vec![0u8, 187, 61, 11, 250, 0]; + let original_ptr = original.as_ptr(); + let addr: CanonicalAddr = original.into(); + assert_eq!(addr.as_slice(), [0u8, 187, 61, 11, 250, 0]); + assert_eq!((addr.0).0.as_ptr(), original_ptr, "must not be copied"); + + // From> for CanonicalAddr + let original = vec![0u8, 187, 61, 11, 250, 0]; + let original_ptr = original.as_ptr(); + let addr = CanonicalAddr::from(original); + assert_eq!(addr.as_slice(), [0u8, 187, 61, 11, 250, 0]); + assert_eq!((addr.0).0.as_ptr(), original_ptr, "must not be copied"); + } + + #[test] + fn canonical_addr_into_vec_works() { + // Into> for CanonicalAddr + let original = CanonicalAddr::from(vec![0u8, 187, 61, 11, 250, 0]); + let original_ptr = (original.0).0.as_ptr(); + let vec: Vec = original.into(); + assert_eq!(vec.as_slice(), [0u8, 187, 61, 11, 250, 0]); + assert_eq!(vec.as_ptr(), original_ptr, "must not be copied"); + + // From for Vec + let original = CanonicalAddr::from(vec![7u8, 35, 49, 101, 0, 255]); + let original_ptr = (original.0).0.as_ptr(); + let vec = Vec::::from(original); + assert_eq!(vec.as_slice(), [7u8, 35, 49, 101, 0, 255]); + assert_eq!(vec.as_ptr(), original_ptr, "must not be copied"); + } + + #[test] + fn canonical_addr_len() { + let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0]; + let canonical_addr = CanonicalAddr::from(bytes); + assert_eq!(canonical_addr.len(), bytes.len()); + } + + #[test] + fn canonical_addr_is_empty() { + let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0]; + let canonical_addr = CanonicalAddr::from(bytes); + assert_eq!(false, canonical_addr.is_empty()); + let empty_canonical_addr = CanonicalAddr::from(vec![]); + assert_eq!(true, empty_canonical_addr.is_empty()); + } + + #[test] + fn canonical_addr_implements_display() { + let bytes: &[u8] = &[ + 0x12, // two hex digits + 0x03, // small values must be padded to two digits + 0xab, // ensure we get upper case + 0x00, // always test extreme values + 0xff, + ]; + let address = CanonicalAddr::from(bytes); + let embedded = format!("Address: {}", address); + assert_eq!(embedded, "Address: 1203AB00FF"); + assert_eq!(address.to_string(), "1203AB00FF"); + } + + #[test] + fn canonical_addr_implements_deref() { + // Dereference to [u8] + let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0]; + let canonical_addr = CanonicalAddr::from(bytes); + assert_eq!(*canonical_addr, [0u8, 187, 61, 11, 250, 0]); + + // This checks deref coercions from &CanonicalAddr to &[u8] works + let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0]; + let canonical_addr = CanonicalAddr::from(bytes); + assert_eq!(canonical_addr.len(), 6); + let canonical_addr_slice: &[u8] = &canonical_addr; + assert_eq!(canonical_addr_slice, &[0u8, 187, 61, 11, 250, 0]); + } + + #[test] + fn canonical_addr_implements_hash() { + let alice1 = CanonicalAddr(Binary::from([0, 187, 61, 11, 250, 0])); + let mut hasher = DefaultHasher::new(); + alice1.hash(&mut hasher); + let alice1_hash = hasher.finish(); + + let alice2 = CanonicalAddr(Binary::from([0, 187, 61, 11, 250, 0])); + let mut hasher = DefaultHasher::new(); + alice2.hash(&mut hasher); + let alice2_hash = hasher.finish(); + + let bob = CanonicalAddr(Binary::from([16, 21, 33, 0, 255, 9])); + let mut hasher = DefaultHasher::new(); + bob.hash(&mut hasher); + let bob_hash = hasher.finish(); + + assert_eq!(alice1_hash, alice2_hash); + assert_ne!(alice1_hash, bob_hash); + } + + /// This requires Hash and Eq to be implemented + #[test] + fn canonical_addr_can_be_used_in_hash_set() { + let alice1 = CanonicalAddr(Binary::from([0, 187, 61, 11, 250, 0])); + let alice2 = CanonicalAddr(Binary::from([0, 187, 61, 11, 250, 0])); + let bob = CanonicalAddr(Binary::from([16, 21, 33, 0, 255, 9])); + + let mut set = HashSet::new(); + set.insert(alice1.clone()); + set.insert(alice2.clone()); + set.insert(bob.clone()); + assert_eq!(set.len(), 2); + + let set1 = HashSet::::from_iter(vec![bob.clone(), alice1.clone()]); + let set2 = HashSet::from_iter(vec![alice1.clone(), alice2.clone(), bob.clone()]); + assert_eq!(set1, set2); } } diff --git a/packages/std/src/encoding.rs b/packages/std/src/binary.rs similarity index 51% rename from packages/std/src/encoding.rs rename to packages/std/src/binary.rs index fa9eaf21c..d824b5e10 100644 --- a/packages/std/src/encoding.rs +++ b/packages/std/src/binary.rs @@ -1,4 +1,6 @@ use std::fmt; +use std::mem; +use std::ops::Deref; use schemars::JsonSchema; use serde::{de, ser, Deserialize, Deserializer, Serialize}; @@ -9,7 +11,7 @@ use crate::errors::{StdError, StdResult}; /// with serde. It also adds some helper methods to help encode inline. /// /// This is only needed as serde-json-{core,wasm} has a horrible encoding for Vec -#[derive(Clone, Default, Debug, PartialEq, JsonSchema)] +#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, JsonSchema)] pub struct Binary(#[schemars(with = "String")] pub Vec); impl Binary { @@ -25,14 +27,52 @@ impl Binary { pub fn to_base64(&self) -> String { base64::encode(&self.0) } + pub fn as_slice(&self) -> &[u8] { self.0.as_slice() } - pub fn len(&self) -> usize { - self.0.len() - } - pub fn is_empty(&self) -> bool { - self.0.is_empty() + + /// Copies content into fixed-sized array. + /// The result type `A: ByteArray` is a workaround for + /// the missing [const-generics](https://rust-lang.github.io/rfcs/2000-const-generics.html). + /// `A` is a fixed-sized array like `[u8; 8]`. + /// + /// ByteArray is implemented for `[u8; 0]` to `[u8; 32]`, such that + /// we are limited by 32 bytes for now. + /// + /// # Examples + /// + /// Copy to array of explicit length + /// + /// ``` + /// # use cosmwasm_std::Binary; + /// let binary = Binary::from(&[0xfb, 0x1f, 0x37]); + /// let array: [u8; 3] = binary.to_array().unwrap(); + /// assert_eq!(array, [0xfb, 0x1f, 0x37]); + /// ``` + /// + /// Copy to integer + /// + /// ``` + /// # use cosmwasm_std::Binary; + /// let binary = Binary::from(&[0x8b, 0x67, 0x64, 0x84, 0xb5, 0xfb, 0x1f, 0x37]); + /// let num = u64::from_be_bytes(binary.to_array().unwrap()); + /// assert_eq!(num, 10045108015024774967); + /// ``` + pub fn to_array(&self) -> StdResult + where + A: ByteArray, + { + let out_size = std::mem::size_of::(); + if self.len() != out_size { + return Err(StdError::invalid_data_size(out_size, self.len())); + } + + // We cannot use Default::default() because it is only implemented for + // short arrays [T; 0] … [T; 32]. + let mut out: A = unsafe { mem::zeroed() }; + >::as_mut(&mut out).copy_from_slice(&self.0); + Ok(out) } } @@ -48,6 +88,19 @@ impl From<&[u8]> for Binary { } } +/// Just like Vec, Binary is a smart pointer to [u8]. +/// This implements `*binary` for us and allows us to +/// do `&*binary`, returning a `&[u8]` from a `&Binary`. +/// With [deref coercions](https://doc.rust-lang.org/1.22.1/book/first-edition/deref-coercions.html#deref-coercions), +/// this allows us to use `&binary` whenever a `&[u8]` is required. +impl Deref for Binary { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + // Macro needed until https://rust-lang.github.io/rfcs/2000-const-generics.html is stable. // See https://users.rust-lang.org/t/how-to-implement-trait-for-fixed-size-array-of-any-size/31494 macro_rules! implement_from_for_fixed_length_arrays { @@ -85,9 +138,41 @@ impl From> for Binary { } } -impl Into> for Binary { - fn into(self) -> Vec { - self.0 +impl From for Vec { + fn from(original: Binary) -> Vec { + original.0 + } +} + +/// Implement `encoding::Binary == std::vec::Vec` +impl PartialEq> for Binary { + fn eq(&self, rhs: &Vec) -> bool { + // Use Vec == Vec + self.0 == *rhs + } +} + +/// Implement `std::vec::Vec == encoding::Binary` +impl PartialEq for Vec { + fn eq(&self, rhs: &Binary) -> bool { + // Use Vec == Vec + *self == rhs.0 + } +} + +/// Implement `Binary == &[u8]` +impl PartialEq<&[u8]> for Binary { + fn eq(&self, rhs: &&[u8]) -> bool { + // Use &[u8] == &[u8] + self.as_slice() == *rhs + } +} + +/// Implement `&[u8] == Binary` +impl PartialEq for &[u8] { + fn eq(&self, rhs: &Binary) -> bool { + // Use &[u8] == &[u8] + *self == rhs.as_slice() } } @@ -131,11 +216,38 @@ impl<'de> de::Visitor<'de> for Base64Visitor { } } +/// A marker trait for `[u8; $N]`, which is needed as long as +/// https://rust-lang.github.io/rfcs/2000-const-generics.html is not stable. +/// +/// Implementing this for other types (like Vec) results in undefined behaviour. +pub unsafe trait ByteArray: Sized + AsMut<[u8]> {} + +// Macro needed until https://rust-lang.github.io/rfcs/2000-const-generics.html is stable. +// See https://users.rust-lang.org/t/how-to-implement-trait-for-fixed-size-array-of-any-size/31494 +macro_rules! implement_fixes_size_arrays { + ($($N:literal)+) => { + $( + unsafe impl ByteArray for [u8; $N] {} + )+ + } +} + +implement_fixes_size_arrays! { + 0 1 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 + 30 31 32 +} + #[cfg(test)] mod test { use super::*; use crate::errors::StdError; use crate::serde::{from_slice, to_vec}; + use std::collections::hash_map::DefaultHasher; + use std::collections::HashSet; + use std::hash::{Hash, Hasher}; + use std::iter::FromIterator; #[test] fn encode_decode() { @@ -152,7 +264,58 @@ mod test { let encoded = Binary(binary.clone()).to_base64(); assert_eq!(8, encoded.len()); let decoded = Binary::from_base64(&encoded).unwrap(); - assert_eq!(binary.as_slice(), decoded.as_slice()); + assert_eq!(binary.deref(), decoded.deref()); + } + + #[test] + fn to_array_works() { + // simple + let binary = Binary::from(&[1, 2, 3]); + let array: [u8; 3] = binary.to_array().unwrap(); + assert_eq!(array, [1, 2, 3]); + + // empty + let binary = Binary::from(&[]); + let array: [u8; 0] = binary.to_array().unwrap(); + assert_eq!(array, [] as [u8; 0]); + + // invalid size + let binary = Binary::from(&[1, 2, 3]); + let error = binary.to_array::<[u8; 8]>().unwrap_err(); + match error { + StdError::InvalidDataSize { + expected, actual, .. + } => { + assert_eq!(expected, 8); + assert_eq!(actual, 3); + } + err => panic!("Unexpected error: {:?}", err), + } + + // long array (32 bytes) + let binary = Binary::from_base64("t119JOQox4WUQEmO/nyqOZfO+wjJm91YG2sfn4ZglvA=").unwrap(); + let array: [u8; 32] = binary.to_array().unwrap(); + assert_eq!( + array, + [ + 0xb7, 0x5d, 0x7d, 0x24, 0xe4, 0x28, 0xc7, 0x85, 0x94, 0x40, 0x49, 0x8e, 0xfe, 0x7c, + 0xaa, 0x39, 0x97, 0xce, 0xfb, 0x08, 0xc9, 0x9b, 0xdd, 0x58, 0x1b, 0x6b, 0x1f, 0x9f, + 0x86, 0x60, 0x96, 0xf0, + ] + ); + + // very long array > 32 bytes (does not yet compile but we can make it happen with Rust 1.47+) + // let binary = + // Binary::from_base64("t119JOQox4WUQEmO/nyqOZfO+wjJm91YG2sfn4ZglvBzyMOwMWq+").unwrap(); + // let array: [u8; 39] = binary.to_array().unwrap(); + // assert_eq!( + // array, + // [ + // 0xb7, 0x5d, 0x7d, 0x24, 0xe4, 0x28, 0xc7, 0x85, 0x94, 0x40, 0x49, 0x8e, 0xfe, 0x7c, + // 0xaa, 0x39, 0x97, 0xce, 0xfb, 0x08, 0xc9, 0x9b, 0xdd, 0x58, 0x1b, 0x6b, 0x1f, 0x9f, + // 0x86, 0x60, 0x96, 0xf0, 0x73, 0xc8, 0xc3, 0xb0, 0x31, 0x6a, 0xbe, + // ] + // ); } #[test] @@ -278,11 +441,19 @@ mod test { #[test] fn into_vec_works() { + // Into> for Binary let original = Binary(vec![0u8, 187, 61, 11, 250, 0]); let original_ptr = original.0.as_ptr(); let vec: Vec = original.into(); assert_eq!(vec.as_slice(), [0u8, 187, 61, 11, 250, 0]); assert_eq!(vec.as_ptr(), original_ptr, "vector must not be copied"); + + // From for Vec + let original = Binary(vec![7u8, 35, 49, 101, 0, 255]); + let original_ptr = original.0.as_ptr(); + let vec = Vec::::from(original); + assert_eq!(vec.as_slice(), [7u8, 35, 49, 101, 0, 255]); + assert_eq!(vec.as_ptr(), original_ptr, "vector must not be copied"); } #[test] @@ -313,4 +484,76 @@ mod test { let res = from_slice::(&serialized); assert!(res.is_err()); } + + #[test] + fn binary_implements_deref() { + // Dereference to [u8] + let binary = Binary(vec![7u8, 35, 49, 101, 0, 255]); + assert_eq!(*binary, [7u8, 35, 49, 101, 0, 255]); + + // This checks deref coercions from &Binary to &[u8] works + let binary = Binary(vec![7u8, 35, 49, 101, 0, 255]); + assert_eq!(binary.len(), 6); + let binary_slice: &[u8] = &binary; + assert_eq!(binary_slice, &[7u8, 35, 49, 101, 0, 255]); + } + + #[test] + fn binary_implements_hash() { + let a1 = Binary::from([0, 187, 61, 11, 250, 0]); + let mut hasher = DefaultHasher::new(); + a1.hash(&mut hasher); + let a1_hash = hasher.finish(); + + let a2 = Binary::from([0, 187, 61, 11, 250, 0]); + let mut hasher = DefaultHasher::new(); + a2.hash(&mut hasher); + let a2_hash = hasher.finish(); + + let b = Binary::from([16, 21, 33, 0, 255, 9]); + let mut hasher = DefaultHasher::new(); + b.hash(&mut hasher); + let b_hash = hasher.finish(); + + assert_eq!(a1_hash, a2_hash); + assert_ne!(a1_hash, b_hash); + } + + /// This requires Hash and Eq to be implemented + #[test] + fn binary_can_be_used_in_hash_set() { + let a1 = Binary::from([0, 187, 61, 11, 250, 0]); + let a2 = Binary::from([0, 187, 61, 11, 250, 0]); + let b = Binary::from([16, 21, 33, 0, 255, 9]); + + let mut set = HashSet::new(); + set.insert(a1.clone()); + set.insert(a2.clone()); + set.insert(b.clone()); + assert_eq!(set.len(), 2); + + let set1 = HashSet::::from_iter(vec![b.clone(), a1.clone()]); + let set2 = HashSet::from_iter(vec![a1.clone(), a2.clone(), b.clone()]); + assert_eq!(set1, set2); + } + + #[test] + fn binary_implements_partial_eq_with_vector() { + let a = Binary(vec![5u8; 3]); + let b = vec![5u8; 3]; + let c = vec![9u8; 3]; + assert_eq!(a, b); + assert_eq!(b, a); + assert_ne!(a, c); + assert_ne!(c, a); + } + + #[test] + fn binary_implements_partial_eq_with_slice() { + let a = Binary(vec![0xAA, 0xBB]); + assert_eq!(a, b"\xAA\xBB" as &[u8]); + assert_eq!(b"\xAA\xBB" as &[u8], a); + assert_ne!(a, b"\x11\x22" as &[u8]); + assert_ne!(b"\x11\x22" as &[u8], a); + } } diff --git a/packages/std/src/coins.rs b/packages/std/src/coins.rs index 8a5c9df11..74148367e 100644 --- a/packages/std/src/coins.rs +++ b/packages/std/src/coins.rs @@ -10,21 +10,58 @@ pub struct Coin { } impl Coin { - pub fn new(amount: u128, denom: &str) -> Self { + pub fn new>(amount: u128, denom: S) -> Self { Coin { amount: Uint128(amount), - denom: denom.to_string(), + denom: denom.into(), } } } -// coins is a shortcut constructor for a set of one denomination of coins -pub fn coins(amount: u128, denom: &str) -> Vec { +/// A shortcut constructor for a set of one denomination of coins +/// +/// # Examples +/// +/// ``` +/// # use cosmwasm_std::{coins, BankMsg, CosmosMsg, HandleResponse}; +/// # use cosmwasm_std::testing::{mock_env, mock_info}; +/// # let env = mock_env(); +/// # let info = mock_info("sender", &[]); +/// let tip = coins(123, "ucosm"); +/// +/// let mut response: HandleResponse = Default::default(); +/// response.messages = vec![CosmosMsg::Bank(BankMsg::Send { +/// from_address: env.contract.address, +/// to_address: info.sender, +/// amount: tip, +/// })]; +/// ``` +pub fn coins>(amount: u128, denom: S) -> Vec { vec![coin(amount, denom)] } -// coin is a shorthand constructor for Coin -pub fn coin(amount: u128, denom: &str) -> Coin { +/// A shorthand constructor for Coin +/// +/// # Examples +/// +/// ``` +/// # use cosmwasm_std::{coin, BankMsg, CosmosMsg, HandleResponse}; +/// # use cosmwasm_std::testing::{mock_env, mock_info}; +/// # let env = mock_env(); +/// # let info = mock_info("sender", &[]); +/// let tip = vec![ +/// coin(123, "ucosm"), +/// coin(24, "ustake"), +/// ]; +/// +/// let mut response: HandleResponse = Default::default(); +/// response.messages = vec![CosmosMsg::Bank(BankMsg::Send { +/// from_address: env.contract.address, +/// to_address: info.sender, +/// amount: tip, +/// })]; +/// ``` +pub fn coin>(amount: u128, denom: S) -> Coin { Coin::new(amount, denom) } @@ -41,6 +78,66 @@ pub fn has_coins(coins: &[Coin], required: &Coin) -> bool { mod test { use super::*; + #[test] + fn coin_works() { + let a = coin(123, "ucosm"); + assert_eq!( + a, + Coin { + amount: Uint128(123), + denom: "ucosm".to_string() + } + ); + + let zero = coin(0, "ucosm"); + assert_eq!( + zero, + Coin { + amount: Uint128(0), + denom: "ucosm".to_string() + } + ); + + let string_denom = coin(42, String::from("ucosm")); + assert_eq!( + string_denom, + Coin { + amount: Uint128(42), + denom: "ucosm".to_string() + } + ); + } + + #[test] + fn coins_works() { + let a = coins(123, "ucosm"); + assert_eq!( + a, + vec![Coin { + amount: Uint128(123), + denom: "ucosm".to_string() + }] + ); + + let zero = coins(0, "ucosm"); + assert_eq!( + zero, + vec![Coin { + amount: Uint128(0), + denom: "ucosm".to_string() + }] + ); + + let string_denom = coins(42, String::from("ucosm")); + assert_eq!( + string_denom, + vec![Coin { + amount: Uint128(42), + denom: "ucosm".to_string() + }] + ); + } + #[test] fn has_coins_matches() { let wallet = vec![coin(12345, "ETH"), coin(555, "BTC")]; diff --git a/packages/std/src/deps.rs b/packages/std/src/deps.rs new file mode 100644 index 000000000..cbc2ce7ce --- /dev/null +++ b/packages/std/src/deps.rs @@ -0,0 +1,88 @@ +use crate::traits::{Api, Querier, Storage}; +use crate::QuerierWrapper; + +/// Holds all external dependencies of the contract. +/// Designed to allow easy dependency injection at runtime. +/// This cannot be copied or cloned since it would behave differently +/// for mock storages and a bridge storage in the VM. +pub struct OwnedDeps { + pub storage: S, + pub api: A, + pub querier: Q, +} + +pub struct DepsMut<'a> { + pub storage: &'a mut dyn Storage, + pub api: &'a dyn Api, + pub querier: QuerierWrapper<'a>, +} + +#[derive(Copy, Clone)] +pub struct Deps<'a> { + pub storage: &'a dyn Storage, + pub api: &'a dyn Api, + pub querier: QuerierWrapper<'a>, +} + +impl OwnedDeps { + pub fn as_ref(&'_ self) -> Deps<'_> { + Deps { + storage: &self.storage, + api: &self.api, + querier: QuerierWrapper::new(&self.querier), + } + } + + pub fn as_mut(&'_ mut self) -> DepsMut<'_> { + DepsMut { + storage: &mut self.storage, + api: &self.api, + querier: QuerierWrapper::new(&self.querier), + } + } +} + +impl<'a> DepsMut<'a> { + pub fn as_ref(&'_ self) -> Deps<'_> { + Deps { + storage: self.storage, + api: self.api, + querier: self.querier, + } + } + + pub fn branch(&'_ mut self) -> DepsMut<'_> { + DepsMut { + storage: self.storage, + api: self.api, + querier: self.querier, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::mock::mock_dependencies; + + // ensure we can call these many times, eg. as sub-calls + fn handle(mut deps: DepsMut) { + handle2(deps.branch()); + query(deps.as_ref()); + handle2(deps.branch()); + } + fn handle2(_deps: DepsMut) {} + + fn query(deps: Deps) { + query2(deps.clone()); + query2(deps.clone()); + } + fn query2(_deps: Deps) {} + + #[test] + fn ensure_easy_reuse() { + let mut deps = mock_dependencies(&[]); + handle(deps.as_mut()); + query(deps.as_ref()) + } +} diff --git a/packages/std/src/entry_points.rs b/packages/std/src/entry_points.rs index 18e2f9738..b8397111b 100644 --- a/packages/std/src/entry_points.rs +++ b/packages/std/src/entry_points.rs @@ -6,31 +6,34 @@ /// The second module should export three functions with the following signatures: /// ``` /// # use cosmwasm_std::{ -/// # Storage, Api, Querier, Extern, Env, StdResult, Binary, +/// # Storage, Api, Querier, DepsMut, Deps, Env, StdResult, Binary, MessageInfo, /// # InitResult, HandleResult, QueryResult, /// # }; /// # /// # type InitMsg = (); -/// pub fn init( -/// deps: &mut Extern, +/// pub fn init( +/// deps: DepsMut, /// env: Env, +/// info: MessageInfo, /// msg: InitMsg, /// ) -> InitResult { /// # Ok(Default::default()) /// } /// /// # type HandleMsg = (); -/// pub fn handle( -/// deps: &mut Extern, +/// pub fn handle( +/// deps: DepsMut, /// env: Env, +/// info: MessageInfo, /// msg: HandleMsg, /// ) -> HandleResult { /// # Ok(Default::default()) /// } /// /// # type QueryMsg = (); -/// pub fn query( -/// deps: &Extern, +/// pub fn query( +/// deps: Deps, +/// env: Env, /// msg: QueryMsg, /// ) -> QueryResult { /// # Ok(Binary(Vec::new())) @@ -49,12 +52,8 @@ macro_rules! create_entry_points { (@migration; $contract:ident, true) => { #[no_mangle] - extern "C" fn migrate(env_ptr: u32, msg_ptr: u32) -> u32 { - do_migrate( - &$contract::migrate::, - env_ptr, - msg_ptr, - ) + extern "C" fn migrate(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 { + do_migrate(&$contract::migrate, env_ptr, info_ptr, msg_ptr) } }; @@ -63,40 +62,26 @@ macro_rules! create_entry_points { (@inner; $contract:ident, migration = $migration:tt) => { mod wasm { use super::$contract; - use cosmwasm_std::{ - do_handle, do_init, do_migrate, do_query, ExternalApi, ExternalQuerier, - ExternalStorage, - }; + use cosmwasm_std::{do_handle, do_init, do_migrate, do_query}; #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &$contract::init::, - env_ptr, - msg_ptr, - ) + extern "C" fn init(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 { + do_init(&$contract::init, env_ptr, info_ptr, msg_ptr) } #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &$contract::handle::, - env_ptr, - msg_ptr, - ) + extern "C" fn handle(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 { + do_handle(&$contract::handle, env_ptr, info_ptr, msg_ptr) } #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &$contract::query::, - msg_ptr, - ) + extern "C" fn query(env_ptr: u32, msg_ptr: u32) -> u32 { + do_query(&$contract::query, env_ptr, msg_ptr) } $crate::create_entry_points!(@migration; $contract, $migration); - // Other C externs like cosmwasm_vm_version_3, allocate, deallocate are available + // Other C externs like cosmwasm_vm_version_4, allocate, deallocate are available // automatically because we `use cosmwasm_std`. } }; @@ -109,12 +94,13 @@ macro_rules! create_entry_points { /// This macro is very similar to the `create_entry_points` macro, except it also requires the `migrate` method: /// ``` /// # use cosmwasm_std::{ -/// # Storage, Api, Querier, Extern, Env, StdResult, Binary, MigrateResult, +/// # Storage, Api, Querier, DepsMut, Env, StdResult, Binary, MigrateResult, MessageInfo, /// # }; /// # type MigrateMsg = (); -/// pub fn migrate( -/// deps: &mut Extern, +/// pub fn migrate( +/// deps: DepsMut, /// _env: Env, +/// _info: MessageInfo, /// msg: MigrateMsg, /// ) -> MigrateResult { /// # Ok(Default::default()) diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs index 786bafb5a..dddea3793 100644 --- a/packages/std/src/errors/mod.rs +++ b/packages/std/src/errors/mod.rs @@ -2,4 +2,4 @@ mod std_error; mod system_error; pub use std_error::{StdError, StdResult}; -pub use system_error::{SystemError, SystemResult}; +pub use system_error::SystemError; diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs index 94946e92f..3719644d0 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -1,6 +1,6 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use snafu::Snafu; +#[cfg(feature = "backtraces")] +use std::backtrace::Backtrace; +use thiserror::Error; /// Structured error type for init, handle and query. /// @@ -16,203 +16,147 @@ use snafu::Snafu; /// /// Checklist for adding a new error: /// - Add enum case -/// - Add to PartialEq implementation -/// - Add serialize/deserialize test /// - Add creator function in std_error_helpers.rs -/// - Regenerate schemas -#[derive(Debug, Serialize, Deserialize, Snafu, JsonSchema)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] +#[derive(Error, Debug)] pub enum StdError { /// Whenever there is no specific error type available - #[snafu(display("Generic error: {}", msg))] + #[error("Generic error: {msg}")] GenericErr { msg: String, - #[serde(skip)] - backtrace: Option, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, }, - #[snafu(display("Invalid Base64 string: {}", msg))] + #[error("Invalid Base64 string: {msg}")] InvalidBase64 { msg: String, - #[serde(skip)] - backtrace: Option, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Invalid data size: expected={expected} actual={actual}")] + InvalidDataSize { + expected: u64, + actual: u64, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, }, /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8. - #[snafu(display("Cannot decode UTF8 bytes into string: {}", msg))] + #[error("Cannot decode UTF8 bytes into string: {msg}")] InvalidUtf8 { msg: String, - #[serde(skip)] - backtrace: Option, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, }, - #[snafu(display("{} not found", kind))] + #[error("{kind} not found")] NotFound { kind: String, - #[serde(skip)] - backtrace: Option, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, }, - #[snafu(display("Error parsing into type {}: {}", target, msg))] + #[error("Error parsing into type {target_type}: {msg}")] ParseErr { /// the target type that was attempted - target: String, + target_type: String, msg: String, - #[serde(skip)] - backtrace: Option, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, }, - #[snafu(display("Error serializing type {}: {}", source, msg))] + #[error("Error serializing type {source_type}: {msg}")] SerializeErr { /// the source type that was attempted - #[snafu(source(false))] - source: String, + source_type: String, msg: String, - #[serde(skip)] - backtrace: Option, - }, - #[snafu(display("Unauthorized"))] - Unauthorized { - #[serde(skip)] - backtrace: Option, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, }, - #[snafu(display("Cannot subtract {} from {}", subtrahend, minuend))] + #[error("Cannot subtract {subtrahend} from {minuend}")] Underflow { minuend: String, subtrahend: String, - #[serde(skip)] - backtrace: Option, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, }, } impl StdError { pub fn generic_err>(msg: S) -> Self { - GenericErr { msg: msg.into() }.build() + StdError::GenericErr { + msg: msg.into(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } } pub fn invalid_base64(msg: S) -> Self { - InvalidBase64 { + StdError::InvalidBase64 { msg: msg.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn invalid_data_size(expected: usize, actual: usize) -> Self { + StdError::InvalidDataSize { + // Cast is safe because usize is 32 or 64 bit large in all environments we support + expected: expected as u64, + actual: actual as u64, + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), } - .build() } pub fn invalid_utf8(msg: S) -> Self { - InvalidUtf8 { + StdError::InvalidUtf8 { msg: msg.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), } - .build() } pub fn not_found>(kind: S) -> Self { - NotFound { kind: kind.into() }.build() + StdError::NotFound { + kind: kind.into(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } } pub fn parse_err, M: ToString>(target: T, msg: M) -> Self { - ParseErr { - target: target.into(), + StdError::ParseErr { + target_type: target.into(), msg: msg.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), } - .build() } pub fn serialize_err, M: ToString>(source: S, msg: M) -> Self { - SerializeErr { - source: source.into(), + StdError::SerializeErr { + source_type: source.into(), msg: msg.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), } - .build() } pub fn underflow(minuend: U, subtrahend: U) -> Self { - Underflow { + StdError::Underflow { minuend: minuend.to_string(), subtrahend: subtrahend.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), } - .build() } +} - pub fn unauthorized() -> Self { - Unauthorized {}.build() +impl From for StdError { + fn from(source: std::str::Utf8Error) -> Self { + Self::invalid_utf8(source) } } -impl PartialEq for StdError { - /// Two errors are considered equal if and only if their payloads (i.e. all fields other than backtrace) are equal. - /// - /// The origin of the error (expressed by its backtrace) is ignored, which allows equality checks on errors and - /// results in tests. This is a property that might not always be desired depending on the use case and something - /// you should be aware of. - /// - /// Note: We destruct the unused backtrace as _ to avoid the use of `..` which silently ignores newly added fields. - #[allow(clippy::unneeded_field_pattern)] - fn eq(&self, other: &Self) -> bool { - match (self, other) { - ( - StdError::GenericErr { msg, backtrace: _ }, - StdError::GenericErr { - msg: msg2, - backtrace: _, - }, - ) => msg == msg2, - ( - StdError::InvalidBase64 { msg, backtrace: _ }, - StdError::InvalidBase64 { - msg: msg2, - backtrace: _, - }, - ) => msg == msg2, - ( - StdError::InvalidUtf8 { msg, backtrace: _ }, - StdError::InvalidUtf8 { - msg: msg2, - backtrace: _, - }, - ) => msg == msg2, - ( - StdError::NotFound { kind, backtrace: _ }, - StdError::NotFound { - kind: kind2, - backtrace: _, - }, - ) => kind == kind2, - ( - StdError::ParseErr { - target, - msg, - backtrace: _, - }, - StdError::ParseErr { - target: target2, - msg: msg2, - backtrace: _, - }, - ) => target == target2 && msg == msg2, - ( - StdError::SerializeErr { - source, - msg, - backtrace: _, - }, - StdError::SerializeErr { - source: source2, - msg: msg2, - backtrace: _, - }, - ) => source == source2 && msg == msg2, - (StdError::Unauthorized { backtrace: _ }, StdError::Unauthorized { backtrace: _ }) => { - true - } - ( - StdError::Underflow { - minuend, - subtrahend, - backtrace: _, - }, - StdError::Underflow { - minuend: minued2, - subtrahend: subtrahend2, - backtrace: _, - }, - ) => minuend == minued2 && subtrahend == subtrahend2, - _ => false, - } +impl From for StdError { + fn from(source: std::string::FromUtf8Error) -> Self { + Self::invalid_utf8(source) } } @@ -226,7 +170,7 @@ pub type StdResult = core::result::Result; #[cfg(test)] mod test { use super::*; - use crate::serde::{from_slice, to_vec}; + use std::str; // constructors @@ -276,6 +220,20 @@ mod test { } } + #[test] + fn invalid_data_size_works() { + let error = StdError::invalid_data_size(31, 14); + match error { + StdError::InvalidDataSize { + expected, actual, .. + } => { + assert_eq!(expected, 31); + assert_eq!(actual, 14); + } + _ => panic!("expect different error"), + } + } + #[test] fn invalid_utf8_works_for_strings() { let error = StdError::invalid_utf8("my text"); @@ -312,8 +270,10 @@ mod test { fn parse_err_works() { let error = StdError::parse_err("Book", "Missing field: title"); match error { - StdError::ParseErr { target, msg, .. } => { - assert_eq!(target, "Book"); + StdError::ParseErr { + target_type, msg, .. + } => { + assert_eq!(target_type, "Book"); assert_eq!(msg, "Missing field: title"); } _ => panic!("expect different error"), @@ -324,8 +284,10 @@ mod test { fn serialize_err_works() { let error = StdError::serialize_err("Book", "Content too long"); match error { - StdError::SerializeErr { source, msg, .. } => { - assert_eq!(source, "Book"); + StdError::SerializeErr { + source_type, msg, .. + } => { + assert_eq!(source_type, "Book"); assert_eq!(msg, "Content too long"); } _ => panic!("expect different error"), @@ -365,127 +327,45 @@ mod test { } #[test] - fn unauthorized_works() { - let error = StdError::unauthorized(); - match error { - StdError::Unauthorized { .. } => {} - _ => panic!("expect different error"), - } - } - - #[test] - fn can_serialize() { - let error = InvalidBase64 { - msg: "invalid length".to_string(), - } - .build(); + fn implements_debug() { + let error: StdError = StdError::underflow(3, 5); + let embedded = format!("Debug message: {:?}", error); assert_eq!( - to_vec(&error).unwrap(), - br#"{"invalid_base64":{"msg":"invalid length"}}"#.to_vec() + embedded, + r#"Debug message: Underflow { minuend: "3", subtrahend: "5" }"# ); } #[test] - fn can_deserialize() { - let error: StdError = - from_slice(br#"{"invalid_base64":{"msg":"invalid length"}}"#).unwrap(); - match error { - StdError::InvalidBase64 { msg, backtrace } => { - assert_eq!(msg, "invalid length"); - assert!(backtrace.is_none()); - } - _ => panic!("invalid type"), - }; + fn implements_display() { + let error: StdError = StdError::underflow(3, 5); + let embedded = format!("Display message: {}", error); + assert_eq!(embedded, "Display message: Cannot subtract 5 from 3"); } - /// The deseralizer in from_slice can perform zero-copy deserializations (https://serde.rs/lifetimes.html). - /// So it is possible to have `&'static str` fields as long as all source data is always static. - /// This is an unrealistic assumption for our use case. This test case ensures we can deseralize - /// errors from limited liefetime sources. #[test] - fn can_deserialize_from_non_static_source() { - let source = (br#"{"not_found":{"kind":"bugs"}}"#).to_vec(); - let error: StdError = from_slice(&source).unwrap(); + fn from_std_str_utf8error_works() { + let error: StdError = str::from_utf8(b"Hello \xF0\x90\x80World") + .unwrap_err() + .into(); match error { - StdError::NotFound { kind, backtrace } => { - assert_eq!(kind, "bugs"); - assert!(backtrace.is_none()); - } - _ => panic!("invalid type"), - }; - } - - #[test] - fn eq_works() { - let error1 = StdError::InvalidBase64 { - msg: "invalid length".to_string(), - backtrace: None, - }; - let error2 = StdError::InvalidBase64 { - msg: "invalid length".to_string(), - backtrace: None, - }; - assert_eq!(error1, error2); - } - - #[test] - fn ne_works() { - let error1 = StdError::InvalidBase64 { - msg: "invalid length".to_string(), - backtrace: None, - }; - let error2 = StdError::InvalidBase64 { - msg: "other bla".to_string(), - backtrace: None, - }; - assert_ne!(error1, error2); - } - - fn assert_conversion(original: StdError) { - let seralized = to_vec(&original).unwrap(); - let restored: StdError = from_slice(&seralized).unwrap(); - assert_eq!(restored, original); - } - - #[test] - fn generic_err_conversion() { - assert_conversion(GenericErr { msg: "something" }.build()); - } - - #[test] - fn invalid_base64_conversion() { - assert_conversion( - InvalidBase64 { - msg: "invalid length".to_string(), + StdError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") } - .build(), - ); - } - - #[test] - fn unauthorized_conversion() { - assert_conversion(Unauthorized {}.build()); - } - - #[test] - fn not_found_conversion() { - assert_conversion(NotFound { kind: "State" }.build()); - } - - #[test] - fn parse_err_conversion() { - let err = from_slice::(b"123").unwrap_err(); - assert_conversion(err); + err => panic!("Unexpected error: {:?}", err), + } } #[test] - fn serialize_err_conversion() { - assert_conversion( - SerializeErr { - source: "Person", - msg: "buffer is full", + fn from_std_string_fromutf8error_works() { + let error: StdError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec()) + .unwrap_err() + .into(); + match error { + StdError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") } - .build(), - ); + err => panic!("Unexpected error: {:?}", err), + } } } diff --git a/packages/std/src/errors/system_error.rs b/packages/std/src/errors/system_error.rs index 6e317bb58..7d4a095f2 100644 --- a/packages/std/src/errors/system_error.rs +++ b/packages/std/src/errors/system_error.rs @@ -32,13 +32,13 @@ impl std::fmt::Display for SystemError { f, "Cannot parse request: {} in: {}", error, - String::from_utf8_lossy(request.as_slice()) + String::from_utf8_lossy(&request) ), SystemError::InvalidResponse { error, response } => write!( f, "Cannot parse response: {} in: {}", error, - String::from_utf8_lossy(response.as_slice()) + String::from_utf8_lossy(&response) ), SystemError::NoSuchContract { addr } => write!(f, "No such contract: {}", addr), SystemError::Unknown {} => write!(f, "Unknown system error"), @@ -48,5 +48,3 @@ impl std::fmt::Display for SystemError { } } } - -pub type SystemResult = Result; diff --git a/packages/std/src/exports.rs b/packages/std/src/exports.rs index 90f124c4a..c28001213 100644 --- a/packages/std/src/exports.rs +++ b/packages/std/src/exports.rs @@ -1,6 +1,6 @@ //! exports exposes the public wasm API //! -//! cosmwasm_vm_version_3, allocate and deallocate turn into Wasm exports +//! cosmwasm_vm_version_4, allocate and deallocate turn into Wasm exports //! as soon as cosmwasm_std is `use`d in the contract, even privately. //! //! do_init and do_wrapper should be wrapped with a extern "C" entry point @@ -11,12 +11,15 @@ use std::vec::Vec; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; -use crate::errors::StdResult; +use crate::deps::OwnedDeps; use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage}; use crate::memory::{alloc, consume_region, release_buffer, Region}; +use crate::results::{ + ContractResult, HandleResponse, InitResponse, MigrateResponse, QueryResponse, +}; use crate::serde::{from_slice, to_vec}; -use crate::traits::Extern; -use crate::{Env, HandleResult, InitResult, MigrateResult, QueryResponse, QueryResult}; +use crate::types::Env; +use crate::{Deps, DepsMut, MessageInfo}; #[cfg(feature = "staking")] #[no_mangle] @@ -26,7 +29,7 @@ extern "C" fn requires_staking() -> () {} /// They can be checked by cosmwasm_vm. /// Update this whenever the Wasm VM interface breaks. #[no_mangle] -extern "C" fn cosmwasm_vm_version_3() -> () {} +extern "C" fn cosmwasm_vm_version_4() -> () {} /// allocate reserves the given number of bytes in wasm memory and returns a pointer /// to a Region defining this data. This space is managed by the calling process @@ -44,159 +47,208 @@ extern "C" fn deallocate(pointer: u32) { let _ = unsafe { consume_region(pointer as *mut Region) }; } +// TODO: replace with https://doc.rust-lang.org/std/ops/trait.Try.html once stabilized +macro_rules! r#try_into_contract_result { + ($expr:expr) => { + match $expr { + Ok(val) => val, + Err(err) => { + return ContractResult::Err(err.to_string()); + } + } + }; + ($expr:expr,) => { + $crate::try_into_contract_result!($expr) + }; +} + /// do_init should be wrapped in an external "C" export, containing a contract-specific function as arg -pub fn do_init( - init_fn: &dyn Fn( - &mut Extern, - Env, - T, - ) -> InitResult, +/// +/// - `M`: message type for request +/// - `C`: custom response message type (see CosmosMsg) +/// - `E`: error type for responses +pub fn do_init( + init_fn: &dyn Fn(DepsMut, Env, MessageInfo, M) -> Result, E>, env_ptr: u32, + info_ptr: u32, msg_ptr: u32, ) -> u32 where - T: DeserializeOwned + JsonSchema, - U: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + M: DeserializeOwned + JsonSchema, + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, { - let res: InitResult = _do_init(init_fn, env_ptr as *mut Region, msg_ptr as *mut Region); + let res = _do_init( + init_fn, + env_ptr as *mut Region, + info_ptr as *mut Region, + msg_ptr as *mut Region, + ); let v = to_vec(&res).unwrap(); release_buffer(v) as u32 } /// do_handle should be wrapped in an external "C" export, containing a contract-specific function as arg -pub fn do_handle( - handle_fn: &dyn Fn( - &mut Extern, - Env, - T, - ) -> HandleResult, +/// +/// - `M`: message type for request +/// - `C`: custom response message type (see CosmosMsg) +/// - `E`: error type for responses +pub fn do_handle( + handle_fn: &dyn Fn(DepsMut, Env, MessageInfo, M) -> Result, E>, env_ptr: u32, + info_ptr: u32, msg_ptr: u32, ) -> u32 where - T: DeserializeOwned + JsonSchema, - U: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + M: DeserializeOwned + JsonSchema, + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, { - let res: HandleResult = - _do_handle(handle_fn, env_ptr as *mut Region, msg_ptr as *mut Region); + let res = _do_handle( + handle_fn, + env_ptr as *mut Region, + info_ptr as *mut Region, + msg_ptr as *mut Region, + ); let v = to_vec(&res).unwrap(); release_buffer(v) as u32 } -/// do_query should be wrapped in an external "C" export, containing a contract-specific function as arg -pub fn do_query( - query_fn: &dyn Fn( - &Extern, - T, - ) -> StdResult, +/// do_migrate should be wrapped in an external "C" export, containing a contract-specific function as arg +/// +/// - `M`: message type for request +/// - `C`: custom response message type (see CosmosMsg) +/// - `E`: error type for responses +pub fn do_migrate( + migrate_fn: &dyn Fn(DepsMut, Env, MessageInfo, M) -> Result, E>, + env_ptr: u32, + info_ptr: u32, msg_ptr: u32, -) -> u32 { - let res: QueryResult = _do_query(query_fn, msg_ptr as *mut Region); +) -> u32 +where + M: DeserializeOwned + JsonSchema, + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, +{ + let res = _do_migrate( + migrate_fn, + env_ptr as *mut Region, + info_ptr as *mut Region, + msg_ptr as *mut Region, + ); let v = to_vec(&res).unwrap(); release_buffer(v) as u32 } -/// do_migrate should be wrapped in an external "C" export, containing a contract-specific function as arg -pub fn do_migrate( - migrate_fn: &dyn Fn( - &mut Extern, - Env, - T, - ) -> MigrateResult, +/// do_query should be wrapped in an external "C" export, containing a contract-specific function as arg +/// +/// - `M`: message type for request +/// - `E`: error type for responses +pub fn do_query( + query_fn: &dyn Fn(Deps, Env, M) -> Result, env_ptr: u32, msg_ptr: u32, ) -> u32 where - T: DeserializeOwned + JsonSchema, - U: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + M: DeserializeOwned + JsonSchema, + E: ToString, { - let res: MigrateResult = - _do_migrate(migrate_fn, env_ptr as *mut Region, msg_ptr as *mut Region); + let res = _do_query(query_fn, env_ptr as *mut Region, msg_ptr as *mut Region); let v = to_vec(&res).unwrap(); release_buffer(v) as u32 } -fn _do_init( - init_fn: &dyn Fn( - &mut Extern, - Env, - T, - ) -> InitResult, +fn _do_init( + init_fn: &dyn Fn(DepsMut, Env, MessageInfo, M) -> Result, E>, env_ptr: *mut Region, + info_ptr: *mut Region, msg_ptr: *mut Region, -) -> InitResult +) -> ContractResult> where - T: DeserializeOwned + JsonSchema, - U: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + M: DeserializeOwned + JsonSchema, + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, { let env: Vec = unsafe { consume_region(env_ptr) }; + let info: Vec = unsafe { consume_region(info_ptr) }; let msg: Vec = unsafe { consume_region(msg_ptr) }; - let env: Env = from_slice(&env)?; - let msg: T = from_slice(&msg)?; + + let env: Env = try_into_contract_result!(from_slice(&env)); + let info: MessageInfo = try_into_contract_result!(from_slice(&info)); + let msg: M = try_into_contract_result!(from_slice(&msg)); + let mut deps = make_dependencies(); - init_fn(&mut deps, env, msg) + init_fn(deps.as_mut(), env, info, msg).into() } -fn _do_handle( - handle_fn: &dyn Fn( - &mut Extern, - Env, - T, - ) -> HandleResult, +fn _do_handle( + handle_fn: &dyn Fn(DepsMut, Env, MessageInfo, M) -> Result, E>, env_ptr: *mut Region, + info_ptr: *mut Region, msg_ptr: *mut Region, -) -> HandleResult +) -> ContractResult> where - T: DeserializeOwned + JsonSchema, - U: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + M: DeserializeOwned + JsonSchema, + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, { let env: Vec = unsafe { consume_region(env_ptr) }; + let info: Vec = unsafe { consume_region(info_ptr) }; let msg: Vec = unsafe { consume_region(msg_ptr) }; - let env: Env = from_slice(&env)?; - let msg: T = from_slice(&msg)?; + let env: Env = try_into_contract_result!(from_slice(&env)); + let info: MessageInfo = try_into_contract_result!(from_slice(&info)); + let msg: M = try_into_contract_result!(from_slice(&msg)); + let mut deps = make_dependencies(); - handle_fn(&mut deps, env, msg) + handle_fn(deps.as_mut(), env, info, msg).into() } -fn _do_query( - query_fn: &dyn Fn( - &Extern, - T, - ) -> StdResult, +fn _do_migrate( + migrate_fn: &dyn Fn(DepsMut, Env, MessageInfo, M) -> Result, E>, + env_ptr: *mut Region, + info_ptr: *mut Region, msg_ptr: *mut Region, -) -> StdResult { +) -> ContractResult> +where + M: DeserializeOwned + JsonSchema, + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, +{ + let env: Vec = unsafe { consume_region(env_ptr) }; + let info: Vec = unsafe { consume_region(info_ptr) }; let msg: Vec = unsafe { consume_region(msg_ptr) }; - let msg: T = from_slice(&msg)?; - let deps = make_dependencies(); - query_fn(&deps, msg) + let env: Env = try_into_contract_result!(from_slice(&env)); + let info: MessageInfo = try_into_contract_result!(from_slice(&info)); + let msg: M = try_into_contract_result!(from_slice(&msg)); + + let mut deps = make_dependencies(); + migrate_fn(deps.as_mut(), env, info, msg).into() } -fn _do_migrate( - migrate_fn: &dyn Fn( - &mut Extern, - Env, - T, - ) -> MigrateResult, +fn _do_query( + query_fn: &dyn Fn(Deps, Env, M) -> Result, env_ptr: *mut Region, msg_ptr: *mut Region, -) -> MigrateResult +) -> ContractResult where - T: DeserializeOwned + JsonSchema, - U: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + M: DeserializeOwned + JsonSchema, + E: ToString, { let env: Vec = unsafe { consume_region(env_ptr) }; let msg: Vec = unsafe { consume_region(msg_ptr) }; - let env: Env = from_slice(&env)?; - let msg: T = from_slice(&msg)?; - let mut deps = make_dependencies(); - migrate_fn(&mut deps, env, msg) + + let env: Env = try_into_contract_result!(from_slice(&env)); + let msg: M = try_into_contract_result!(from_slice(&msg)); + + let deps = make_dependencies(); + query_fn(deps.as_ref(), env, msg).into() } /// Makes all bridges to external dependencies (i.e. Wasm imports) that are injected by the VM -fn make_dependencies() -> Extern { - Extern { +fn make_dependencies() -> OwnedDeps { + OwnedDeps { storage: ExternalStorage::new(), api: ExternalApi::new(), querier: ExternalQuerier::new(), diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index a8d6c3e14..8222058ad 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -1,13 +1,17 @@ use std::vec::Vec; use crate::addresses::{CanonicalAddr, HumanAddr}; -use crate::encoding::Binary; -use crate::errors::{StdError, StdResult}; -#[cfg(feature = "iterator")] -use crate::iterator::{Order, KV}; +use crate::binary::Binary; +use crate::errors::{StdError, StdResult, SystemError}; use crate::memory::{alloc, build_region, consume_region, Region}; +use crate::results::SystemResult; use crate::serde::from_slice; -use crate::traits::{Api, Querier, QuerierResult, ReadonlyStorage, Storage}; +use crate::traits::{Api, Querier, QuerierResult, Storage}; +#[cfg(feature = "iterator")] +use crate::{ + iterator::{Order, KV}, + memory::get_optional_region_address, +}; /// An upper bound for typical canonical address lengths (e.g. 20 in Cosmos SDK/Ethereum or 32 in Nano/Substrate) const CANONICAL_ADDRESS_BUFFER_LENGTH: usize = 32; @@ -28,8 +32,9 @@ extern "C" { #[cfg(feature = "iterator")] fn db_next(iterator_id: u32) -> u32; - fn canonicalize_address(source: u32, destination: u32) -> u32; - fn humanize_address(source: u32, destination: u32) -> u32; + fn canonicalize_address(source_ptr: u32, destination_ptr: u32) -> u32; + fn humanize_address(source_ptr: u32, destination_ptr: u32) -> u32; + fn debug(source_ptr: u32); /// Executes a query on the chain (import). Not to be confused with the /// query export, which queries the state of the contract. @@ -46,7 +51,7 @@ impl ExternalStorage { } } -impl ReadonlyStorage for ExternalStorage { +impl Storage for ExternalStorage { fn get(&self, key: &[u8]) -> Option> { let key = build_region(key); let key_ptr = &*key as *const Region as u32; @@ -62,35 +67,11 @@ impl ReadonlyStorage for ExternalStorage { Some(data) } - #[cfg(feature = "iterator")] - fn range( - &self, - start: Option<&[u8]>, - end: Option<&[u8]>, - order: Order, - ) -> Box> { - // start and end (Regions) must remain in scope as long as the start_ptr / end_ptr do - // thus they are not inside a block - let start = start.map(|s| build_region(s)); - let start_ptr = match start { - Some(reg) => &*reg as *const Region as u32, - None => 0, - }; - let end = end.map(|e| build_region(e)); - let end_ptr = match end { - Some(reg) => &*reg as *const Region as u32, - None => 0, - }; - let order = order as i32; - - let iterator_id = unsafe { db_scan(start_ptr, end_ptr, order) }; - let iter = ExternalIterator { iterator_id }; - Box::new(iter) - } -} - -impl Storage for ExternalStorage { fn set(&mut self, key: &[u8], value: &[u8]) { + if value.is_empty() { + panic!("TL;DR: Value must not be empty in Storage::set but in most cases you can use Storage::remove instead. Long story: Getting empty values from storage is not well supported at the moment. Some of our internal interfaces cannot differentiate between a non-existent key and an empty value. Right now, you cannot rely on the behaviour of empty values. To protect you from trouble later on, we stop here. Sorry for the inconvenience! We highly welcome you to contribute to CosmWasm, making this more solid one way or the other."); + } + // keep the boxes in scope, so we free it at the end (don't cast to pointers same line as build_region) let key = build_region(key); let key_ptr = &*key as *const Region as u32; @@ -105,6 +86,24 @@ impl Storage for ExternalStorage { let key_ptr = &*key as *const Region as u32; unsafe { db_remove(key_ptr) }; } + + #[cfg(feature = "iterator")] + fn range( + &self, + start: Option<&[u8]>, + end: Option<&[u8]>, + order: Order, + ) -> Box> { + // There is lots of gotchas on turning options into regions for FFI, thus this design + // See: https://github.com/CosmWasm/cosmwasm/pull/509 + let start_region = start.map(build_region); + let end_region = end.map(build_region); + let start_region_addr = get_optional_region_address(&start_region.as_ref()); + let end_region_addr = get_optional_region_address(&end_region.as_ref()); + let iterator_id = unsafe { db_scan(start_region_addr, end_region_addr, order as i32) }; + let iter = ExternalIterator { iterator_id }; + Box::new(iter) + } } #[cfg(feature = "iterator")] @@ -171,7 +170,7 @@ impl Api for ExternalApi { } fn human_address(&self, canonical: &CanonicalAddr) -> StdResult { - let send = build_region(canonical.as_slice()); + let send = build_region(&canonical); let send_ptr = &*send as *const Region as u32; let human = alloc(HUMAN_ADDRESS_BUFFER_LENGTH); @@ -187,6 +186,13 @@ impl Api for ExternalApi { let address = unsafe { consume_string_region_written_by_vm(human) }; Ok(address.into()) } + + fn debug(&self, message: &str) { + // keep the boxes in scope, so we free it at the end (don't cast to pointers same line as build_region) + let region = build_region(message.as_bytes()); + let region_ptr = region.as_ref() as *const Region as u32; + unsafe { debug(region_ptr) }; + } } /// Takes a pointer to a Region and reads the data into a String. @@ -212,8 +218,13 @@ impl Querier for ExternalQuerier { let request_ptr = &*req as *const Region as u32; let response_ptr = unsafe { query_chain(request_ptr) }; - let response = unsafe { consume_region(response_ptr as *mut Region) }; - from_slice(&response).unwrap_or_else(|err| Ok(Err(err))) + + from_slice(&response).unwrap_or_else(|parsing_err| { + SystemResult::Err(SystemError::InvalidResponse { + error: parsing_err.to_string(), + response: response.into(), + }) + }) } } diff --git a/packages/std/src/init_handle.rs b/packages/std/src/init_handle.rs deleted file mode 100644 index c21df3790..000000000 --- a/packages/std/src/init_handle.rs +++ /dev/null @@ -1,416 +0,0 @@ -//! Types and helpers for init and handle - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use std::fmt; - -use crate::addresses::HumanAddr; -use crate::coins::Coin; -use crate::encoding::Binary; -use crate::errors::{StdError, StdResult}; -use crate::types::Empty; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -// See https://github.com/serde-rs/serde/issues/1296 why we cannot add De-Serialize trait bounds to T -pub enum CosmosMsg -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - Bank(BankMsg), - // by default we use RawMsg, but a contract can override that - // to call into more app-specific code (whatever they define) - Custom(T), - Staking(StakingMsg), - Wasm(WasmMsg), -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum BankMsg { - // this moves tokens in the underlying sdk - Send { - from_address: HumanAddr, - to_address: HumanAddr, - amount: Vec, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum StakingMsg { - Delegate { - // delegator is automatically set to address of the calling contract - validator: HumanAddr, - amount: Coin, - }, - Undelegate { - // delegator is automatically set to address of the calling contract - validator: HumanAddr, - amount: Coin, - }, - Withdraw { - // delegator is automatically set to address of the calling contract - validator: HumanAddr, - /// this is the "withdraw address", the one that should receive the rewards - /// if None, then use delegator address - recipient: Option, - }, - Redelegate { - // delegator is automatically set to address of the calling contract - src_validator: HumanAddr, - dst_validator: HumanAddr, - amount: Coin, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum WasmMsg { - /// this dispatches a call to another contract at a known address (with known ABI) - Execute { - contract_addr: HumanAddr, - /// msg is the json-encoded HandleMsg struct (as raw Binary) - msg: Binary, - send: Vec, - }, - /// this instantiates a new contracts from previously uploaded wasm code - Instantiate { - code_id: u64, - /// msg is the json-encoded InitMsg struct (as raw Binary) - msg: Binary, - send: Vec, - /// optional human-readbale label for the contract - label: Option, - }, -} - -impl From for CosmosMsg { - fn from(msg: BankMsg) -> Self { - CosmosMsg::Bank(msg) - } -} - -#[cfg(feature = "staking")] -impl From for CosmosMsg { - fn from(msg: StakingMsg) -> Self { - CosmosMsg::Staking(msg) - } -} - -impl From for CosmosMsg { - fn from(msg: WasmMsg) -> Self { - CosmosMsg::Wasm(msg) - } -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] -pub struct LogAttribute { - pub key: String, - pub value: String, -} - -/// A shorthand to produce a log attribute -pub fn log(key: K, value: V) -> LogAttribute { - LogAttribute { - key: key.to_string(), - value: value.to_string(), - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - pub messages: Vec>, - pub log: Vec, -} - -pub type InitResult = StdResult>; - -impl Default for InitResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - fn default() -> Self { - InitResponse { - messages: vec![], - log: vec![], - } - } -} - -impl TryFrom> for InitResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - type Error = StdError; - - fn try_from(ctx: Context) -> Result { - if ctx.data.is_some() { - Err(StdError::generic_err( - "cannot convert Context with data to InitResponse", - )) - } else { - Ok(InitResponse { - messages: ctx.messages, - log: ctx.log, - }) - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct HandleResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - pub messages: Vec>, - pub log: Vec, - pub data: Option, -} - -pub type HandleResult = StdResult>; - -impl Default for HandleResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - fn default() -> Self { - HandleResponse { - messages: vec![], - log: vec![], - data: None, - } - } -} - -impl From> for HandleResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - fn from(ctx: Context) -> Self { - HandleResponse { - messages: ctx.messages, - log: ctx.log, - data: ctx.data, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - pub messages: Vec>, - pub log: Vec, - pub data: Option, -} - -pub type MigrateResult = StdResult>; - -impl Default for MigrateResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - fn default() -> Self { - MigrateResponse { - messages: vec![], - log: vec![], - data: None, - } - } -} - -impl From> for MigrateResponse -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - fn from(ctx: Context) -> Self { - MigrateResponse { - messages: ctx.messages, - log: ctx.log, - data: ctx.data, - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Context -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - messages: Vec>, - log: Vec, - data: Option, -} - -impl Default for Context -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - fn default() -> Self { - Context { - messages: vec![], - log: vec![], - data: None, - } - } -} - -impl Context -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - pub fn new() -> Self { - Context::default() - } - - pub fn add_log(&mut self, key: K, value: V) { - self.log.push(log(key, value)); - } - - pub fn add_message>>(&mut self, msg: U) { - self.messages.push(msg.into()); - } - - pub fn set_data>(&mut self, data: U) { - self.data = Some(data.into()); - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::errors::StdError; - use crate::{coins, from_slice, to_vec, Uint128}; - use std::convert::TryInto; - - #[test] - fn log_works_for_different_types() { - let expeceted = LogAttribute { - key: "foo".to_string(), - value: "42".to_string(), - }; - - assert_eq!(log("foo", "42"), expeceted); - assert_eq!(log("foo".to_string(), "42"), expeceted); - assert_eq!(log("foo", "42".to_string()), expeceted); - assert_eq!(log("foo", HumanAddr::from("42")), expeceted); - assert_eq!(log("foo", Uint128(42)), expeceted); - assert_eq!(log("foo", 42), expeceted); - } - - #[test] - fn can_deser_error_result() { - let fail = InitResult::Err(StdError::Unauthorized { backtrace: None }); - let bin = to_vec(&fail).expect("encode contract result"); - println!("error: {}", std::str::from_utf8(&bin).unwrap()); - let back: InitResult = from_slice(&bin).expect("decode contract result"); - assert_eq!(fail, back); - } - - #[test] - fn can_deser_ok_result() { - let send = InitResult::Ok(InitResponse { - messages: vec![BankMsg::Send { - from_address: HumanAddr("me".to_string()), - to_address: HumanAddr("you".to_string()), - amount: coins(1015, "earth"), - } - .into()], - log: vec![LogAttribute { - key: "action".to_string(), - value: "release".to_string(), - }], - }); - let bin = to_vec(&send).expect("encode contract result"); - println!("ok: {}", std::str::from_utf8(&bin).unwrap()); - let back: InitResult = from_slice(&bin).expect("decode contract result"); - assert_eq!(send, back); - } - - #[test] - fn msg_from_works() { - let from_address = HumanAddr("me".to_string()); - let to_address = HumanAddr("you".to_string()); - let amount = coins(1015, "earth"); - let bank = BankMsg::Send { - from_address, - to_address, - amount, - }; - let msg: CosmosMsg = bank.clone().into(); - match msg { - CosmosMsg::Bank(msg) => assert_eq!(bank, msg), - _ => panic!("must encode in Bank variant"), - } - } - - #[test] - fn empty_context() { - let ctx = Context::new(); - - let init: InitResponse = ctx.clone().try_into().unwrap(); - assert_eq!(init, InitResponse::default()); - - let init: HandleResponse = ctx.clone().try_into().unwrap(); - assert_eq!(init, HandleResponse::default()); - - let init: MigrateResponse = ctx.clone().try_into().unwrap(); - assert_eq!(init, MigrateResponse::default()); - } - - #[test] - fn full_context() { - let mut ctx = Context::new(); - - // build it up with the builder commands - ctx.add_log("sender", &HumanAddr::from("john")); - ctx.add_log("action", "test"); - ctx.add_message(BankMsg::Send { - from_address: HumanAddr::from("goo"), - to_address: HumanAddr::from("foo"), - amount: coins(128, "uint"), - }); - - // and this is what is should return - let expected_log = vec![log("sender", "john"), log("action", "test")]; - let expected_msgs = vec![CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from("goo"), - to_address: HumanAddr::from("foo"), - amount: coins(128, "uint"), - })]; - let expected_data = Some(Binary::from(b"banana")); - - // try InitResponse before setting data - let init: InitResponse = ctx.clone().try_into().unwrap(); - assert_eq!(&init.messages, &expected_msgs); - assert_eq!(&init.log, &expected_log); - - ctx.set_data(b"banana"); - // should fail with data set - let init_err: StdResult = ctx.clone().try_into(); - match init_err.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "cannot convert Context with data to InitResponse") - } - e => panic!("Unexpected error: {}", e), - } - - // try Handle with everything set - let handle: HandleResponse = ctx.clone().try_into().unwrap(); - assert_eq!(&handle.messages, &expected_msgs); - assert_eq!(&handle.log, &expected_log); - assert_eq!(&handle.data, &expected_data); - - // try Migrate with everything set - let migrate: MigrateResponse = ctx.clone().try_into().unwrap(); - assert_eq!(&migrate.messages, &expected_msgs); - assert_eq!(&migrate.log, &expected_log); - assert_eq!(&migrate.data, &expected_data); - } -} diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 0011a5b7a..ba6132579 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -1,39 +1,44 @@ +#![cfg_attr(feature = "backtraces", feature(backtrace))] + // Exposed on all platforms mod addresses; +mod binary; mod coins; -mod encoding; +mod deps; mod entry_points; mod errors; -mod init_handle; #[cfg(feature = "iterator")] mod iterator; mod math; mod query; +mod results; mod serde; mod storage; mod traits; mod types; pub use crate::addresses::{CanonicalAddr, HumanAddr}; +pub use crate::binary::{Binary, ByteArray}; pub use crate::coins::{coin, coins, has_coins, Coin}; -pub use crate::encoding::Binary; -pub use crate::errors::{StdError, StdResult, SystemError, SystemResult}; -pub use crate::init_handle::{ - log, BankMsg, Context, CosmosMsg, HandleResponse, HandleResult, InitResponse, InitResult, - LogAttribute, MigrateResponse, MigrateResult, StakingMsg, WasmMsg, -}; +pub use crate::deps::{Deps, DepsMut, OwnedDeps}; +pub use crate::errors::{StdError, StdResult, SystemError}; #[cfg(feature = "iterator")] pub use crate::iterator::{Order, KV}; pub use crate::math::{Decimal, Uint128}; pub use crate::query::{ AllBalanceResponse, AllDelegationsResponse, BalanceResponse, BankQuery, BondedDenomResponse, - Delegation, FullDelegation, QueryRequest, QueryResponse, QueryResult, StakingQuery, Validator, + CustomQuery, Delegation, FullDelegation, QueryRequest, StakingQuery, Validator, ValidatorsResponse, WasmQuery, }; +pub use crate::results::{ + attr, 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; -pub use crate::traits::{Api, Extern, Querier, QuerierResult, ReadonlyStorage, Storage}; +pub use crate::traits::{Api, Querier, QuerierResult, QuerierWrapper, Storage}; pub use crate::types::{BlockInfo, ContractInfo, Empty, Env, MessageInfo}; // Exposed in wasm build only @@ -58,8 +63,8 @@ mod mock; #[cfg(not(target_arch = "wasm32"))] pub mod testing { pub use crate::mock::{ - mock_dependencies, mock_dependencies_with_balances, mock_env, BankQuerier, MockApi, - MockQuerier, MockQuerierCustomHandlerResult, MockStorage, StakingQuerier, - MOCK_CONTRACT_ADDR, + digit_sum, mock_dependencies, mock_dependencies_with_balances, mock_env, mock_info, + riffle_shuffle, BankQuerier, MockApi, MockQuerier, MockQuerierCustomHandlerResult, + MockStorage, StakingQuerier, MOCK_CONTRACT_ADDR, }; } diff --git a/packages/std/src/math.rs b/packages/std/src/math.rs index d62e86424..96c402509 100644 --- a/packages/std/src/math.rs +++ b/packages/std/src/math.rs @@ -2,6 +2,7 @@ use schemars::JsonSchema; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use std::convert::{TryFrom, TryInto}; use std::fmt::{self, Write}; +use std::iter::Sum; use std::ops; use std::str::FromStr; @@ -229,30 +230,50 @@ impl fmt::Display for Uint128 { } } -impl ops::Add for Uint128 { +impl ops::Add for Uint128 { type Output = Self; - fn add(self, other: Self) -> Self { - Uint128(self.u128() + other.u128()) + fn add(self, rhs: Self) -> Self { + Uint128(self.u128() + rhs.u128()) + } +} + +impl<'a> ops::Add<&'a Uint128> for Uint128 { + type Output = Self; + + fn add(self, rhs: &'a Uint128) -> Self { + Uint128(self.u128() + rhs.u128()) + } +} + +impl ops::AddAssign for Uint128 { + fn add_assign(&mut self, rhs: Uint128) { + self.0 += rhs.u128(); } } -impl ops::AddAssign for Uint128 { - fn add_assign(&mut self, other: Self) { - self.0 += other.u128(); +impl<'a> ops::AddAssign<&'a Uint128> for Uint128 { + fn add_assign(&mut self, rhs: &'a Uint128) { + self.0 += rhs.u128(); } } -impl ops::Sub for Uint128 { +impl ops::Sub for Uint128 { type Output = StdResult; - fn sub(self, other: Self) -> StdResult { - let (min, sub) = (self.u128(), other.u128()); - if sub > min { - Err(StdError::underflow(min, sub)) - } else { - Ok(Uint128(min - sub)) - } + fn sub(self, other: Uint128) -> StdResult { + self.sub(&other) + } +} + +impl<'a> ops::Sub<&'a Uint128> for Uint128 { + type Output = StdResult; + + fn sub(self, rhs: &'a Uint128) -> StdResult { + let (min, sub) = (self.u128(), rhs.u128()); + min.checked_sub(sub) + .map(Uint128) + .ok_or_else(|| StdError::underflow(min, sub)) } } @@ -334,6 +355,18 @@ impl<'de> de::Visitor<'de> for Uint128Visitor { } } +impl Sum for Uint128 { + fn sum>(iter: I) -> Self { + iter.fold(Uint128::zero(), ops::Add::add) + } +} + +impl<'a> Sum<&'a Uint128> for Uint128 { + fn sum>(iter: I) -> Self { + iter.fold(Uint128::zero(), ops::Add::add) + } +} + #[cfg(test)] mod test { use super::*; @@ -662,25 +695,31 @@ mod test { let a = Uint128(12345); let b = Uint128(23456); - // test + and - for valid values + // test + with owned and reference right hand side assert_eq!(a + b, Uint128(35801)); + assert_eq!(a + &b, Uint128(35801)); + + // test - with owned and reference right hand side assert_eq!((b - a).unwrap(), Uint128(11111)); + assert_eq!((b - &a).unwrap(), Uint128(11111)); - // test += + // test += with owned and reference right hand side let mut c = Uint128(300000); c += b; assert_eq!(c, Uint128(323456)); + let mut d = Uint128(300000); + d += &b; + assert_eq!(d, Uint128(323456)); // error result on underflow (- would produce negative result) - let underflow = a - b; - match underflow { - Ok(_) => panic!("should error"), - Err(StdError::Underflow { + let underflow_result = a - b; + match underflow_result.unwrap_err() { + StdError::Underflow { minuend, subtrahend, .. - }) => assert_eq!((minuend, subtrahend), (a.to_string(), b.to_string())), - _ => panic!("expected underflow error"), + } => assert_eq!((minuend, subtrahend), (a.to_string(), b.to_string())), + err => panic!("Unexpected error: {:?}", err), } } @@ -757,4 +796,16 @@ mod test { let right = Uint128(0); assert_eq!(left * right, Uint128(0)); } + + #[test] + fn sum_works() { + let nums = vec![Uint128(17), Uint128(123), Uint128(540), Uint128(82)]; + let expected = Uint128(762); + + let sum_as_ref = nums.iter().sum(); + assert_eq!(expected, sum_as_ref); + + let sum_as_owned = nums.into_iter().sum(); + assert_eq!(expected, sum_as_owned); + } } diff --git a/packages/std/src/memory.rs b/packages/std/src/memory.rs index 6e7ff5166..80bd2ed44 100644 --- a/packages/std/src/memory.rs +++ b/packages/std/src/memory.rs @@ -84,8 +84,20 @@ pub fn build_region(data: &[u8]) -> Box { fn build_region_from_components(offset: u32, capacity: u32, length: u32) -> Box { Box::new(Region { - offset: offset, - capacity: capacity, - length: length, + offset, + capacity, + length, }) } + +/// Returns the address of the optional Region as an offset in linear memory, +/// or zero if not present +#[cfg(feature = "iterator")] +pub fn get_optional_region_address(region: &Option<&Box>) -> u32 { + /// Returns the address of the Region as an offset in linear memory + fn get_region_address(region: &Box) -> u32 { + region.as_ref() as *const Region as u32 + } + + region.map(get_region_address).unwrap_or(0) +} diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 7f4ec5216..796414779 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -2,17 +2,19 @@ use serde::de::DeserializeOwned; use std::collections::HashMap; use crate::addresses::{CanonicalAddr, HumanAddr}; +use crate::binary::Binary; use crate::coins::Coin; -use crate::encoding::Binary; -use crate::errors::{StdError, StdResult, SystemError, SystemResult}; +use crate::deps::OwnedDeps; +use crate::errors::{StdError, StdResult, SystemError}; use crate::query::{ AllBalanceResponse, AllDelegationsResponse, BalanceResponse, BankQuery, BondedDenomResponse, - DelegationResponse, FullDelegation, QueryRequest, StakingQuery, Validator, ValidatorsResponse, - WasmQuery, + CustomQuery, DelegationResponse, FullDelegation, QueryRequest, StakingQuery, Validator, + ValidatorsResponse, WasmQuery, }; +use crate::results::{ContractResult, SystemResult}; use crate::serde::{from_slice, to_binary}; use crate::storage::MemoryStorage; -use crate::traits::{Api, Extern, Querier, QuerierResult}; +use crate::traits::{Api, Querier, QuerierResult}; use crate::types::{BlockInfo, ContractInfo, Empty, Env, MessageInfo}; pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; @@ -20,13 +22,12 @@ pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; /// All external requirements that can be injected for unit tests. /// It sets the given balance for the contract itself, nothing else pub fn mock_dependencies( - canonical_length: usize, contract_balance: &[Coin], -) -> Extern { +) -> OwnedDeps { let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - Extern { + OwnedDeps { storage: MockStorage::default(), - api: MockApi::new(canonical_length), + api: MockApi::default(), querier: MockQuerier::new(&[(&contract_addr, contract_balance)]), } } @@ -34,12 +35,11 @@ pub fn mock_dependencies( /// Initializes the querier along with the mock_dependencies. /// Sets all balances provided (yoy must explicitly set contract balance if desired) pub fn mock_dependencies_with_balances( - canonical_length: usize, balances: &[(&HumanAddr, &[Coin])], -) -> Extern { - Extern { +) -> OwnedDeps { + OwnedDeps { storage: MockStorage::default(), - api: MockApi::new(canonical_length), + api: MockApi::default(), querier: MockQuerier::new(balances), } } @@ -53,18 +53,26 @@ pub type MockStorage = MemoryStorage; // not really smart, but allows us to see a difference (and consistent length for canonical adddresses) #[derive(Copy, Clone)] pub struct MockApi { - canonical_length: usize, + /// Length of canonical addresses created with this API. Contracts should not make any assumtions + /// what this value is. + pub canonical_length: usize, } impl MockApi { - pub fn new(canonical_length: usize) -> Self { - MockApi { canonical_length } + #[deprecated( + since = "0.11.0", + note = "The canonical length argument is unused. Use MockApi::default() instead." + )] + pub fn new(_canonical_length: usize) -> Self { + MockApi::default() } } impl Default for MockApi { fn default() -> Self { - Self::new(20) + MockApi { + canonical_length: 24, + } } } @@ -83,11 +91,17 @@ impl Api for MockApi { } let mut out = Vec::from(human.as_str()); - let append = self.canonical_length - out.len(); - if append > 0 { - out.extend(vec![0u8; append]); + + // pad to canonical length with NULL bytes + out.resize(self.canonical_length, 0x00); + // content-dependent rotate followed by shuffle to destroy + // the most obvious structure (https://github.com/CosmWasm/cosmwasm/issues/552) + let rotate_by = digit_sum(&out) % self.canonical_length; + out.rotate_left(rotate_by); + for _ in 0..18 { + out = riffle_shuffle(&out); } - Ok(CanonicalAddr(Binary(out))) + Ok(out.into()) } fn human_address(&self, canonical: &CanonicalAddr) -> StdResult { @@ -97,42 +111,57 @@ impl Api for MockApi { )); } - // remove trailing 0's (TODO: fix this - but fine for first tests) - let trimmed: Vec = canonical - .as_slice() - .iter() - .cloned() - .filter(|&x| x != 0) - .collect(); + let mut tmp: Vec = canonical.clone().into(); + // Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds) + for _ in 0..2 { + tmp = riffle_shuffle(&tmp); + } + // Rotate back + let rotate_by = digit_sum(&tmp) % self.canonical_length; + tmp.rotate_right(rotate_by); + // Remove NULL bytes (i.e. the padding) + let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect(); // decode UTF-8 bytes into string - let human = String::from_utf8(trimmed).map_err(StdError::invalid_utf8)?; - Ok(HumanAddr(human)) + let human = String::from_utf8(trimmed)?; + Ok(human.into()) + } + + fn debug(&self, message: &str) { + println!("{}", message); } } -/// Just set sender and sent funds for the message. The rest uses defaults. -/// The sender will be canonicalized internally to allow developers pasing in human readable senders. +/// Returns a default enviroment with height, time, chain_id, and contract address +/// You can submit as is to most contracts, or modify height/time if you want to +/// test for expiration. +/// /// This is intended for use in test code only. -pub fn mock_env>(sender: U, sent: &[Coin]) -> Env { +pub fn mock_env() -> Env { Env { block: BlockInfo { height: 12_345, time: 1_571_797_419, + time_nanos: 879305533, chain_id: "cosmos-testnet-14002".to_string(), }, - message: MessageInfo { - sender: sender.into(), - sent_funds: sent.to_vec(), - }, contract: ContractInfo { address: HumanAddr::from(MOCK_CONTRACT_ADDR), }, } } +/// Just set sender and sent funds for the message. The essential for +/// This is intended for use in test code only. +pub fn mock_info>(sender: U, sent: &[Coin]) -> MessageInfo { + MessageInfo { + sender: sender.into(), + sent_funds: sent.to_vec(), + } +} + /// 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>; +pub type MockQuerierCustomHandlerResult = SystemResult>; /// MockQuerier holds an immutable table of bank balances /// TODO: also allow querying contracts @@ -156,7 +185,7 @@ impl MockQuerier { wasm: NoWasmQuerier {}, // strange argument notation suggested as a workaround here: https://github.com/rust-lang/rust/issues/41078#issuecomment-294296365 custom_handler: Box::from(|_: &_| -> MockQuerierCustomHandlerResult { - Err(SystemError::UnsupportedRequest { + SystemResult::Err(SystemError::UnsupportedRequest { kind: "custom".to_string(), }) }), @@ -191,12 +220,12 @@ impl MockQuerier { } } -impl Querier for MockQuerier { +impl Querier for MockQuerier { fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { let request: QueryRequest = match from_slice(bin_request) { Ok(v) => v, Err(e) => { - return Err(SystemError::InvalidRequest { + return SystemResult::Err(SystemError::InvalidRequest { error: format!("Parsing query request: {}", e), request: bin_request.into(), }) @@ -206,7 +235,7 @@ impl Querier for MockQuerier { } } -impl MockQuerier { +impl MockQuerier { pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { match &request { QueryRequest::Bank(bank_query) => self.bank.query(bank_query), @@ -229,7 +258,7 @@ impl NoWasmQuerier { WasmQuery::Raw { contract_addr, .. } => contract_addr, } .clone(); - Err(SystemError::NoSuchContract { addr }) + SystemResult::Err(SystemError::NoSuchContract { addr }) } } @@ -248,7 +277,7 @@ impl BankQuerier { } pub fn query(&self, request: &BankQuery) -> QuerierResult { - match request { + let contract_result: ContractResult = match request { BankQuery::Balance { address, denom } => { // proper error on not found, serialize result on found let amount = self @@ -262,16 +291,18 @@ impl BankQuerier { denom: denom.to_string(), }, }; - Ok(to_binary(&bank_res)) + to_binary(&bank_res).into() } BankQuery::AllBalances { address } => { // proper error on not found, serialize result on found let bank_res = AllBalanceResponse { amount: self.balances.get(address).cloned().unwrap_or_default(), }; - Ok(to_binary(&bank_res)) + to_binary(&bank_res).into() } - } + }; + // system result is always ok in the mock implementation + SystemResult::Ok(contract_result) } } @@ -292,18 +323,18 @@ impl StakingQuerier { } pub fn query(&self, request: &StakingQuery) -> QuerierResult { - match request { + let contract_result: ContractResult = match request { StakingQuery::BondedDenom {} => { let res = BondedDenomResponse { denom: self.denom.clone(), }; - Ok(to_binary(&res)) + to_binary(&res).into() } StakingQuery::Validators {} => { let res = ValidatorsResponse { validators: self.validators.clone(), }; - Ok(to_binary(&res)) + to_binary(&res).into() } StakingQuery::AllDelegations { delegator } => { let delegations: Vec<_> = self @@ -314,7 +345,7 @@ impl StakingQuerier { .map(|d| d.into()) .collect(); let res = AllDelegationsResponse { delegations }; - Ok(to_binary(&res)) + to_binary(&res).into() } StakingQuery::Delegation { delegator, @@ -327,12 +358,37 @@ impl StakingQuerier { let res = DelegationResponse { delegation: delegation.cloned(), }; - Ok(to_binary(&res)) + to_binary(&res).into() } - } + }; + // system result is always ok in the mock implementation + SystemResult::Ok(contract_result) } } +/// Performs a perfect shuffle (in shuffle) +/// +/// https://en.wikipedia.org/wiki/Riffle_shuffle_permutation#Perfect_shuffles +/// https://en.wikipedia.org/wiki/In_shuffle +pub fn riffle_shuffle(input: &[T]) -> Vec { + assert!( + input.len() % 2 == 0, + "Method only defined for even number of elements" + ); + let mid = input.len() / 2; + let (left, right) = input.split_at(mid); + let mut out = Vec::::with_capacity(input.len()); + for i in 0..mid { + out.push(right[i].clone()); + out.push(left[i].clone()); + } + out +} + +pub fn digit_sum(input: &[u8]) -> usize { + input.iter().fold(0, |sum, val| sum + (*val as usize)) +} + #[cfg(test)] mod test { use super::*; @@ -340,13 +396,13 @@ mod test { use crate::{coin, coins, from_binary, Decimal, HumanAddr}; #[test] - fn mock_env_arguments() { + fn mock_info_arguments() { let name = HumanAddr("my name".to_string()); // make sure we can generate with &str, &HumanAddr, and HumanAddr - let a = mock_env("my name", &coins(100, "atom")); - let b = mock_env(&name, &coins(100, "atom")); - let c = mock_env(name, &coins(100, "atom")); + let a = mock_info("my name", &coins(100, "atom")); + let b = mock_info(&name, &coins(100, "atom")); + let c = mock_info(name, &coins(100, "atom")); // and the results are the same assert_eq!(a, b); @@ -354,22 +410,19 @@ mod test { } #[test] - fn flip_addresses() { - let api = MockApi::new(20); - let human = HumanAddr("shorty".to_string()); - let canon = api.canonical_address(&human).unwrap(); - assert_eq!(canon.len(), 20); - assert_eq!(&canon.as_slice()[0..6], human.as_str().as_bytes()); - assert_eq!(&canon.as_slice()[6..], &[0u8; 14]); + fn canonicalize_and_humanize_restores_original() { + let api = MockApi::default(); - let recovered = api.human_address(&canon).unwrap(); - assert_eq!(human, recovered); + let original = HumanAddr::from("shorty"); + let canonical = api.canonical_address(&original).unwrap(); + let recovered = api.human_address(&canonical).unwrap(); + assert_eq!(recovered, original); } #[test] #[should_panic(expected = "length not correct")] fn human_address_input_length() { - let api = MockApi::new(10); + let api = MockApi::default(); let input = CanonicalAddr(Binary(vec![61; 11])); api.human_address(&input).unwrap(); } @@ -377,7 +430,7 @@ mod test { #[test] #[should_panic(expected = "address too short")] fn canonical_address_min_input_length() { - let api = MockApi::new(10); + let api = MockApi::default(); let human = HumanAddr("1".to_string()); let _ = api.canonical_address(&human).unwrap(); } @@ -385,8 +438,8 @@ mod test { #[test] #[should_panic(expected = "address too long")] fn canonical_address_max_input_length() { - let api = MockApi::new(10); - let human = HumanAddr("longer-than-10".to_string()); + let api = MockApi::default(); + let human = HumanAddr::from("some-extremely-long-address-not-supported-by-this-api"); let _ = api.canonical_address(&human).unwrap(); } @@ -532,14 +585,14 @@ mod test { validator: val1.clone(), amount: coin(100, "ustake"), can_redelegate: coin(100, "ustake"), - accumulated_rewards: coin(5, "ustake"), + accumulated_rewards: coins(5, "ustake"), }; let del2a = FullDelegation { delegator: user_a.clone(), validator: val2.clone(), amount: coin(500, "ustake"), can_redelegate: coin(500, "ustake"), - accumulated_rewards: coin(20, "ustake"), + accumulated_rewards: coins(20, "ustake"), }; // note we cannot have multiple delegations on one validator, they are collapsed into one @@ -548,7 +601,7 @@ mod test { validator: val1.clone(), amount: coin(500, "ustake"), can_redelegate: coin(0, "ustake"), - accumulated_rewards: coin(0, "ustake"), + accumulated_rewards: coins(0, "ustake"), }; // and another one on val2 @@ -557,7 +610,7 @@ mod test { validator: val2.clone(), amount: coin(8888, "ustake"), can_redelegate: coin(4567, "ustake"), - accumulated_rewards: coin(900, "ustake"), + accumulated_rewards: coins(900, "ustake"), }; let staking = StakingQuerier::new( @@ -600,4 +653,52 @@ mod test { let dels = get_delegator(&staking, user_c.clone(), val2.clone()); assert_eq!(dels, Some(del2c.clone())); } + + #[test] + fn riffle_shuffle_works() { + // Example from https://en.wikipedia.org/wiki/In_shuffle + let start = [0xA, 0x2, 0x3, 0x4, 0x5, 0x6]; + let round1 = riffle_shuffle(&start); + assert_eq!(round1, [0x4, 0xA, 0x5, 0x2, 0x6, 0x3]); + let round2 = riffle_shuffle(&round1); + assert_eq!(round2, [0x2, 0x4, 0x6, 0xA, 0x3, 0x5]); + let round3 = riffle_shuffle(&round2); + assert_eq!(round3, start); + + // For 14 elements, the original order is restored after 4 executions + // See https://en.wikipedia.org/wiki/In_shuffle#Mathematics and https://oeis.org/A002326 + let original = [12, 33, 76, 576, 0, 44, 1, 14, 78, 99, 871212, -7, 2, -1]; + let mut result = Vec::from(original); + for _ in 0..4 { + result = riffle_shuffle(&result); + } + assert_eq!(result, original); + + // For 24 elements, the original order is restored after 20 executions + let original = [ + 7, 4, 2, 4656, 23, 45, 23, 1, 12, 76, 576, 0, 12, 1, 14, 78, 99, 12, 1212, 444, 31, + 111, 424, 34, + ]; + let mut result = Vec::from(original); + for _ in 0..20 { + result = riffle_shuffle(&result); + } + assert_eq!(result, original); + } + + #[test] + fn digit_sum_works() { + assert_eq!(digit_sum(&[]), 0); + assert_eq!(digit_sum(&[0]), 0); + assert_eq!(digit_sum(&[0, 0]), 0); + assert_eq!(digit_sum(&[0, 0, 0]), 0); + + assert_eq!(digit_sum(&[1, 0, 0]), 1); + assert_eq!(digit_sum(&[0, 1, 0]), 1); + assert_eq!(digit_sum(&[0, 0, 1]), 1); + + assert_eq!(digit_sum(&[1, 2, 3]), 6); + + assert_eq!(digit_sum(&[255, 1]), 256); + } } diff --git a/packages/std/src/query.rs b/packages/std/src/query.rs index 70ca2b003..337a8b7eb 100644 --- a/packages/std/src/query.rs +++ b/packages/std/src/query.rs @@ -1,22 +1,17 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::fmt; use crate::addresses::HumanAddr; +use crate::binary::Binary; use crate::coins::Coin; -use crate::encoding::Binary; -use crate::errors::StdResult; use crate::math::Decimal; - -pub type QueryResponse = Binary; - -pub type QueryResult = StdResult; +use crate::types::Empty; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum QueryRequest { +pub enum QueryRequest { Bank(BankQuery), - Custom(T), + Custom(C), Staking(StakingQuery), Wasm(WasmQuery), } @@ -33,6 +28,29 @@ pub enum BankQuery { AllBalances { address: HumanAddr }, } +/// A trait that is required to avoid conflicts with other query types like BankQuery and WasmQuery +/// in generic implementations. +/// You need to implement it in your custom query type. +/// +/// # Examples +/// +/// ``` +/// # use cosmwasm_std::CustomQuery; +/// # use schemars::JsonSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +/// #[serde(rename_all = "snake_case")] +/// pub enum MyCustomQuery { +/// Ping {}, +/// Capitalized { text: String }, +/// } +/// +/// impl CustomQuery for MyCustomQuery {} +/// ``` +pub trait CustomQuery: Serialize {} + +impl CustomQuery for Empty {} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WasmQuery { @@ -44,7 +62,7 @@ pub enum WasmQuery { msg: Binary, }, /// this queries the raw kv-store of the contract. - /// returns the raw, unparsed data stored at that key (or `Ok(Err(StdError:NotFound{}))` if missing) + /// returns the raw, unparsed data stored at that key, which may be an empty vector if not present Raw { contract_addr: HumanAddr, /// Key is the raw key used in the contracts Storage @@ -52,20 +70,26 @@ pub enum WasmQuery { }, } -impl From for QueryRequest { +impl From for QueryRequest { fn from(msg: BankQuery) -> Self { QueryRequest::Bank(msg) } } +impl From for QueryRequest { + fn from(msg: C) -> Self { + QueryRequest::Custom(msg) + } +} + #[cfg(feature = "staking")] -impl From for QueryRequest { +impl From for QueryRequest { fn from(msg: StakingQuery) -> Self { QueryRequest::Staking(msg) } } -impl From for QueryRequest { +impl From for QueryRequest { fn from(msg: WasmQuery) -> Self { QueryRequest::Wasm(msg) } @@ -156,7 +180,7 @@ pub struct FullDelegation { /// but there are many places between the two pub can_redelegate: Coin, /// How much we can currently withdraw - pub accumulated_rewards: Coin, + pub accumulated_rewards: Vec, } /// ValidatorsResponse is data format returned from StakingRequest::Validators query diff --git a/packages/std/src/results/attribute.rs b/packages/std/src/results/attribute.rs new file mode 100644 index 000000000..a6f8f6910 --- /dev/null +++ b/packages/std/src/results/attribute.rs @@ -0,0 +1,39 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// An key value pair that is used in the context of event attributes in logs +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] +pub struct Attribute { + pub key: String, + pub value: String, +} + +/// Creates a new Attribute. +pub fn attr(key: K, value: V) -> Attribute { + Attribute { + key: key.to_string(), + value: value.to_string(), + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::addresses::HumanAddr; + use crate::Uint128; + + #[test] + fn attr_works_for_different_types() { + let expeceted = Attribute { + key: "foo".to_string(), + value: "42".to_string(), + }; + + assert_eq!(attr("foo", "42"), expeceted); + assert_eq!(attr("foo".to_string(), "42"), expeceted); + assert_eq!(attr("foo", "42".to_string()), expeceted); + assert_eq!(attr("foo", HumanAddr::from("42")), expeceted); + assert_eq!(attr("foo", Uint128(42)), expeceted); + assert_eq!(attr("foo", 42), expeceted); + } +} diff --git a/packages/std/src/results/context.rs b/packages/std/src/results/context.rs new file mode 100644 index 000000000..1db073fff --- /dev/null +++ b/packages/std/src/results/context.rs @@ -0,0 +1,178 @@ +use schemars::JsonSchema; +use std::convert::TryFrom; +use std::fmt; + +use crate::binary::Binary; +use crate::errors::StdError; +use crate::types::Empty; + +use super::attribute::{attr, Attribute}; +use super::cosmos_msg::CosmosMsg; +use super::handle::HandleResponse; +use super::init::InitResponse; +use super::migrate::MigrateResponse; + +#[derive(Clone, Debug, PartialEq)] +pub struct Context +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + messages: Vec>, + /// The attributes that will be emitted as part of a "wasm" event + attributes: Vec, + data: Option, +} + +impl Default for Context +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn default() -> Self { + Context { + messages: vec![], + attributes: vec![], + data: None, + } + } +} + +impl Context +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + pub fn new() -> Self { + Context::default() + } + + pub fn add_attribute(&mut self, key: K, value: V) { + self.attributes.push(attr(key, value)); + } + + pub fn add_message>>(&mut self, msg: U) { + self.messages.push(msg.into()); + } + + pub fn set_data>(&mut self, data: U) { + self.data = Some(data.into()); + } +} + +impl TryFrom> for InitResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + type Error = StdError; + + fn try_from(ctx: Context) -> Result { + if ctx.data.is_some() { + Err(StdError::generic_err( + "cannot convert Context with data to InitResponse", + )) + } else { + Ok(InitResponse { + messages: ctx.messages, + attributes: ctx.attributes, + }) + } + } +} + +impl From> for HandleResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(ctx: Context) -> Self { + HandleResponse { + messages: ctx.messages, + attributes: ctx.attributes, + data: ctx.data, + } + } +} + +impl From> for MigrateResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(ctx: Context) -> Self { + MigrateResponse { + messages: ctx.messages, + attributes: ctx.attributes, + data: ctx.data, + } + } +} + +#[cfg(test)] +mod test { + use super::super::{BankMsg, HandleResponse, InitResponse, MigrateResponse}; + use super::*; + use crate::addresses::HumanAddr; + use crate::coins; + use crate::errors::{StdError, StdResult}; + use std::convert::TryInto; + + #[test] + fn empty_context() { + let ctx = Context::new(); + + let init: InitResponse = ctx.clone().try_into().unwrap(); + assert_eq!(init, InitResponse::default()); + + let init: HandleResponse = ctx.clone().try_into().unwrap(); + assert_eq!(init, HandleResponse::default()); + + let init: MigrateResponse = ctx.clone().try_into().unwrap(); + assert_eq!(init, MigrateResponse::default()); + } + + #[test] + fn full_context() { + let mut ctx = Context::new(); + + // build it up with the builder commands + ctx.add_attribute("sender", &HumanAddr::from("john")); + ctx.add_attribute("action", "test"); + ctx.add_message(BankMsg::Send { + from_address: HumanAddr::from("goo"), + to_address: HumanAddr::from("foo"), + amount: coins(128, "uint"), + }); + + // and this is what is should return + let expected_msgs = vec![CosmosMsg::Bank(BankMsg::Send { + from_address: HumanAddr::from("goo"), + to_address: HumanAddr::from("foo"), + amount: coins(128, "uint"), + })]; + let expected_attributes = vec![attr("sender", "john"), attr("action", "test")]; + let expected_data = Some(Binary::from(b"banana")); + + // try InitResponse before setting data + let init: InitResponse = ctx.clone().try_into().unwrap(); + assert_eq!(&init.messages, &expected_msgs); + assert_eq!(&init.attributes, &expected_attributes); + + ctx.set_data(b"banana"); + // should fail with data set + let init_err: StdResult = ctx.clone().try_into(); + match init_err.unwrap_err() { + StdError::GenericErr { msg, .. } => { + assert_eq!(msg, "cannot convert Context with data to InitResponse") + } + err => panic!("Unexpected error: {:?}", err), + } + + // try Handle with everything set + let handle: HandleResponse = ctx.clone().try_into().unwrap(); + assert_eq!(&handle.messages, &expected_msgs); + assert_eq!(&handle.attributes, &expected_attributes); + assert_eq!(&handle.data, &expected_data); + + // try Migrate with everything set + let migrate: MigrateResponse = ctx.clone().try_into().unwrap(); + assert_eq!(&migrate.messages, &expected_msgs); + assert_eq!(&migrate.attributes, &expected_attributes); + assert_eq!(&migrate.data, &expected_data); + } +} diff --git a/packages/std/src/results/contract_result.rs b/packages/std/src/results/contract_result.rs new file mode 100644 index 000000000..5fce73c91 --- /dev/null +++ b/packages/std/src/results/contract_result.rs @@ -0,0 +1,165 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// 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. +/// +/// We 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. +/// +/// # Examples +/// +/// Success: +/// +/// ``` +/// # 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()); +/// ``` +/// +/// Failure: +/// +/// ``` +/// # 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()); +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ContractResult { + Ok(S), + /// 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. + #[serde(rename = "error")] + Err(String), +} + +// Implementations here mimic the Result API and should be implemented via a conversion to Result +// to ensure API consistency +impl ContractResult { + /// Converts a `ContractResult` to a `Result` as a convenient way + /// to access the full Result API. + pub fn into_result(self) -> Result { + Result::::from(self) + } + + pub fn unwrap(self) -> S { + self.into_result().unwrap() + } +} + +impl ContractResult { + pub fn unwrap_err(self) -> String { + self.into_result().unwrap_err() + } +} + +impl From> for ContractResult { + fn from(original: Result) -> ContractResult { + match original { + Ok(value) => ContractResult::Ok(value), + Err(err) => ContractResult::Err(err.to_string()), + } + } +} + +impl From> for Result { + fn from(original: ContractResult) -> Result { + match original { + ContractResult::Ok(value) => Ok(value), + ContractResult::Err(err) => Err(err), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{from_slice, to_vec, HandleResponse, HandleResult, StdError, StdResult}; + + #[test] + fn contract_result_serialization_works() { + let result = ContractResult::Ok(12); + assert_eq!(&to_vec(&result).unwrap(), b"{\"ok\":12}"); + + let result = ContractResult::Ok("foo"); + assert_eq!(&to_vec(&result).unwrap(), b"{\"ok\":\"foo\"}"); + + let result: ContractResult = ContractResult::Ok(HandleResponse::default()); + assert_eq!( + to_vec(&result).unwrap(), + br#"{"ok":{"messages":[],"attributes":[],"data":null}}"#.to_vec() + ); + + let result: ContractResult = ContractResult::Err("broken".to_string()); + assert_eq!(&to_vec(&result).unwrap(), b"{\"error\":\"broken\"}"); + } + + #[test] + fn contract_result_deserialization_works() { + let result: ContractResult = from_slice(br#"{"ok":12}"#).unwrap(); + assert_eq!(result, ContractResult::Ok(12)); + + let result: ContractResult = from_slice(br#"{"ok":"foo"}"#).unwrap(); + assert_eq!(result, ContractResult::Ok("foo".to_string())); + + let result: ContractResult = + from_slice(br#"{"ok":{"messages":[],"attributes":[],"data":null}}"#).unwrap(); + assert_eq!(result, ContractResult::Ok(HandleResponse::default())); + + let result: ContractResult = from_slice(br#"{"error":"broken"}"#).unwrap(); + assert_eq!(result, ContractResult::Err("broken".to_string())); + + // ignores whitespace + let result: ContractResult = from_slice(b" {\n\t \"ok\": 5898\n} ").unwrap(); + assert_eq!(result, ContractResult::Ok(5898)); + + // fails for additional attributes + let parse: StdResult> = from_slice(br#"{"unrelated":321,"ok":4554}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + let parse: StdResult> = from_slice(br#"{"ok":4554,"unrelated":321}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + let parse: StdResult> = + from_slice(br#"{"ok":4554,"error":"What's up now?"}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + } + + #[test] + fn can_convert_from_core_result() { + let original: HandleResult = Ok(HandleResponse::default()); + let converted: ContractResult = original.into(); + assert_eq!(converted, ContractResult::Ok(HandleResponse::default())); + + let original: HandleResult = Err(StdError::generic_err("broken")); + let converted: ContractResult = original.into(); + assert_eq!( + converted, + ContractResult::Err("Generic error: broken".to_string()) + ); + } + + #[test] + fn can_convert_to_core_result() { + let original = ContractResult::Ok(HandleResponse::default()); + let converted: Result = original.into(); + assert_eq!(converted, Ok(HandleResponse::default())); + + let original = ContractResult::Err("went wrong".to_string()); + let converted: Result = original.into(); + assert_eq!(converted, Err("went wrong".to_string())); + } +} diff --git a/packages/std/src/results/cosmos_msg.rs b/packages/std/src/results/cosmos_msg.rs new file mode 100644 index 000000000..96e2ee7bb --- /dev/null +++ b/packages/std/src/results/cosmos_msg.rs @@ -0,0 +1,125 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::addresses::HumanAddr; +use crate::binary::Binary; +use crate::coins::Coin; +use crate::types::Empty; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +// See https://github.com/serde-rs/serde/issues/1296 why we cannot add De-Serialize trait bounds to T +pub enum CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + Bank(BankMsg), + // by default we use RawMsg, but a contract can override that + // to call into more app-specific code (whatever they define) + Custom(T), + Staking(StakingMsg), + Wasm(WasmMsg), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum BankMsg { + // this moves tokens in the underlying sdk + Send { + from_address: HumanAddr, + to_address: HumanAddr, + amount: Vec, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum StakingMsg { + Delegate { + // delegator is automatically set to address of the calling contract + validator: HumanAddr, + amount: Coin, + }, + Undelegate { + // delegator is automatically set to address of the calling contract + validator: HumanAddr, + amount: Coin, + }, + Withdraw { + // delegator is automatically set to address of the calling contract + validator: HumanAddr, + /// this is the "withdraw address", the one that should receive the rewards + /// if None, then use delegator address + recipient: Option, + }, + Redelegate { + // delegator is automatically set to address of the calling contract + src_validator: HumanAddr, + dst_validator: HumanAddr, + amount: Coin, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WasmMsg { + /// this dispatches a call to another contract at a known address (with known ABI) + Execute { + contract_addr: HumanAddr, + /// msg is the json-encoded HandleMsg struct (as raw Binary) + msg: Binary, + send: Vec, + }, + /// this instantiates a new contracts from previously uploaded wasm code + Instantiate { + code_id: u64, + /// msg is the json-encoded InitMsg struct (as raw Binary) + msg: Binary, + send: Vec, + /// optional human-readbale label for the contract + label: Option, + }, +} + +impl From for CosmosMsg { + fn from(msg: BankMsg) -> Self { + CosmosMsg::Bank(msg) + } +} + +#[cfg(feature = "staking")] +impl From for CosmosMsg { + fn from(msg: StakingMsg) -> Self { + CosmosMsg::Staking(msg) + } +} + +impl From for CosmosMsg { + fn from(msg: WasmMsg) -> Self { + CosmosMsg::Wasm(msg) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::coins; + + #[test] + fn from_bank_msg_works() { + let from_address = HumanAddr::from("me"); + let to_address = HumanAddr::from("you"); + let amount = coins(1015, "earth"); + let bank = BankMsg::Send { + from_address, + to_address, + amount, + }; + let msg: CosmosMsg = bank.clone().into(); + match msg { + CosmosMsg::Bank(msg) => assert_eq!(bank, msg), + _ => panic!("must encode in Bank variant"), + } + } +} diff --git a/packages/std/src/results/handle.rs b/packages/std/src/results/handle.rs new file mode 100644 index 000000000..059d67450 --- /dev/null +++ b/packages/std/src/results/handle.rs @@ -0,0 +1,36 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::binary::Binary; +use crate::errors::StdError; +use crate::types::Empty; + +use super::attribute::Attribute; +use super::cosmos_msg::CosmosMsg; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct HandleResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + pub messages: Vec>, + /// The attributes that will be emitted as part of a "wasm" event + pub attributes: Vec, + pub data: Option, +} + +impl Default for HandleResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn default() -> Self { + HandleResponse { + messages: vec![], + attributes: vec![], + data: None, + } + } +} + +pub type HandleResult = Result, StdError>; diff --git a/packages/std/src/results/init.rs b/packages/std/src/results/init.rs new file mode 100644 index 000000000..d18a85385 --- /dev/null +++ b/packages/std/src/results/init.rs @@ -0,0 +1,60 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::errors::StdError; +use crate::types::Empty; + +use super::attribute::Attribute; +use super::cosmos_msg::CosmosMsg; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InitResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + pub messages: Vec>, + /// The attributes that will be emitted as part of a "wasm" event + pub attributes: Vec, +} + +impl Default for InitResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn default() -> Self { + InitResponse { + messages: vec![], + attributes: vec![], + } + } +} + +pub type InitResult = Result, StdError>; + +#[cfg(test)] +mod test { + use super::super::BankMsg; + use super::*; + use crate::addresses::HumanAddr; + use crate::{coins, from_slice, to_vec}; + + #[test] + fn can_serialize_and_deserialize_init_response() { + let original = InitResponse { + messages: vec![BankMsg::Send { + from_address: HumanAddr::from("me"), + to_address: HumanAddr::from("you"), + amount: coins(1015, "earth"), + } + .into()], + attributes: vec![Attribute { + key: "action".to_string(), + value: "release".to_string(), + }], + }; + let serialized = to_vec(&original).expect("encode contract result"); + let deserialized: InitResponse = from_slice(&serialized).expect("decode contract result"); + assert_eq!(deserialized, original); + } +} diff --git a/packages/std/src/results/migrate.rs b/packages/std/src/results/migrate.rs new file mode 100644 index 000000000..34b059594 --- /dev/null +++ b/packages/std/src/results/migrate.rs @@ -0,0 +1,36 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::binary::Binary; +use crate::errors::StdError; +use crate::types::Empty; + +use super::attribute::Attribute; +use super::cosmos_msg::CosmosMsg; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct MigrateResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + pub messages: Vec>, + /// The attributes that will be emitted as part of a "wasm" event + pub attributes: Vec, + pub data: Option, +} + +impl Default for MigrateResponse +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn default() -> Self { + MigrateResponse { + messages: vec![], + attributes: vec![], + data: None, + } + } +} + +pub type MigrateResult = Result, StdError>; diff --git a/packages/std/src/results/mod.rs b/packages/std/src/results/mod.rs new file mode 100644 index 000000000..7f8305826 --- /dev/null +++ b/packages/std/src/results/mod.rs @@ -0,0 +1,21 @@ +//! This module contains the messages that are sent from the contract to the VM as an execution result + +mod attribute; +mod context; +mod contract_result; +mod cosmos_msg; +mod handle; +mod init; +mod migrate; +mod query; +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 handle::{HandleResponse, HandleResult}; +pub use init::{InitResponse, InitResult}; +pub use migrate::{MigrateResponse, MigrateResult}; +pub use query::{QueryResponse, QueryResult}; +pub use system_result::SystemResult; diff --git a/packages/std/src/results/query.rs b/packages/std/src/results/query.rs new file mode 100644 index 000000000..c0713ffb0 --- /dev/null +++ b/packages/std/src/results/query.rs @@ -0,0 +1,6 @@ +use crate::binary::Binary; +use crate::errors::StdError; + +pub type QueryResponse = Binary; + +pub type QueryResult = Result; diff --git a/packages/std/src/results/system_result.rs b/packages/std/src/results/system_result.rs new file mode 100644 index 000000000..8fd844c5b --- /dev/null +++ b/packages/std/src/results/system_result.rs @@ -0,0 +1,76 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use super::super::errors::SystemError; + +/// This is the outer result type returned by a querier to the contract. +/// +/// We 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. +/// +/// # Examples +/// +/// Success: +/// +/// ``` +/// # use cosmwasm_std::{to_vec, Binary, ContractResult, SystemResult}; +/// let data = Binary::from(b"hello, world"); +/// let result = SystemResult::Ok(ContractResult::Ok(data)); +/// assert_eq!(to_vec(&result).unwrap(), br#"{"ok":{"ok":"aGVsbG8sIHdvcmxk"}}"#.to_vec()); +/// ``` +/// +/// Failure: +/// +/// ``` +/// # use cosmwasm_std::{to_vec, Binary, ContractResult, SystemResult, SystemError}; +/// let error = SystemError::Unknown {}; +/// let result: SystemResult = SystemResult::Err(error); +/// assert_eq!(to_vec(&result).unwrap(), br#"{"error":{"unknown":{}}}"#.to_vec()); +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SystemResult { + Ok(S), + #[serde(rename = "error")] + Err(SystemError), +} + +// Implementations here mimic the Result API and should be implemented via a conversion to Result +// to ensure API consistency +impl SystemResult { + /// Converts a `ContractResult` to a `Result` as a convenient way + /// to access the full Result API. + pub fn into_result(self) -> Result { + Result::::from(self) + } + + pub fn unwrap(self) -> S { + self.into_result().unwrap() + } +} + +impl SystemResult { + pub fn unwrap_err(self) -> SystemError { + self.into_result().unwrap_err() + } +} + +impl From> for SystemResult { + fn from(original: Result) -> SystemResult { + match original { + Ok(value) => SystemResult::Ok(value), + Err(err) => SystemResult::Err(err), + } + } +} + +impl From> for Result { + fn from(original: SystemResult) -> Result { + match original { + SystemResult::Ok(value) => Ok(value), + SystemResult::Err(err) => Err(err), + } + } +} diff --git a/packages/std/src/serde.rs b/packages/std/src/serde.rs index 2b5ffaf7d..696870e12 100644 --- a/packages/std/src/serde.rs +++ b/packages/std/src/serde.rs @@ -5,7 +5,7 @@ use serde::{de::DeserializeOwned, Serialize}; use std::any::type_name; -use crate::encoding::Binary; +use crate::binary::Binary; use crate::errors::{StdError, StdResult}; pub fn from_slice(value: &[u8]) -> StdResult { @@ -89,6 +89,18 @@ mod test { ); } + #[test] + fn from_slice_or_binary() { + let msg = SomeMsg::Refund {}; + let serialized: Binary = to_binary(&msg).unwrap(); + + let parse_binary: SomeMsg = from_binary(&serialized).unwrap(); + assert_eq!(parse_binary, msg); + + let parse_slice: SomeMsg = from_slice(&serialized).unwrap(); + assert_eq!(parse_slice, msg); + } + #[test] fn to_vec_works_for_special_chars() { let msg = SomeMsg::Cowsay { diff --git a/packages/std/src/storage.rs b/packages/std/src/storage.rs index 85dc48ac6..b9813965f 100644 --- a/packages/std/src/storage.rs +++ b/packages/std/src/storage.rs @@ -1,4 +1,5 @@ use std::collections::BTreeMap; +use std::fmt; #[cfg(feature = "iterator")] use std::iter; #[cfg(feature = "iterator")] @@ -6,7 +7,7 @@ use std::ops::{Bound, RangeBounds}; #[cfg(feature = "iterator")] use crate::iterator::{Order, KV}; -use crate::traits::{ReadonlyStorage, Storage}; +use crate::traits::Storage; #[derive(Default)] pub struct MemoryStorage { @@ -19,11 +20,23 @@ impl MemoryStorage { } } -impl ReadonlyStorage for MemoryStorage { +impl Storage for MemoryStorage { fn get(&self, key: &[u8]) -> Option> { self.data.get(key).cloned() } + fn set(&mut self, key: &[u8], value: &[u8]) { + if value.is_empty() { + panic!("TL;DR: Value must not be empty in Storage::set but in most cases you can use Storage::remove instead. Long story: Getting empty values from storage is not well supported at the moment. Some of our internal interfaces cannot differentiate between a non-existent key and an empty value. Right now, you cannot rely on the behaviour of empty values. To protect you from trouble later on, we stop here. Sorry for the inconvenience! We highly welcome you to contribute to CosmWasm, making this more solid one way or the other."); + } + + self.data.insert(key.to_vec(), value.to_vec()); + } + + fn remove(&mut self, key: &[u8]) { + self.data.remove(key); + } + #[cfg(feature = "iterator")] /// range allows iteration over a set of keys, either forwards or backwards /// uses standard rust range notation, and eg db.range(b"foo"..b"bar") also works reverse @@ -52,6 +65,28 @@ impl ReadonlyStorage for MemoryStorage { } } +/// This debug implementation is made for inspecting storages in unit testing. +/// It is made for human readability only and the output can change at any time. +impl fmt::Debug for MemoryStorage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "MemoryStorage ({} entries)", self.data.len())?; + f.write_str(" {\n")?; + for (key, value) in &self.data { + f.write_str(" 0x")?; + for byte in key { + write!(f, "{:02x}", byte)?; + } + f.write_str(": 0x")?; + for byte in value { + write!(f, "{:02x}", byte)?; + } + f.write_str("\n")?; + } + f.write_str("}")?; + Ok(()) + } +} + #[cfg(feature = "iterator")] fn range_bounds(start: Option<&[u8]>, end: Option<&[u8]>) -> impl RangeBounds> { ( @@ -71,16 +106,6 @@ fn clone_item(item_ref: BTreeMapPairRef) -> KV { (key.clone(), value.clone()) } -impl Storage for MemoryStorage { - fn set(&mut self, key: &[u8], value: &[u8]) { - self.data.insert(key.to_vec(), value.to_vec()); - } - - fn remove(&mut self, key: &[u8]) { - self.data.remove(key); - } -} - #[cfg(test)] mod test { use super::*; @@ -94,6 +119,15 @@ mod test { assert_eq!(store.get(b"food"), None); } + #[test] + #[should_panic( + expected = "Getting empty values from storage is not well supported at the moment." + )] + fn set_panics_for_empty() { + let mut store = MemoryStorage::new(); + store.set(b"foo", b""); + } + #[test] fn delete() { let mut store = MemoryStorage::new(); @@ -245,4 +279,54 @@ mod test { ); } } + + #[test] + fn memory_storage_implements_debug() { + let store = MemoryStorage::new(); + assert_eq!( + format!("{:?}", store), + "MemoryStorage (0 entries) {\n\ + }" + ); + + // With one element + let mut store = MemoryStorage::new(); + store.set(&[0x00, 0xAB, 0xDD], &[0xFF, 0xD5]); + assert_eq!( + format!("{:?}", store), + "MemoryStorage (1 entries) {\n\ + \x20\x200x00abdd: 0xffd5\n\ + }" + ); + + // Sorted by key + let mut store = MemoryStorage::new(); + store.set(&[0x00, 0xAB, 0xDD], &[0xFF, 0xD5]); + store.set(&[0x00, 0xAB, 0xEE], &[0xFF, 0xD5]); + store.set(&[0x00, 0xAB, 0xCC], &[0xFF, 0xD5]); + assert_eq!( + format!("{:?}", store), + "MemoryStorage (3 entries) {\n\ + \x20\x200x00abcc: 0xffd5\n\ + \x20\x200x00abdd: 0xffd5\n\ + \x20\x200x00abee: 0xffd5\n\ + }" + ); + + // Different lengths + let mut store = MemoryStorage::new(); + store.set(&[0xAA], &[0x11]); + store.set(&[0xAA, 0xBB], &[0x11, 0x22]); + store.set(&[0xAA, 0xBB, 0xCC], &[0x11, 0x22, 0x33]); + store.set(&[0xAA, 0xBB, 0xCC, 0xDD], &[0x11, 0x22, 0x33, 0x44]); + assert_eq!( + format!("{:?}", store), + "MemoryStorage (4 entries) {\n\ + \x20\x200xaa: 0x11\n\ + \x20\x200xaabb: 0x1122\n\ + \x20\x200xaabbcc: 0x112233\n\ + \x20\x200xaabbccdd: 0x11223344\n\ + }" + ); + } } diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 98bb1e5b4..4818e498c 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -1,45 +1,26 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::addresses::{CanonicalAddr, HumanAddr}; +use crate::binary::Binary; use crate::coins::Coin; -use crate::encoding::Binary; -use crate::errors::{StdError, StdResult, SystemResult}; +use crate::errors::{StdError, StdResult}; #[cfg(feature = "iterator")] use crate::iterator::{Order, KV}; -use crate::query::{AllBalanceResponse, BalanceResponse, BankQuery, QueryRequest}; +use crate::query::{ + AllBalanceResponse, BalanceResponse, BankQuery, CustomQuery, QueryRequest, WasmQuery, +}; #[cfg(feature = "staking")] use crate::query::{ AllDelegationsResponse, BondedDenomResponse, Delegation, DelegationResponse, FullDelegation, StakingQuery, Validator, ValidatorsResponse, }; -use crate::serde::{from_binary, to_vec}; +use crate::results::{ContractResult, SystemResult}; +use crate::serde::{from_binary, to_binary, to_vec}; use crate::types::Empty; -/// Holds all external dependencies of the contract. -/// Designed to allow easy dependency injection at runtime. -/// This cannot be copied or cloned since it would behave differently -/// for mock storages and a bridge storage in the VM. -pub struct Extern { - pub storage: S, - pub api: A, - pub querier: Q, -} - -impl Extern { - /// change_querier is a helper mainly for test code when swapping out the Querier - /// from the auto-generated one from mock_dependencies. This changes the type of - /// Extern so replaces requires some boilerplate. - pub fn change_querier T>(self, transform: F) -> Extern { - Extern { - storage: self.storage, - api: self.api, - querier: transform(self.querier), - } - } -} - -/// ReadonlyStorage is access to the contracts persistent data store -pub trait ReadonlyStorage { +/// Storage provides read and write access to a persistent storage. +/// If you only want to provide read access, provide `&Storage` +pub trait Storage { /// Returns None when key does not exist. /// Returns Some(Vec) when key exists. /// @@ -59,10 +40,7 @@ pub trait ReadonlyStorage { end: Option<&[u8]>, order: Order, ) -> Box + 'a>; -} -// Storage extends ReadonlyStorage to give mutable access -pub trait Storage: ReadonlyStorage { fn set(&mut self, key: &[u8], value: &[u8]); /// Removes a database entry at `key`. /// @@ -71,22 +49,30 @@ pub trait Storage: ReadonlyStorage { fn remove(&mut self, key: &[u8]); } -/// Api are callbacks to system functions defined outside of the wasm modules. -/// This is a trait to allow Mocks in the test code. +/// Api are callbacks to system functions implemented outside of the wasm modules. +/// Currently it just supports address conversion but we could add eg. crypto functions here. /// -/// Currently it just supports address conversion, we could add eg. crypto functions here. -/// These should all be pure (stateless) functions. If you need state, you probably want -/// to use the Querier. +/// This is a trait to allow mocks in the test code. Its members have a read-only +/// reference to the Api instance to allow accessing configuration. +/// Implementations must not have mutable state, such that an instance can freely +/// be copied and shared between threads without affecting the behaviour. +/// Given an Api instance, all members should return the same value when called with the same +/// arguments. In particular this means the result must not depend in the state of the chain. +/// If you need to access chaim state, you probably want to use the Querier. +/// Side effects (such as logging) are allowed. /// /// We can use feature flags to opt-in to non-essential methods /// for backwards compatibility in systems that don't have them all. -pub trait Api: Copy + Clone + Send { +pub trait Api { fn canonical_address(&self, human: &HumanAddr) -> StdResult; fn human_address(&self, canonical: &CanonicalAddr) -> StdResult; + /// Emits a debugging message that is handled depending on the environment (typically printed to console or ignored). + /// Those messages are not persisted to chain. + fn debug(&self, message: &str); } /// A short-hand alias for the two-level query result (1. accessing the contract, 2. executing query in the contract) -pub type QuerierResult = SystemResult>; +pub type QuerierResult = SystemResult>; pub trait Querier { /// raw_query is all that must be implemented for the Querier. @@ -95,10 +81,26 @@ pub trait Querier { /// types. People using the querier probably want one of the simpler auto-generated /// helper methods fn raw_query(&self, bin_request: &[u8]) -> QuerierResult; +} + +#[derive(Copy, Clone)] +pub struct QuerierWrapper<'a>(&'a dyn Querier); + +impl<'a> QuerierWrapper<'a> { + pub fn new(querier: &'a dyn Querier) -> Self { + QuerierWrapper(querier) + } + + /// This allows us to pass through binary queries from one level to another without + /// knowing the custom format, or we can decode it, with the knowledge of the allowed + /// types. You probably want one of the simpler auto-generated helper methods + pub fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + self.0.raw_query(bin_request) + } /// query is a shorthand for custom_query when we are not using a custom type, /// this allows us to avoid specifying "Empty" in all the type definitions. - fn query(&self, request: &QueryRequest) -> StdResult { + pub fn query(&self, request: &QueryRequest) -> StdResult { self.custom_query(request) } @@ -109,31 +111,26 @@ pub trait Querier { /// Any error (System Error, Error or called contract, or Parse Error) are flattened into /// one level. Only use this if you don't need to check the SystemError /// eg. If you don't differentiate between contract missing and contract returned error - fn custom_query( + pub fn custom_query( &self, - request: &QueryRequest, + request: &QueryRequest, ) -> StdResult { - let raw = match to_vec(request) { - Ok(raw) => raw, - Err(e) => { - return Err(StdError::generic_err(format!( - "Serializing QueryRequest: {}", - e - ))) - } - }; + let raw = to_vec(request).map_err(|serialize_err| { + StdError::generic_err(format!("Serializing QueryRequest: {}", serialize_err)) + })?; match self.raw_query(&raw) { - Err(sys) => Err(StdError::generic_err(format!( + SystemResult::Err(system_err) => Err(StdError::generic_err(format!( "Querier system error: {}", - sys + system_err ))), - Ok(Err(err)) => Err(err), - // in theory we would process the response, but here it is the same type, so just pass through - Ok(Ok(res)) => from_binary(&res), + SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::generic_err( + format!("Querier contract error: {}", contract_err), + )), + SystemResult::Ok(ContractResult::Ok(value)) => from_binary(&value), } } - fn query_balance>(&self, address: U, denom: &str) -> StdResult { + pub fn query_balance>(&self, address: U, denom: &str) -> StdResult { let request = BankQuery::Balance { address: address.into(), denom: denom.to_string(), @@ -143,7 +140,7 @@ pub trait Querier { Ok(res.amount) } - fn query_all_balances>(&self, address: U) -> StdResult> { + pub fn query_all_balances>(&self, address: U) -> StdResult> { let request = BankQuery::AllBalances { address: address.into(), } @@ -152,22 +149,77 @@ pub trait Querier { Ok(res.amount) } + // this queries another wasm contract. You should know a priori the proper types for T and U + // (response and request) based on the contract API + pub fn query_wasm_smart>( + &self, + contract: V, + msg: &U, + ) -> StdResult { + let request = WasmQuery::Smart { + contract_addr: contract.into(), + msg: to_binary(msg)?, + } + .into(); + self.query(&request) + } + + // this queries the raw storage from another wasm contract. + // you must know the exact layout and are implementation dependent + // (not tied to an interface like query_wasm_smart) + // that said, if you are building a few contracts together, this is a much cheaper approach + // + // Similar return value to Storage.get(). Returns Some(val) or None if the data is there. + // It only returns error on some runtime issue, not on any data cases. + pub fn query_wasm_raw, U: Into>( + &self, + contract: T, + key: U, + ) -> StdResult>> { + let request: QueryRequest = WasmQuery::Raw { + contract_addr: contract.into(), + key: key.into(), + } + .into(); + // we cannot use query, as it will try to parse the binary data, when we just want to return it, + // so a bit of code copy here... + let raw = to_vec(&request).map_err(|serialize_err| { + StdError::generic_err(format!("Serializing QueryRequest: {}", serialize_err)) + })?; + match self.raw_query(&raw) { + SystemResult::Err(system_err) => Err(StdError::generic_err(format!( + "Querier system error: {}", + system_err + ))), + SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::generic_err( + format!("Querier contract error: {}", contract_err), + )), + SystemResult::Ok(ContractResult::Ok(value)) => { + if value.is_empty() { + Ok(None) + } else { + Ok(Some(value.into())) + } + } + } + } + #[cfg(feature = "staking")] - fn query_validators(&self) -> StdResult> { + pub fn query_validators(&self) -> StdResult> { let request = StakingQuery::Validators {}.into(); let res: ValidatorsResponse = self.query(&request)?; Ok(res.validators) } #[cfg(feature = "staking")] - fn query_bonded_denom(&self) -> StdResult { + pub fn query_bonded_denom(&self) -> StdResult { let request = StakingQuery::BondedDenom {}.into(); let res: BondedDenomResponse = self.query(&request)?; Ok(res.denom) } #[cfg(feature = "staking")] - fn query_all_delegations>( + pub fn query_all_delegations>( &self, delegator: U, ) -> StdResult> { @@ -180,7 +232,7 @@ pub trait Querier { } #[cfg(feature = "staking")] - fn query_delegation>( + pub fn query_delegation>( &self, delegator: U, validator: U, diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs index 9f368deb4..5e2498eee 100644 --- a/packages/std/src/types.rs +++ b/packages/std/src/types.rs @@ -7,21 +7,69 @@ use crate::coins::Coin; #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] pub struct Env { pub block: BlockInfo, - pub message: MessageInfo, pub contract: ContractInfo, } #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] pub struct BlockInfo { pub height: u64, - // time is seconds since epoch begin (Jan. 1, 1970) + /// Absolute time of the block creation in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC). + /// + /// The source of this is the [BFT Time in Tendermint](https://docs.tendermint.com/master/spec/consensus/bft-time.html), + /// converted from nanoseconds to second precision by truncating the fractioal part. pub time: u64, + /// The fractional part of the block time in nanoseconds since `time` (0 to 999999999). + /// Add this to `time` if you need a high precision block time. + /// + /// # Examples + /// + /// Using chrono: + /// + /// ``` + /// # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; + /// # let env = Env { + /// # block: BlockInfo { + /// # height: 12_345, + /// # time: 1_571_797_419, + /// # time_nanos: 879305533, + /// # chain_id: "cosmos-testnet-14002".to_string(), + /// # }, + /// # contract: ContractInfo { + /// # address: HumanAddr::from("contract"), + /// # }, + /// # }; + /// # extern crate chrono; + /// use chrono::NaiveDateTime; + /// let dt = NaiveDateTime::from_timestamp(env.block.time as i64, env.block.time_nanos as u32); + /// ``` + /// + /// Creating a simple millisecond-precision timestamp (as used in JavaScript): + /// + /// ``` + /// # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; + /// # let env = Env { + /// # block: BlockInfo { + /// # height: 12_345, + /// # time: 1_571_797_419, + /// # time_nanos: 879305533, + /// # chain_id: "cosmos-testnet-14002".to_string(), + /// # }, + /// # contract: ContractInfo { + /// # address: HumanAddr::from("contract"), + /// # }, + /// # }; + /// let millis = (env.block.time * 1_000) + (env.block.time_nanos / 1_000_000); + /// ``` + pub time_nanos: u64, pub chain_id: String, } +/// MessageInfo is sent with `init`, `handle`, and `migrate` calls, but not with queries. +/// It contains the essential info for authorization - identity of the call, and payment #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] pub struct MessageInfo { - /// The `sender` field from the wasm/store-code, wasm/instantiate or wasm/execute message. + /// The `sender` field from the `wasm/MsgStoreCode`, `wasm/MsgInstantiateContract`, `wasm/MsgMigrateContract` + /// or `wasm/MsgExecuteContract` message. /// You can think of this as the address that initiated the action (i.e. the message). What that /// means exactly heavily depends on the application. /// diff --git a/packages/storage/Cargo.toml b/packages/storage/Cargo.toml index 457284912..39ca5d624 100644 --- a/packages/storage/Cargo.toml +++ b/packages/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmwasm-storage" -version = "0.10.0" +version = "0.12.0" authors = ["Ethan Frey "] edition = "2018" description = "CosmWasm library with useful helpers for Storage patterns" @@ -19,8 +19,5 @@ iterator = ["cosmwasm-std/iterator"] [dependencies] # Uses the path when built locally; uses the given version from crates.io when published -cosmwasm-std = { path = "../std", version = "0.10.0" } +cosmwasm-std = { path = "../std", version = "0.12.0" } serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } - -[dev-dependencies] -snafu = { version = "0.6.3" } diff --git a/packages/storage/README.md b/packages/storage/README.md index 7a1619691..3f9bd691f 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -10,7 +10,7 @@ boilterplate. ## Contents - [PrefixedStorage](#prefixed-storage) -- [TypedStoreage](#typed-storage) +- [TypedStorage](#typed-storage) - [Bucket](#bucket) - [Singleton](#singleton) diff --git a/packages/storage/src/bucket.rs b/packages/storage/src/bucket.rs index a93941840..402adab81 100644 --- a/packages/storage/src/bucket.rs +++ b/packages/storage/src/bucket.rs @@ -1,7 +1,7 @@ use serde::{de::DeserializeOwned, ser::Serialize}; use std::marker::PhantomData; -use cosmwasm_std::{to_vec, ReadonlyStorage, StdResult, Storage}; +use cosmwasm_std::{to_vec, StdError, StdResult, Storage}; #[cfg(feature = "iterator")] use cosmwasm_std::{Order, KV}; @@ -13,49 +13,48 @@ use crate::namespace_helpers::{get_with_prefix, remove_with_prefix, set_with_pre use crate::type_helpers::deserialize_kv; use crate::type_helpers::{may_deserialize, must_deserialize}; -pub fn bucket<'a, S: Storage, T>(namespace: &[u8], storage: &'a mut S) -> Bucket<'a, S, T> +/// An alias of Bucket::new for less verbose usage +pub fn bucket<'a, T>(storage: &'a mut dyn Storage, namespace: &[u8]) -> Bucket<'a, T> where T: Serialize + DeserializeOwned, { - Bucket::new(namespace, storage) + Bucket::new(storage, namespace) } -pub fn bucket_read<'a, S: ReadonlyStorage, T>( - namespace: &[u8], - storage: &'a S, -) -> ReadonlyBucket<'a, S, T> +/// An alias of ReadonlyBucket::new for less verbose usage +pub fn bucket_read<'a, T>(storage: &'a dyn Storage, namespace: &[u8]) -> ReadonlyBucket<'a, T> where T: Serialize + DeserializeOwned, { - ReadonlyBucket::new(namespace, storage) + ReadonlyBucket::new(storage, namespace) } -pub struct Bucket<'a, S: Storage, T> +pub struct Bucket<'a, T> where T: Serialize + DeserializeOwned, { - storage: &'a mut S, - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData<&'a T>, + storage: &'a mut dyn Storage, prefix: Vec, + // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed + data: PhantomData, } -impl<'a, S: Storage, T> Bucket<'a, S, T> +impl<'a, T> Bucket<'a, T> where T: Serialize + DeserializeOwned, { - pub fn new(namespace: &[u8], storage: &'a mut S) -> Self { + pub fn new(storage: &'a mut dyn Storage, namespace: &[u8]) -> Self { Bucket { - prefix: to_length_prefixed(namespace), storage, + prefix: to_length_prefixed(namespace), data: PhantomData, } } - pub fn multilevel(namespaces: &[&[u8]], storage: &'a mut S) -> Self { + pub fn multilevel(storage: &'a mut dyn Storage, namespaces: &[&[u8]]) -> Self { Bucket { - prefix: to_length_prefixed_nested(namespaces), storage, + prefix: to_length_prefixed_nested(namespaces), data: PhantomData, } } @@ -95,15 +94,14 @@ where Box::new(mapped) } - /// update will load the data, perform the specified action, and store the result + /// Loads the data, perform the specified action, and store the result /// in the database. This is shorthand for some common sequences, which may be useful. - /// Note that this only updates *pre-existing* values. If you want to modify possibly - /// non-existent values, please use `may_update` /// - /// This is the least stable of the APIs, and definitely needs some usage - pub fn update(&mut self, key: &[u8], action: A) -> StdResult + /// If the data exists, `action(Some(value))` is called. Otherwise `action(None)` is called. + pub fn update(&mut self, key: &[u8], action: A) -> Result where - A: FnOnce(Option) -> StdResult, + A: FnOnce(Option) -> Result, + E: From, { let input = self.may_load(key)?; let output = action(input)?; @@ -112,32 +110,32 @@ where } } -pub struct ReadonlyBucket<'a, S: ReadonlyStorage, T> +pub struct ReadonlyBucket<'a, T> where T: Serialize + DeserializeOwned, { - storage: &'a S, - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData<&'a T>, + storage: &'a dyn Storage, prefix: Vec, + // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed + data: PhantomData, } -impl<'a, S: ReadonlyStorage, T> ReadonlyBucket<'a, S, T> +impl<'a, T> ReadonlyBucket<'a, T> where T: Serialize + DeserializeOwned, { - pub fn new(namespace: &[u8], storage: &'a S) -> Self { + pub fn new(storage: &'a dyn Storage, namespace: &[u8]) -> Self { ReadonlyBucket { - prefix: to_length_prefixed(namespace), storage, + prefix: to_length_prefixed(namespace), data: PhantomData, } } - pub fn multilevel(namespaces: &[&[u8]], storage: &'a S) -> Self { + pub fn multilevel(storage: &'a dyn Storage, namespaces: &[&[u8]]) -> Self { ReadonlyBucket { - prefix: to_length_prefixed_nested(namespaces), storage, + prefix: to_length_prefixed_nested(namespaces), data: PhantomData, } } @@ -184,7 +182,7 @@ mod test { #[test] fn store_and_load() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); // save data let data = Data { @@ -201,7 +199,7 @@ mod test { #[test] fn remove_works() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); // save data let data = Data { @@ -223,7 +221,7 @@ mod test { #[test] fn readonly_works() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); // save data let data = Data { @@ -232,7 +230,7 @@ mod test { }; bucket.save(b"maria", &data).unwrap(); - let reader = bucket_read::<_, Data>(b"data", &mut store); + let reader = bucket_read::(&mut store, b"data"); // check empty data handling assert!(reader.load(b"john").is_err()); @@ -246,7 +244,7 @@ mod test { #[test] fn buckets_isolated() { let mut store = MockStorage::new(); - let mut bucket1 = bucket::<_, Data>(b"data", &mut store); + let mut bucket1 = bucket::(&mut store, b"data"); // save data let data = Data { @@ -255,7 +253,7 @@ mod test { }; bucket1.save(b"maria", &data).unwrap(); - let mut bucket2 = bucket::<_, Data>(b"dat", &mut store); + let mut bucket2 = bucket::(&mut store, b"dat"); // save data (dat, amaria) vs (data, maria) let data2 = Data { @@ -265,14 +263,14 @@ mod test { bucket2.save(b"amaria", &data2).unwrap(); // load one - let reader = bucket_read::<_, Data>(b"data", &store); + let reader = bucket_read::(&store, b"data"); let loaded = reader.load(b"maria").unwrap(); assert_eq!(data, loaded); // no cross load assert_eq!(None, reader.may_load(b"amaria").unwrap()); // load the other - let reader2 = bucket_read::<_, Data>(b"dat", &store); + let reader2 = bucket_read::(&store, b"dat"); let loaded2 = reader2.load(b"amaria").unwrap(); assert_eq!(data2, loaded2); // no cross load @@ -282,7 +280,7 @@ mod test { #[test] fn update_success() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); // initial data let init = Data { @@ -312,7 +310,7 @@ mod test { #[test] fn update_can_change_variable_from_outer_scope() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); let init = Data { name: "Maria".to_string(), age: 42, @@ -322,7 +320,7 @@ mod test { // show we can capture data from the closure let mut old_age = 0i32; bucket - .update(b"maria", |mayd: Option| { + .update(b"maria", |mayd: Option| -> StdResult<_> { let mut d = mayd.ok_or(StdError::not_found("Data"))?; old_age = d.age; d.age += 1; @@ -335,7 +333,7 @@ mod test { #[test] fn update_fails_on_error() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); // initial data let init = Data { @@ -355,10 +353,56 @@ mod test { assert_eq!(loaded, init); } + #[test] + fn update_supports_custom_error_types() { + #[derive(Debug)] + enum MyError { + Std, + NotFound, + } + + impl From for MyError { + fn from(_original: StdError) -> MyError { + MyError::Std + } + } + + let mut store = MockStorage::new(); + let mut bucket = bucket::(&mut store, b"data"); + + // initial data + let init = Data { + name: "Maria".to_string(), + age: 42, + }; + bucket.save(b"maria", &init).unwrap(); + + // it's my birthday + let res = bucket.update(b"bob", |data| { + if let Some(mut data) = data { + if data.age < 0 { + // Uses Into to convert StdError to MyError + return Err(StdError::generic_err("Current age is negative").into()); + } + if data.age > 10 { + to_vec(&data)?; // Uses From to convert StdError to MyError + } + data.age += 1; + Ok(data) + } else { + return Err(MyError::NotFound); + } + }); + match res.unwrap_err() { + MyError::NotFound { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + } + #[test] fn update_handles_on_no_data() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); let init_value = Data { name: "Maria".to_string(), @@ -383,7 +427,7 @@ mod test { #[cfg(feature = "iterator")] fn range_over_data() { let mut store = MockStorage::new(); - let mut bucket = bucket::<_, Data>(b"data", &mut store); + let mut bucket = bucket::(&mut store, b"data"); let jose = Data { name: "Jose".to_string(), @@ -405,7 +449,7 @@ mod test { assert_eq!(data[1], (b"maria".to_vec(), maria.clone())); // also works for readonly - let read_bucket = bucket_read::<_, Data>(b"data", &store); + let read_bucket = bucket_read::(&store, b"data"); let res_data: StdResult>> = read_bucket.range(None, None, Order::Ascending).collect(); let data = res_data.unwrap(); diff --git a/packages/storage/src/lib.rs b/packages/storage/src/lib.rs index e9973c557..486d07079 100644 --- a/packages/storage/src/lib.rs +++ b/packages/storage/src/lib.rs @@ -4,14 +4,10 @@ mod namespace_helpers; mod prefixed_storage; mod sequence; mod singleton; -mod transactions; mod type_helpers; -mod typed; pub use bucket::{bucket, bucket_read, Bucket, ReadonlyBucket}; pub use length_prefixed::{to_length_prefixed, to_length_prefixed_nested}; pub use prefixed_storage::{prefixed, prefixed_read, PrefixedStorage, ReadonlyPrefixedStorage}; pub use sequence::{currval, nextval, sequence}; pub use singleton::{singleton, singleton_read, ReadonlySingleton, Singleton}; -pub use transactions::{transactional, RepLog, StorageTransaction}; -pub use typed::{typed, typed_read, ReadonlyTypedStorage, TypedStorage}; diff --git a/packages/storage/src/namespace_helpers.rs b/packages/storage/src/namespace_helpers.rs index d05a91fcd..230e130bd 100644 --- a/packages/storage/src/namespace_helpers.rs +++ b/packages/storage/src/namespace_helpers.rs @@ -1,17 +1,17 @@ +use cosmwasm_std::Storage; #[cfg(feature = "iterator")] use cosmwasm_std::{Order, KV}; -use cosmwasm_std::{ReadonlyStorage, Storage}; -pub(crate) fn get_with_prefix( - storage: &S, +pub(crate) fn get_with_prefix( + storage: &dyn Storage, namespace: &[u8], key: &[u8], ) -> Option> { storage.get(&concat(namespace, key)) } -pub(crate) fn set_with_prefix( - storage: &mut S, +pub(crate) fn set_with_prefix( + storage: &mut dyn Storage, namespace: &[u8], key: &[u8], value: &[u8], @@ -19,7 +19,7 @@ pub(crate) fn set_with_prefix( storage.set(&concat(namespace, key), value); } -pub(crate) fn remove_with_prefix(storage: &mut S, namespace: &[u8], key: &[u8]) { +pub(crate) fn remove_with_prefix(storage: &mut dyn Storage, namespace: &[u8], key: &[u8]) { storage.remove(&concat(namespace, key)); } @@ -31,8 +31,8 @@ fn concat(namespace: &[u8], key: &[u8]) -> Vec { } #[cfg(feature = "iterator")] -pub(crate) fn range_with_prefix<'a, S: ReadonlyStorage>( - storage: &'a S, +pub(crate) fn range_with_prefix<'a>( + storage: &'a dyn Storage, namespace: &[u8], start: Option<&[u8]>, end: Option<&[u8]>, diff --git a/packages/storage/src/prefixed_storage.rs b/packages/storage/src/prefixed_storage.rs index faacec90c..650dd69c1 100644 --- a/packages/storage/src/prefixed_storage.rs +++ b/packages/storage/src/prefixed_storage.rs @@ -1,56 +1,63 @@ +use cosmwasm_std::Storage; #[cfg(feature = "iterator")] use cosmwasm_std::{Order, KV}; -use cosmwasm_std::{ReadonlyStorage, Storage}; use crate::length_prefixed::{to_length_prefixed, to_length_prefixed_nested}; #[cfg(feature = "iterator")] use crate::namespace_helpers::range_with_prefix; use crate::namespace_helpers::{get_with_prefix, remove_with_prefix, set_with_prefix}; -// prefixed_read is a helper function for less verbose usage -pub fn prefixed_read<'a, T: ReadonlyStorage>( - prefix: &[u8], - storage: &'a T, -) -> ReadonlyPrefixedStorage<'a, T> { - ReadonlyPrefixedStorage::new(prefix, storage) +/// An alias of PrefixedStorage::new for less verbose usage +pub fn prefixed<'a>(storage: &'a mut dyn Storage, namespace: &[u8]) -> PrefixedStorage<'a> { + PrefixedStorage::new(storage, namespace) } -// prefixed_rw is a helper function for less verbose usage -pub fn prefixed<'a, T: Storage>(prefix: &[u8], storage: &'a mut T) -> PrefixedStorage<'a, T> { - PrefixedStorage::new(prefix, storage) +/// An alias of ReadonlyPrefixedStorage::new for less verbose usage +pub fn prefixed_read<'a>( + storage: &'a dyn Storage, + namespace: &[u8], +) -> ReadonlyPrefixedStorage<'a> { + ReadonlyPrefixedStorage::new(storage, namespace) } -pub struct ReadonlyPrefixedStorage<'a, T: ReadonlyStorage> { +pub struct PrefixedStorage<'a> { + storage: &'a mut dyn Storage, prefix: Vec, - storage: &'a T, } -impl<'a, T: ReadonlyStorage> ReadonlyPrefixedStorage<'a, T> { - pub fn new(namespace: &[u8], storage: &'a T) -> Self { - ReadonlyPrefixedStorage { - prefix: to_length_prefixed(namespace), +impl<'a> PrefixedStorage<'a> { + pub fn new(storage: &'a mut dyn Storage, namespace: &[u8]) -> Self { + PrefixedStorage { storage, + prefix: to_length_prefixed(namespace), } } // Nested namespaces as documented in // https://github.com/webmaster128/key-namespacing#nesting - pub fn multilevel(namespaces: &[&[u8]], storage: &'a T) -> Self { - ReadonlyPrefixedStorage { - prefix: to_length_prefixed_nested(namespaces), + pub fn multilevel(storage: &'a mut dyn Storage, namespaces: &[&[u8]]) -> Self { + PrefixedStorage { storage, + prefix: to_length_prefixed_nested(namespaces), } } -} -impl<'a, T: ReadonlyStorage> ReadonlyStorage for ReadonlyPrefixedStorage<'a, T> { - fn get(&self, key: &[u8]) -> Option> { + pub fn get(&self, key: &[u8]) -> Option> { get_with_prefix(self.storage, &self.prefix, key) } + pub fn set(&mut self, key: &[u8], value: &[u8]) { + set_with_prefix(self.storage, &self.prefix, key, value); + } + + pub fn remove(&mut self, key: &[u8]) { + remove_with_prefix(self.storage, &self.prefix, key); + } + #[cfg(feature = "iterator")] /// range allows iteration over a set of keys, either forwards or backwards - fn range<'b>( + /// uses standard rust range notation, and eg db.range(b"foo"..b"bar") also works reverse + pub fn range<'b>( &'b self, start: Option<&[u8]>, end: Option<&[u8]>, @@ -60,38 +67,35 @@ impl<'a, T: ReadonlyStorage> ReadonlyStorage for ReadonlyPrefixedStorage<'a, T> } } -pub struct PrefixedStorage<'a, T: Storage> { +pub struct ReadonlyPrefixedStorage<'a> { + storage: &'a dyn Storage, prefix: Vec, - storage: &'a mut T, } -impl<'a, T: Storage> PrefixedStorage<'a, T> { - pub fn new(namespace: &[u8], storage: &'a mut T) -> Self { - PrefixedStorage { - prefix: to_length_prefixed(namespace), +impl<'a> ReadonlyPrefixedStorage<'a> { + pub fn new(storage: &'a dyn Storage, namespace: &[u8]) -> Self { + ReadonlyPrefixedStorage { storage, + prefix: to_length_prefixed(namespace), } } // Nested namespaces as documented in // https://github.com/webmaster128/key-namespacing#nesting - pub fn multilevel(namespaces: &[&[u8]], storage: &'a mut T) -> Self { - PrefixedStorage { - prefix: to_length_prefixed_nested(namespaces), + pub fn multilevel(storage: &'a dyn Storage, namespaces: &[&[u8]]) -> Self { + ReadonlyPrefixedStorage { storage, + prefix: to_length_prefixed_nested(namespaces), } } -} -impl<'a, T: Storage> ReadonlyStorage for PrefixedStorage<'a, T> { - fn get(&self, key: &[u8]) -> Option> { + pub fn get(&self, key: &[u8]) -> Option> { get_with_prefix(self.storage, &self.prefix, key) } #[cfg(feature = "iterator")] /// range allows iteration over a set of keys, either forwards or backwards - /// uses standard rust range notation, and eg db.range(b"foo"..b"bar") also works reverse - fn range<'b>( + pub fn range<'b>( &'b self, start: Option<&[u8]>, end: Option<&[u8]>, @@ -101,62 +105,66 @@ impl<'a, T: Storage> ReadonlyStorage for PrefixedStorage<'a, T> { } } -impl<'a, T: Storage> Storage for PrefixedStorage<'a, T> { - fn set(&mut self, key: &[u8], value: &[u8]) { - set_with_prefix(self.storage, &self.prefix, key, value); - } - - fn remove(&mut self, key: &[u8]) { - remove_with_prefix(self.storage, &self.prefix, key); - } -} - #[cfg(test)] mod test { use super::*; use cosmwasm_std::testing::MockStorage; #[test] - fn prefix_safe() { + fn prefixed_storage_set_and_get() { let mut storage = MockStorage::new(); - // we use a block scope here to release the &mut before we use it in the next storage - let mut foo = PrefixedStorage::new(b"foo", &mut storage); + // set + let mut foo = PrefixedStorage::new(&mut storage, b"foo"); foo.set(b"bar", b"gotcha"); - assert_eq!(foo.get(b"bar"), Some(b"gotcha".to_vec())); + assert_eq!(storage.get(b"\x00\x03foobar").unwrap(), b"gotcha".to_vec()); - // try readonly correctly - let rfoo = ReadonlyPrefixedStorage::new(b"foo", &storage); - assert_eq!(rfoo.get(b"bar"), Some(b"gotcha".to_vec())); + // get + let foo = PrefixedStorage::new(&mut storage, b"foo"); + assert_eq!(foo.get(b"bar"), Some(b"gotcha".to_vec())); + assert_eq!(foo.get(b"elsewhere"), None); + } - // no collisions with other prefixes - let fo = ReadonlyPrefixedStorage::new(b"fo", &storage); - assert_eq!(fo.get(b"obar"), None); + #[test] + fn prefixed_storage_multilevel_set_and_get() { + let mut storage = MockStorage::new(); - // Note: explicit scoping is not required, but you must not refer to `foo` anytime after you - // initialize a different PrefixedStorage. Uncomment this to see errors: - // assert_eq!(Some(b"gotcha".to_vec()), foo.get(b"bar")); + // set + let mut bar = PrefixedStorage::multilevel(&mut storage, &[b"foo", b"bar"]); + bar.set(b"baz", b"winner"); + assert_eq!( + storage.get(b"\x00\x03foo\x00\x03barbaz").unwrap(), + b"winner".to_vec() + ); + + // get + let bar = PrefixedStorage::multilevel(&mut storage, &[b"foo", b"bar"]); + assert_eq!(bar.get(b"baz"), Some(b"winner".to_vec())); + assert_eq!(bar.get(b"elsewhere"), None); } #[test] - fn multi_level() { + fn readonly_prefixed_storage_get() { let mut storage = MockStorage::new(); + storage.set(b"\x00\x03foobar", b"gotcha"); - // set with nested - let mut foo = PrefixedStorage::new(b"foo", &mut storage); - let mut bar = PrefixedStorage::new(b"bar", &mut foo); - bar.set(b"baz", b"winner"); + // try readonly correctly + let foo = ReadonlyPrefixedStorage::new(&storage, b"foo"); + assert_eq!(foo.get(b"bar"), Some(b"gotcha".to_vec())); + assert_eq!(foo.get(b"elsewhere"), None); - // we can nest them the same encoding with one operation - let loader = ReadonlyPrefixedStorage::multilevel(&[b"foo", b"bar"], &storage); - assert_eq!(loader.get(b"baz"), Some(b"winner".to_vec())); + // no collisions with other prefixes + let fo = ReadonlyPrefixedStorage::new(&storage, b"fo"); + assert_eq!(fo.get(b"obar"), None); + } - // set with multilevel - let mut foobar = PrefixedStorage::multilevel(&[b"foo", b"bar"], &mut storage); - foobar.set(b"second", b"time"); + #[test] + fn readonly_prefixed_storage_multilevel_get() { + let mut storage = MockStorage::new(); + storage.set(b"\x00\x03foo\x00\x03barbaz", b"winner"); - let a = ReadonlyPrefixedStorage::new(b"foo", &storage); - let b = ReadonlyPrefixedStorage::new(b"bar", &a); - assert_eq!(b.get(b"second"), Some(b"time".to_vec())); + let bar = ReadonlyPrefixedStorage::multilevel(&storage, &[b"foo", b"bar"]); + assert_eq!(bar.get(b"baz"), Some(b"winner".to_vec())); + assert_eq!(bar.get(b"elsewhere"), None); } } diff --git a/packages/storage/src/sequence.rs b/packages/storage/src/sequence.rs index 505dbe90e..5929d02f9 100644 --- a/packages/storage/src/sequence.rs +++ b/packages/storage/src/sequence.rs @@ -3,19 +3,19 @@ use cosmwasm_std::{StdResult, Storage}; use crate::Singleton; /// Sequence creates a custom Singleton to hold an empty sequence -pub fn sequence<'a, S: Storage>(storage: &'a mut S, key: &[u8]) -> Singleton<'a, S, u64> { +pub fn sequence<'a>(storage: &'a mut dyn Storage, key: &[u8]) -> Singleton<'a, u64> { Singleton::new(storage, key) } /// currval returns the last value returned by nextval. If the sequence has never been used, /// then it will return 0. -pub fn currval(seq: &Singleton) -> StdResult { +pub fn currval(seq: &Singleton) -> StdResult { Ok(seq.may_load()?.unwrap_or_default()) } /// nextval increments the counter by 1 and returns the new value. /// On the first time it is called (no sequence info in db) it will return 1. -pub fn nextval(seq: &mut Singleton) -> StdResult { +pub fn nextval(seq: &mut Singleton) -> StdResult { let val = currval(&seq)? + 1; seq.save(&val)?; Ok(val) diff --git a/packages/storage/src/singleton.rs b/packages/storage/src/singleton.rs index b900cf135..83be97c76 100644 --- a/packages/storage/src/singleton.rs +++ b/packages/storage/src/singleton.rs @@ -1,24 +1,21 @@ use serde::{de::DeserializeOwned, ser::Serialize}; use std::marker::PhantomData; -use cosmwasm_std::{to_vec, ReadonlyStorage, StdResult, Storage}; +use cosmwasm_std::{to_vec, StdError, StdResult, Storage}; use crate::length_prefixed::to_length_prefixed; use crate::type_helpers::{may_deserialize, must_deserialize}; -// singleton is a helper function for less verbose usage -pub fn singleton<'a, S: Storage, T>(storage: &'a mut S, key: &[u8]) -> Singleton<'a, S, T> +/// An alias of Singleton::new for less verbose usage +pub fn singleton<'a, T>(storage: &'a mut dyn Storage, key: &[u8]) -> Singleton<'a, T> where T: Serialize + DeserializeOwned, { Singleton::new(storage, key) } -// singleton_read is a helper function for less verbose usage -pub fn singleton_read<'a, S: ReadonlyStorage, T>( - storage: &'a S, - key: &[u8], -) -> ReadonlySingleton<'a, S, T> +/// An alias of ReadonlySingleton::new for less verbose usage +pub fn singleton_read<'a, T>(storage: &'a dyn Storage, key: &[u8]) -> ReadonlySingleton<'a, T> where T: Serialize + DeserializeOwned, { @@ -29,21 +26,21 @@ where /// work on a single storage key. It performs the to_length_prefixed transformation /// on the given name to ensure no collisions, and then provides the standard /// TypedStorage accessors, without requiring a key (which is defined in the constructor) -pub struct Singleton<'a, S: Storage, T> +pub struct Singleton<'a, T> where T: Serialize + DeserializeOwned, { - storage: &'a mut S, + storage: &'a mut dyn Storage, key: Vec, // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData<&'a T>, + data: PhantomData, } -impl<'a, S: Storage, T> Singleton<'a, S, T> +impl<'a, T> Singleton<'a, T> where T: Serialize + DeserializeOwned, { - pub fn new(storage: &'a mut S, key: &[u8]) -> Self { + pub fn new(storage: &'a mut dyn Storage, key: &[u8]) -> Self { Singleton { storage, key: to_length_prefixed(key), @@ -78,9 +75,10 @@ where /// in the database. This is shorthand for some common sequences, which may be useful /// /// This is the least stable of the APIs, and definitely needs some usage - pub fn update(&mut self, action: A) -> StdResult + pub fn update(&mut self, action: A) -> Result where - A: FnOnce(T) -> StdResult, + A: FnOnce(T) -> Result, + E: From, { let input = self.load()?; let output = action(input)?; @@ -89,23 +87,23 @@ where } } -/// ReadonlySingleton only requires a ReadonlyStorage and exposes only the +/// ReadonlySingleton only requires a Storage and exposes only the /// methods of Singleton that don't modify state. -pub struct ReadonlySingleton<'a, S: ReadonlyStorage, T> +pub struct ReadonlySingleton<'a, T> where T: Serialize + DeserializeOwned, { - storage: &'a S, + storage: &'a dyn Storage, key: Vec, // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData<&'a T>, + data: PhantomData, } -impl<'a, S: ReadonlyStorage, T> ReadonlySingleton<'a, S, T> +impl<'a, T> ReadonlySingleton<'a, T> where T: Serialize + DeserializeOwned, { - pub fn new(storage: &'a S, key: &[u8]) -> Self { + pub fn new(storage: &'a dyn Storage, key: &[u8]) -> Self { ReadonlySingleton { storage, key: to_length_prefixed(key), @@ -144,7 +142,7 @@ mod test { #[test] fn save_and_load() { let mut store = MockStorage::new(); - let mut single = Singleton::<_, Config>::new(&mut store, b"config"); + let mut single = Singleton::::new(&mut store, b"config"); assert!(single.load().is_err()); assert_eq!(single.may_load().unwrap(), None); @@ -161,7 +159,7 @@ mod test { #[test] fn remove_works() { let mut store = MockStorage::new(); - let mut single = Singleton::<_, Config>::new(&mut store, b"config"); + let mut single = Singleton::::new(&mut store, b"config"); // store data let cfg = Config { @@ -183,7 +181,7 @@ mod test { #[test] fn isolated_reads() { let mut store = MockStorage::new(); - let mut writer = singleton::<_, Config>(&mut store, b"config"); + let mut writer = singleton::(&mut store, b"config"); let cfg = Config { owner: "admin".to_string(), @@ -191,17 +189,17 @@ mod test { }; writer.save(&cfg).unwrap(); - let reader = singleton_read::<_, Config>(&store, b"config"); + let reader = singleton_read::(&store, b"config"); assert_eq!(cfg, reader.load().unwrap()); - let other_reader = singleton_read::<_, Config>(&store, b"config2"); + let other_reader = singleton_read::(&store, b"config2"); assert_eq!(other_reader.may_load().unwrap(), None); } #[test] fn update_success() { let mut store = MockStorage::new(); - let mut writer = singleton::<_, Config>(&mut store, b"config"); + let mut writer = singleton::(&mut store, b"config"); let cfg = Config { owner: "admin".to_string(), @@ -209,7 +207,7 @@ mod test { }; writer.save(&cfg).unwrap(); - let output = writer.update(|mut c| { + let output = writer.update(|mut c| -> StdResult<_> { c.max_tokens *= 2; Ok(c) }); @@ -224,7 +222,7 @@ mod test { #[test] fn update_can_change_variable_from_outer_scope() { let mut store = MockStorage::new(); - let mut writer = singleton::<_, Config>(&mut store, b"config"); + let mut writer = singleton::(&mut store, b"config"); let cfg = Config { owner: "admin".to_string(), max_tokens: 1234, @@ -233,7 +231,7 @@ mod test { let mut old_max_tokens = 0i32; writer - .update(|mut c| { + .update(|mut c| -> StdResult<_> { old_max_tokens = c.max_tokens; c.max_tokens *= 2; Ok(c) @@ -243,9 +241,9 @@ mod test { } #[test] - fn update_failure() { + fn update_does_not_change_data_on_error() { let mut store = MockStorage::new(); - let mut writer = singleton::<_, Config>(&mut store, b"config"); + let mut writer = singleton::(&mut store, b"config"); let cfg = Config { owner: "admin".to_string(), @@ -253,10 +251,53 @@ mod test { }; writer.save(&cfg).unwrap(); - let output = writer.update(&|_c| Err(StdError::unauthorized())); - match output { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Unexpected output: {:?}", output), + let output = writer.update(&|_c| Err(StdError::underflow(4, 7))); + match output.unwrap_err() { + StdError::Underflow { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + assert_eq!(writer.load().unwrap(), cfg); + } + + #[test] + fn update_supports_custom_errors() { + #[derive(Debug)] + enum MyError { + Std(StdError), + Foo, + } + + impl From for MyError { + fn from(original: StdError) -> MyError { + MyError::Std(original) + } + } + + let mut store = MockStorage::new(); + let mut writer = singleton::(&mut store, b"config"); + + let cfg = Config { + owner: "admin".to_string(), + max_tokens: 1234, + }; + writer.save(&cfg).unwrap(); + + let res = writer.update(|mut c| { + if c.max_tokens > 5000 { + return Err(MyError::Foo); + } + if c.max_tokens > 20 { + return Err(StdError::generic_err("broken stuff").into()); // Uses Into to convert StdError to MyError + } + if c.max_tokens > 10 { + to_vec(&c)?; // Uses From to convert StdError to MyError + } + c.max_tokens += 20; + Ok(c) + }); + match res.unwrap_err() { + MyError::Std(StdError::GenericErr { .. }) => {} + err => panic!("Unexpected error: {:?}", err), } assert_eq!(writer.load().unwrap(), cfg); } diff --git a/packages/storage/src/transactions.rs b/packages/storage/src/transactions.rs deleted file mode 100644 index 2be91b5c9..000000000 --- a/packages/storage/src/transactions.rs +++ /dev/null @@ -1,565 +0,0 @@ -#[cfg(feature = "iterator")] -use std::cmp::Ordering; -use std::collections::BTreeMap; -#[cfg(feature = "iterator")] -use std::iter; -#[cfg(feature = "iterator")] -use std::iter::Peekable; -#[cfg(feature = "iterator")] -use std::ops::{Bound, RangeBounds}; - -#[cfg(feature = "iterator")] -use cosmwasm_std::{Order, KV}; -use cosmwasm_std::{ReadonlyStorage, StdResult, Storage}; - -#[cfg(feature = "iterator")] -/// The BTreeMap specific key-value pair reference type, as returned by BTreeMap, T>::range. -/// This is internal as it can change any time if the map implementation is swapped out. -type BTreeMapPairRef<'a, T = Vec> = (&'a Vec, &'a T); - -pub struct StorageTransaction<'a, S: ReadonlyStorage> { - /// read-only access to backing storage - storage: &'a S, - /// these are local changes not flushed to backing storage - local_state: BTreeMap, Delta>, - /// a log of local changes not yet flushed to backing storage - rep_log: RepLog, -} - -impl<'a, S: ReadonlyStorage> StorageTransaction<'a, S> { - pub fn new(storage: &'a S) -> Self { - StorageTransaction { - storage, - local_state: BTreeMap::new(), - rep_log: RepLog::new(), - } - } - - /// prepares this transaction to be committed to storage - pub fn prepare(self) -> RepLog { - self.rep_log - } - - /// rollback will consume the checkpoint and drop all changes (no really needed, going out of scope does the same, but nice for clarity) - pub fn rollback(self) {} -} - -impl<'a, S: ReadonlyStorage> ReadonlyStorage for StorageTransaction<'a, S> { - fn get(&self, key: &[u8]) -> Option> { - match self.local_state.get(key) { - Some(val) => match val { - Delta::Set { value } => Some(value.clone()), - Delta::Delete {} => None, - }, - None => self.storage.get(key), - } - } - - #[cfg(feature = "iterator")] - /// range allows iteration over a set of keys, either forwards or backwards - /// uses standard rust range notation, and eg db.range(b"foo"..b"bar") also works reverse - fn range<'b>( - &'b self, - start: Option<&[u8]>, - end: Option<&[u8]>, - order: Order, - ) -> Box + 'b> { - let bounds = range_bounds(start, end); - - // BTreeMap.range panics if range is start > end. - // However, this cases represent just empty range and we treat it as such. - let local: Box>> = - match (bounds.start_bound(), bounds.end_bound()) { - (Bound::Included(start), Bound::Excluded(end)) if start > end => { - Box::new(iter::empty()) - } - _ => { - let local_raw = self.local_state.range(bounds); - match order { - Order::Ascending => Box::new(local_raw), - Order::Descending => Box::new(local_raw.rev()), - } - } - }; - - let base = self.storage.range(start, end, order); - let merged = MergeOverlay::new(local, base, order); - Box::new(merged) - } -} - -impl<'a, S: ReadonlyStorage> Storage for StorageTransaction<'a, S> { - fn set(&mut self, key: &[u8], value: &[u8]) { - let op = Op::Set { - key: key.to_vec(), - value: value.to_vec(), - }; - self.local_state.insert(key.to_vec(), op.to_delta()); - self.rep_log.append(op); - } - - fn remove(&mut self, key: &[u8]) { - let op = Op::Delete { key: key.to_vec() }; - self.local_state.insert(key.to_vec(), op.to_delta()); - self.rep_log.append(op); - } -} - -pub struct RepLog { - /// this is a list of changes to be written to backing storage upon commit - ops_log: Vec, -} - -impl RepLog { - fn new() -> Self { - RepLog { ops_log: vec![] } - } - - /// appends an op to the list of changes to be applied upon commit - fn append(&mut self, op: Op) { - self.ops_log.push(op); - } - - /// applies the stored list of `Op`s to the provided `Storage` - pub fn commit(self, storage: &mut S) { - for op in self.ops_log { - op.apply(storage); - } - } -} - -/// Op is the user operation, which can be stored in the RepLog. -/// Currently Set or Delete. -enum Op { - /// represents the `Set` operation for setting a key-value pair in storage - Set { - key: Vec, - value: Vec, - }, - Delete { - key: Vec, - }, -} - -impl Op { - /// applies this `Op` to the provided storage - pub fn apply(&self, storage: &mut S) { - match self { - Op::Set { key, value } => storage.set(&key, &value), - Op::Delete { key } => storage.remove(&key), - } - } - - /// converts the Op to a delta, which can be stored in a local cache - pub fn to_delta(&self) -> Delta { - match self { - Op::Set { value, .. } => Delta::Set { - value: value.clone(), - }, - Op::Delete { .. } => Delta::Delete {}, - } - } -} - -/// Delta is the changes, stored in the local transaction cache. -/// This is either Set{value} or Delete{}. Note that this is the "value" -/// part of a BTree, so the Key (from the Op) is stored separately. -enum Delta { - Set { value: Vec }, - Delete {}, -} - -#[cfg(feature = "iterator")] -struct MergeOverlay<'a, L, R> -where - L: Iterator>, - R: Iterator, -{ - left: Peekable, - right: Peekable, - order: Order, -} - -#[cfg(feature = "iterator")] -impl<'a, L, R> MergeOverlay<'a, L, R> -where - L: Iterator>, - R: Iterator, -{ - fn new(left: L, right: R, order: Order) -> Self { - MergeOverlay { - left: left.peekable(), - right: right.peekable(), - order, - } - } - - fn pick_match(&mut self, lkey: Vec, rkey: Vec) -> Option { - // compare keys - result is such that Ordering::Less => return left side - let order = match self.order { - Order::Ascending => lkey.cmp(&rkey), - Order::Descending => rkey.cmp(&lkey), - }; - - // left must be translated and filtered before return, not so with right - match order { - Ordering::Less => self.take_left(), - Ordering::Equal => { - // - let _ = self.right.next(); - self.take_left() - } - Ordering::Greater => self.right.next(), - } - } - - /// take_left must only be called when we know self.left.next() will return Some - fn take_left(&mut self) -> Option { - let (lkey, lval) = self.left.next().unwrap(); - match lval { - Delta::Set { value } => Some((lkey.clone(), value.clone())), - Delta::Delete {} => self.next(), - } - } -} - -#[cfg(feature = "iterator")] -impl<'a, L, R> Iterator for MergeOverlay<'a, L, R> -where - L: Iterator>, - R: Iterator, -{ - type Item = KV; - - fn next(&mut self) -> Option { - let (left, right) = (self.left.peek(), self.right.peek()); - match (left, right) { - (Some(litem), Some(ritem)) => { - let (lkey, _) = litem; - let (rkey, _) = ritem; - - // we just use cloned keys to avoid double mutable references - // (we must release the return value from peek, before beginning to call next or other mut methods - let (l, r) = (lkey.to_vec(), rkey.to_vec()); - self.pick_match(l, r) - } - (Some(_), None) => self.take_left(), - (None, Some(_)) => self.right.next(), - (None, None) => None, - } - } -} - -pub fn transactional(storage: &mut S, callback: C) -> StdResult -where - S: Storage, - C: FnOnce(&mut StorageTransaction) -> StdResult, -{ - let mut stx = StorageTransaction::new(storage); - let res = callback(&mut stx)?; - stx.prepare().commit(storage); - Ok(res) -} - -#[cfg(feature = "iterator")] -fn range_bounds(start: Option<&[u8]>, end: Option<&[u8]>) -> impl RangeBounds> { - ( - start.map_or(Bound::Unbounded, |x| Bound::Included(x.to_vec())), - end.map_or(Bound::Unbounded, |x| Bound::Excluded(x.to_vec())), - ) -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::{MemoryStorage, StdError}; - - #[cfg(feature = "iterator")] - // iterator_test_suite takes a storage, adds data and runs iterator tests - // the storage must previously have exactly one key: "foo" = "bar" - // (this allows us to test StorageTransaction and other wrapped storage better) - fn iterator_test_suite(store: &mut S) { - // ensure we had previously set "foo" = "bar" - assert_eq!(store.get(b"foo"), Some(b"bar".to_vec())); - assert_eq!(store.range(None, None, Order::Ascending).count(), 1); - - // setup - add some data, and delete part of it as well - store.set(b"ant", b"hill"); - store.set(b"ze", b"bra"); - - // noise that should be ignored - store.set(b"bye", b"bye"); - store.remove(b"bye"); - - // unbounded - { - let iter = store.range(None, None, Order::Ascending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"ant".to_vec(), b"hill".to_vec()), - (b"foo".to_vec(), b"bar".to_vec()), - (b"ze".to_vec(), b"bra".to_vec()), - ] - ); - } - - // unbounded (descending) - { - let iter = store.range(None, None, Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"ze".to_vec(), b"bra".to_vec()), - (b"foo".to_vec(), b"bar".to_vec()), - (b"ant".to_vec(), b"hill".to_vec()), - ] - ); - } - - // bounded - { - let iter = store.range(Some(b"f"), Some(b"n"), Order::Ascending); - let elements: Vec = iter.collect(); - assert_eq!(elements, vec![(b"foo".to_vec(), b"bar".to_vec())]); - } - - // bounded (descending) - { - let iter = store.range(Some(b"air"), Some(b"loop"), Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"foo".to_vec(), b"bar".to_vec()), - (b"ant".to_vec(), b"hill".to_vec()), - ] - ); - } - - // bounded empty [a, a) - { - let iter = store.range(Some(b"foo"), Some(b"foo"), Order::Ascending); - let elements: Vec = iter.collect(); - assert_eq!(elements, vec![]); - } - - // bounded empty [a, a) (descending) - { - let iter = store.range(Some(b"foo"), Some(b"foo"), Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!(elements, vec![]); - } - - // bounded empty [a, b) with b < a - { - let iter = store.range(Some(b"z"), Some(b"a"), Order::Ascending); - let elements: Vec = iter.collect(); - assert_eq!(elements, vec![]); - } - - // bounded empty [a, b) with b < a (descending) - { - let iter = store.range(Some(b"z"), Some(b"a"), Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!(elements, vec![]); - } - - // right unbounded - { - let iter = store.range(Some(b"f"), None, Order::Ascending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"foo".to_vec(), b"bar".to_vec()), - (b"ze".to_vec(), b"bra".to_vec()), - ] - ); - } - - // right unbounded (descending) - { - let iter = store.range(Some(b"f"), None, Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"ze".to_vec(), b"bra".to_vec()), - (b"foo".to_vec(), b"bar".to_vec()), - ] - ); - } - - // left unbounded - { - let iter = store.range(None, Some(b"f"), Order::Ascending); - let elements: Vec = iter.collect(); - assert_eq!(elements, vec![(b"ant".to_vec(), b"hill".to_vec()),]); - } - - // left unbounded (descending) - { - let iter = store.range(None, Some(b"no"), Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"foo".to_vec(), b"bar".to_vec()), - (b"ant".to_vec(), b"hill".to_vec()), - ] - ); - } - } - - #[test] - fn delete_local() { - let mut base = MemoryStorage::new(); - let mut check = StorageTransaction::new(&base); - check.set(b"foo", b"bar"); - check.set(b"food", b"bank"); - check.remove(b"foo"); - - assert_eq!(check.get(b"foo"), None); - assert_eq!(check.get(b"food"), Some(b"bank".to_vec())); - - // now commit to base and query there - check.prepare().commit(&mut base); - assert_eq!(base.get(b"foo"), None); - assert_eq!(base.get(b"food"), Some(b"bank".to_vec())); - } - - #[test] - fn delete_from_base() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - let mut check = StorageTransaction::new(&base); - check.set(b"food", b"bank"); - check.remove(b"foo"); - - assert_eq!(check.get(b"foo"), None); - assert_eq!(check.get(b"food"), Some(b"bank".to_vec())); - - // now commit to base and query there - check.prepare().commit(&mut base); - assert_eq!(base.get(b"foo"), None); - assert_eq!(base.get(b"food"), Some(b"bank".to_vec())); - } - - #[test] - #[cfg(feature = "iterator")] - fn storage_transaction_iterator_empty_base() { - let base = MemoryStorage::new(); - let mut check = StorageTransaction::new(&base); - check.set(b"foo", b"bar"); - iterator_test_suite(&mut check); - } - - #[test] - #[cfg(feature = "iterator")] - fn storage_transaction_iterator_with_base_data() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - let mut check = StorageTransaction::new(&base); - iterator_test_suite(&mut check); - } - - #[test] - #[cfg(feature = "iterator")] - fn storage_transaction_iterator_removed_items_from_base() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - base.set(b"food", b"bank"); - let mut check = StorageTransaction::new(&base); - check.remove(b"food"); - iterator_test_suite(&mut check); - } - - #[test] - fn commit_writes_through() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - - let mut check = StorageTransaction::new(&base); - assert_eq!(check.get(b"foo"), Some(b"bar".to_vec())); - check.set(b"subtx", b"works"); - check.prepare().commit(&mut base); - - assert_eq!(base.get(b"subtx"), Some(b"works".to_vec())); - } - - #[test] - fn storage_remains_readable() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - - let mut stxn1 = StorageTransaction::new(&base); - - assert_eq!(stxn1.get(b"foo"), Some(b"bar".to_vec())); - - stxn1.set(b"subtx", b"works"); - assert_eq!(stxn1.get(b"subtx"), Some(b"works".to_vec())); - - // Can still read from base, txn is not yet committed - assert_eq!(base.get(b"subtx"), None); - - stxn1.prepare().commit(&mut base); - assert_eq!(base.get(b"subtx"), Some(b"works".to_vec())); - } - - #[test] - fn rollback_has_no_effect() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - - let mut check = StorageTransaction::new(&base); - assert_eq!(check.get(b"foo"), Some(b"bar".to_vec())); - check.set(b"subtx", b"works"); - check.rollback(); - - assert_eq!(base.get(b"subtx"), None); - } - - #[test] - fn ignore_same_as_rollback() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - - let mut check = StorageTransaction::new(&base); - assert_eq!(check.get(b"foo"), Some(b"bar".to_vec())); - check.set(b"subtx", b"works"); - - assert_eq!(base.get(b"subtx"), None); - } - - #[test] - fn transactional_works() { - let mut base = MemoryStorage::new(); - base.set(b"foo", b"bar"); - - // writes on success - let res: StdResult = transactional(&mut base, |store| { - // ensure we can read from the backing store - assert_eq!(store.get(b"foo"), Some(b"bar".to_vec())); - // we write in the Ok case - store.set(b"good", b"one"); - Ok(5) - }); - assert_eq!(res.unwrap(), 5); - assert_eq!(base.get(b"good"), Some(b"one".to_vec())); - - // rejects on error - let res: StdResult = transactional(&mut base, |store| { - // ensure we can read from the backing store - assert_eq!(store.get(b"foo"), Some(b"bar".to_vec())); - assert_eq!(store.get(b"good"), Some(b"one".to_vec())); - // we write in the Error case - store.set(b"bad", b"value"); - Err(StdError::unauthorized()) - }); - assert!(res.is_err()); - assert_eq!(base.get(b"bad"), None); - } -} diff --git a/packages/storage/src/typed.rs b/packages/storage/src/typed.rs deleted file mode 100644 index 0ee6665d7..000000000 --- a/packages/storage/src/typed.rs +++ /dev/null @@ -1,330 +0,0 @@ -use serde::{de::DeserializeOwned, ser::Serialize}; -use std::marker::PhantomData; - -use cosmwasm_std::{to_vec, ReadonlyStorage, StdResult, Storage}; -#[cfg(feature = "iterator")] -use cosmwasm_std::{Order, KV}; - -#[cfg(feature = "iterator")] -use crate::type_helpers::deserialize_kv; -use crate::type_helpers::{may_deserialize, must_deserialize}; - -pub fn typed(storage: &mut S) -> TypedStorage -where - T: Serialize + DeserializeOwned, -{ - TypedStorage::new(storage) -} - -pub fn typed_read(storage: &S) -> ReadonlyTypedStorage -where - T: Serialize + DeserializeOwned, -{ - ReadonlyTypedStorage::new(storage) -} - -pub struct TypedStorage<'a, S: Storage, T> -where - T: Serialize + DeserializeOwned, -{ - storage: &'a mut S, - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData<&'a T>, -} - -impl<'a, S: Storage, T> TypedStorage<'a, S, T> -where - T: Serialize + DeserializeOwned, -{ - pub fn new(storage: &'a mut S) -> Self { - TypedStorage { - storage, - data: PhantomData, - } - } - - /// save will serialize the model and store, returns an error on serialization issues - pub fn save(&mut self, key: &[u8], data: &T) -> StdResult<()> { - self.storage.set(key, &to_vec(data)?); - Ok(()) - } - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, key: &[u8]) -> StdResult { - let value = self.storage.get(key); - must_deserialize(&value) - } - - /// may_load will parse the data stored at the key if present, returns Ok(None) if no data there. - /// returns an error on issues parsing - pub fn may_load(&self, key: &[u8]) -> StdResult> { - let value = self.storage.get(key); - may_deserialize(&value) - } - - #[cfg(feature = "iterator")] - pub fn range<'b>( - &'b self, - start: Option<&[u8]>, - end: Option<&[u8]>, - order: Order, - ) -> Box>> + 'b> { - let mapped = self - .storage - .range(start, end, order) - .map(deserialize_kv::); - Box::new(mapped) - } - - /// update will load the data, perform the specified action, and store the result - /// in the database. This is shorthand for some common sequences, which may be useful - /// - /// This is the least stable of the APIs, and definitely needs some usage - pub fn update(&mut self, key: &[u8], action: A) -> StdResult - where - A: FnOnce(Option) -> StdResult, - { - let input = self.may_load(key)?; - let output = action(input)?; - self.save(key, &output)?; - Ok(output) - } -} - -pub struct ReadonlyTypedStorage<'a, S: ReadonlyStorage, T> -where - T: Serialize + DeserializeOwned, -{ - storage: &'a S, - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData<&'a T>, -} - -impl<'a, S: ReadonlyStorage, T> ReadonlyTypedStorage<'a, S, T> -where - T: Serialize + DeserializeOwned, -{ - pub fn new(storage: &'a S) -> Self { - ReadonlyTypedStorage { - storage, - data: PhantomData, - } - } - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, key: &[u8]) -> StdResult { - let value = self.storage.get(key); - must_deserialize(&value) - } - - /// may_load will parse the data stored at the key if present, returns Ok(None) if no data there. - /// returns an error on issues parsing - pub fn may_load(&self, key: &[u8]) -> StdResult> { - let value = self.storage.get(key); - may_deserialize(&value) - } - - #[cfg(feature = "iterator")] - pub fn range<'b>( - &'b self, - start: Option<&[u8]>, - end: Option<&[u8]>, - order: Order, - ) -> Box>> + 'b> { - let mapped = self - .storage - .range(start, end, order) - .map(deserialize_kv::); - Box::new(mapped) - } -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::testing::MockStorage; - use cosmwasm_std::StdError; - use serde::{Deserialize, Serialize}; - - use crate::prefixed; - - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] - struct Data { - pub name: String, - pub age: i32, - } - - #[test] - fn store_and_load() { - let mut store = MockStorage::new(); - let mut bucket = TypedStorage::<_, Data>::new(&mut store); - - // check empty data handling - assert!(bucket.load(b"maria").is_err()); - assert_eq!(bucket.may_load(b"maria").unwrap(), None); - - // save data - let data = Data { - name: "Maria".to_string(), - age: 42, - }; - bucket.save(b"maria", &data).unwrap(); - - // load it properly - let loaded = bucket.load(b"maria").unwrap(); - assert_eq!(data, loaded); - } - - #[test] - fn store_with_prefix() { - let mut store = MockStorage::new(); - let mut space = prefixed(b"data", &mut store); - let mut bucket = typed::<_, Data>(&mut space); - - // save data - let data = Data { - name: "Maria".to_string(), - age: 42, - }; - bucket.save(b"maria", &data).unwrap(); - - // load it properly - let loaded = bucket.load(b"maria").unwrap(); - assert_eq!(data, loaded); - } - - #[test] - fn readonly_works() { - let mut store = MockStorage::new(); - let mut bucket = typed::<_, Data>(&mut store); - - // save data - let data = Data { - name: "Maria".to_string(), - age: 42, - }; - bucket.save(b"maria", &data).unwrap(); - - let reader = typed_read::<_, Data>(&mut store); - - // check empty data handling - assert!(reader.load(b"john").is_err()); - assert_eq!(reader.may_load(b"john").unwrap(), None); - - // load it properly - let loaded = reader.load(b"maria").unwrap(); - assert_eq!(data, loaded); - } - - #[test] - fn update_success() { - let mut store = MockStorage::new(); - let mut bucket = typed::<_, Data>(&mut store); - - // initial data - let init = Data { - name: "Maria".to_string(), - age: 42, - }; - bucket.save(b"maria", &init).unwrap(); - - // it's my birthday (fail if no data) - let birthday = |mayd: Option| -> StdResult { - let mut d = mayd.ok_or(StdError::not_found("Data"))?; - d.age += 1; - Ok(d) - }; - let output = bucket.update(b"maria", &birthday).unwrap(); - let expected = Data { - name: "Maria".to_string(), - age: 43, - }; - assert_eq!(output, expected); - - // load it properly - let loaded = bucket.load(b"maria").unwrap(); - assert_eq!(loaded, expected); - } - - #[test] - fn update_fails_on_error() { - let mut store = MockStorage::new(); - let mut bucket = typed::<_, Data>(&mut store); - - // initial data - let init = Data { - name: "Maria".to_string(), - age: 42, - }; - bucket.save(b"maria", &init).unwrap(); - - // it's my birthday - let output = bucket.update(b"maria", |_d| { - Err(StdError::generic_err("cuz i feel like it")) - }); - assert!(output.is_err()); - - // load it properly - let loaded = bucket.load(b"maria").unwrap(); - assert_eq!(loaded, init); - } - - #[test] - fn update_handles_on_no_data() { - let mut store = MockStorage::new(); - let mut bucket = typed::<_, Data>(&mut store); - - let init_value = Data { - name: "Maria".to_string(), - age: 42, - }; - - // it's my birthday - let output = bucket - .update(b"maria", |d| match d { - Some(_) => Err(StdError::generic_err("Ensure this was empty")), - None => Ok(init_value.clone()), - }) - .unwrap(); - assert_eq!(output, init_value); - - // nothing stored - let loaded = bucket.load(b"maria").unwrap(); - assert_eq!(loaded, init_value); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_over_data() { - let mut store = MockStorage::new(); - let mut bucket = typed::<_, Data>(&mut store); - - let jose = Data { - name: "Jose".to_string(), - age: 42, - }; - let maria = Data { - name: "Maria".to_string(), - age: 27, - }; - - bucket.save(b"maria", &maria).unwrap(); - bucket.save(b"jose", &jose).unwrap(); - - let res_data: StdResult>> = - bucket.range(None, None, Order::Ascending).collect(); - let data = res_data.unwrap(); - assert_eq!(data.len(), 2); - assert_eq!(data[0], (b"jose".to_vec(), jose.clone())); - assert_eq!(data[1], (b"maria".to_vec(), maria.clone())); - - // also works for readonly - let read_bucket = typed_read::<_, Data>(&store); - let res_data: StdResult>> = - read_bucket.range(None, None, Order::Ascending).collect(); - let data = res_data.unwrap(); - assert_eq!(data.len(), 2); - assert_eq!(data[0], (b"jose".to_vec(), jose)); - assert_eq!(data[1], (b"maria".to_vec(), maria)); - } -} diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml index 07455d092..9804c9e75 100644 --- a/packages/vm/Cargo.toml +++ b/packages/vm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmwasm-vm" -version = "0.10.0" +version = "0.12.0" authors = ["Ethan Frey "] edition = "2018" description = "VM bindings to run cosmwams contracts" @@ -19,8 +19,8 @@ singlepass = ["wasmer-singlepass-backend"] # default selects which *one* is re-exported in backends/mod.rs (available through eg backends::*) default-cranelift = ["wasmer-clif-backend"] default-singlepass = ["wasmer-singlepass-backend"] -# enable this for better error reporting -backtraces = ["snafu/backtraces"] +# The backtraces fature is unused for now, i.e. it does nothing. +backtraces = [] # iterator allows us to iterate over all DB items in a given range # this must be enabled to support cosmwasm contracts compiled with the 'iterator' feature # optional as some merkle stores (like tries) don't support this @@ -30,8 +30,9 @@ iterator = ["cosmwasm-std/iterator"] staking = ["cosmwasm-std/staking"] [dependencies] +clru = "0.2.0" # Uses the path when built locally; uses the given version from crates.io when published -cosmwasm-std = { path = "../std", version = "0.10.0" } +cosmwasm-std = { path = "../std", version = "0.12.0" } serde_json = "1.0" wasmer-runtime-core = "=0.17.0" wasmer-middleware-common = "=0.17.0" @@ -39,12 +40,12 @@ wasmer-clif-backend = { version = "=0.17.0", optional = true } wasmer-singlepass-backend = { version = "=0.17.0", optional = true } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } -snafu = { version = "0.6.3" } sha2 = "0.9.1" +thiserror = "1.0" hex = "0.4" memmap = "0.7" -parity-wasm = "0.41" +parity-wasm = "0.42" [dev-dependencies] tempfile = "3.1.0" -wabt = "0.9.1" +wat = "1.0" diff --git a/packages/vm/README.md b/packages/vm/README.md index 0fd5a1fca..9e24ad555 100644 --- a/packages/vm/README.md +++ b/packages/vm/README.md @@ -8,6 +8,20 @@ efficient writing of unit tests, as well as a public API to run contracts in eg. go-cosmwasm. As such it includes all glue code needed for typical actions, like fs caching. +## Compatibility + +A VM can support one or more contract-VM interface versions. The interface +version is communicated by the contract via a Wasm import. This is the current +compatibility list: + +| cosmwasm-vm | Supported interface versions | cosmwasm-std | +| ----------- | ---------------------------- | ------------ | +| 0.12 | `cosmwasm_vm_version_4` | 0.11-0.12 | +| 0.11 | `cosmwasm_vm_version_4` | 0.11-0.12 | +| 0.10 | `cosmwasm_vm_version_3` | 0.10 | +| 0.9 | `cosmwasm_vm_version_2` | 0.9 | +| 0.8 | `cosmwasm_vm_version_1` | 0.8 | + ## Setup There are demo files in `testdata/*.wasm`. Those are compiled and optimized @@ -23,8 +37,8 @@ To rebuild the test contracts, go to the repo root and do 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.9.0 ./contracts/hackatom \ - && cp contracts/hackatom/contract.wasm packages/vm/testdata/contract_0.10.wasm + cosmwasm/rust-optimizer:0.10.5 ./contracts/hackatom \ + && cp artifacts/hackatom.wasm packages/vm/testdata/contract_0.12.wasm ``` ## Testing diff --git a/packages/vm/src/backend.rs b/packages/vm/src/backend.rs new file mode 100644 index 000000000..97591c74f --- /dev/null +++ b/packages/vm/src/backend.rs @@ -0,0 +1,307 @@ +use std::fmt::Debug; +use std::ops::AddAssign; +use std::string::FromUtf8Error; +use thiserror::Error; + +use cosmwasm_std::{Binary, CanonicalAddr, ContractResult, HumanAddr, SystemResult}; +#[cfg(feature = "iterator")] +use cosmwasm_std::{Order, KV}; + +#[derive(Copy, Clone, Debug)] +pub struct GasInfo { + /// The gas cost of a computation that was executed already but not yet charged + pub cost: u64, + /// Gas that was used and charged externally. This is needed to + /// adjust the VM's gas limit but does not affect the gas usage. + pub externally_used: u64, +} + +impl GasInfo { + pub fn with_cost(amount: u64) -> Self { + GasInfo { + cost: amount, + externally_used: 0, + } + } + + pub fn with_externally_used(amount: u64) -> Self { + GasInfo { + cost: 0, + externally_used: amount, + } + } + + /// Creates a gas information with no cost for the caller and with zero externally used gas. + /// + /// Caution: when using this you need to make sure no gas was metered externally to keep the gas values in sync. + pub fn free() -> Self { + GasInfo { + cost: 0, + externally_used: 0, + } + } +} + +impl AddAssign for GasInfo { + fn add_assign(&mut self, other: Self) { + *self = GasInfo { + cost: self.cost + other.cost, + externally_used: self.externally_used + other.cost, + }; + } +} + +/// Holds all external dependencies of the contract. +/// Designed to allow easy dependency injection at runtime. +/// This cannot be copied or cloned since it would behave differently +/// for mock storages and a bridge storage in the VM. +pub struct Backend { + pub storage: S, + pub api: A, + pub querier: Q, +} + +/// Access to the VM's backend storage, i.e. the chain +pub trait Storage { + /// Returns Err on error. + /// Returns Ok(None) when key does not exist. + /// Returns Ok(Some(Vec)) when key exists. + /// + /// Note: Support for differentiating between a non-existent key and a key with empty value + /// is not great yet and might not be possible in all backends. But we're trying to get there. + fn get(&self, key: &[u8]) -> BackendResult>>; + + /// Allows iteration over a set of key/value pairs, either forwards or backwards. + /// Returns an interator ID that is unique within the Storage instance. + /// + /// The bound `start` is inclusive and `end` is exclusive. + /// + /// If `start` is lexicographically greater than or equal to `end`, an empty range is described, mo matter of the order. + /// + /// This call must not change data in the storage, but creating and storing a new iterator can be a mutating operation on + /// the Storage implementation. + /// The implementation must ensure that iterator IDs are assigned in a deterministic manner as this is + /// environment data that is injected into the contract. + #[cfg(feature = "iterator")] + fn scan( + &mut self, + start: Option<&[u8]>, + end: Option<&[u8]>, + order: Order, + ) -> BackendResult; + + /// Returns the next element of the iterator with the given ID. + /// + /// If the ID is not found, a BackendError::IteratorDoesNotExist is returned. + /// + /// This call must not change data in the storage, but incrementing an iterator can be a mutating operation on + /// the Storage implementation. + #[cfg(feature = "iterator")] + fn next(&mut self, iterator_id: u32) -> BackendResult>; + + fn set(&mut self, key: &[u8], value: &[u8]) -> BackendResult<()>; + + /// Removes a database entry at `key`. + /// + /// The current interface does not allow to differentiate between a key that existed + /// before and one that didn't exist. See https://github.com/CosmWasm/cosmwasm/issues/290 + fn remove(&mut self, key: &[u8]) -> BackendResult<()>; +} + +/// Api are callbacks to system functions defined outside of the wasm modules. +/// This is a trait to allow Mocks in the test code. +/// +/// Currently it just supports address conversion, we could add eg. crypto functions here. +/// These should all be pure (stateless) functions. If you need state, you probably want +/// to use the Querier. +/// +/// We can use feature flags to opt-in to non-essential methods +/// for backwards compatibility in systems that don't have them all. +pub trait Api: Copy + Clone + Send { + fn canonical_address(&self, human: &HumanAddr) -> BackendResult; + fn human_address(&self, canonical: &CanonicalAddr) -> BackendResult; +} + +pub trait Querier { + /// This is all that must be implemented for the Querier. + /// This allows us to pass through binary queries from one level to another without + /// knowing the custom format, or we can decode it, with the knowledge of the allowed + /// types. + /// + /// The gas limit describes how much VM gas this particular query is allowed + /// to comsume when measured separately from the rest of the contract. + /// The returned gas info (in BackendResult) can exceed the gas limit in cases + /// where the query could not be aborted exactly at the limit. + fn query_raw( + &self, + request: &[u8], + gas_limit: u64, + ) -> BackendResult>>; +} + +/// A result type for calling into the backend. Such a call can cause +/// non-negligible computational cost in both success and faiure case and must always have gas information +/// attached. +pub type BackendResult = (core::result::Result, GasInfo); + +#[derive(Error, Debug)] +pub enum BackendError { + #[error("Panic in FFI call")] + ForeignPanic {}, + #[error("Bad argument")] + BadArgument {}, + #[error("VM received invalid UTF-8 data from backend")] + InvalidUtf8 {}, + #[error("Iterator with ID {id} does not exist")] + IteratorDoesNotExist { id: u32 }, + #[error("Ran out of gas during call into backend")] + OutOfGas {}, + #[error("Unknown error during call into backend: {msg:?}")] + Unknown { msg: Option }, + // This is the only error case of BackendError that is reported back to the contract. + #[error("User error during call into backend: {msg}")] + UserErr { msg: String }, +} + +impl BackendError { + pub fn foreign_panic() -> Self { + BackendError::ForeignPanic {} + } + + pub fn bad_argument() -> Self { + BackendError::BadArgument {} + } + + pub fn iterator_does_not_exist(iterator_id: u32) -> Self { + BackendError::IteratorDoesNotExist { id: iterator_id } + } + + pub fn out_of_gas() -> Self { + BackendError::OutOfGas {} + } + + pub fn unknown(msg: S) -> Self { + BackendError::Unknown { + msg: Some(msg.to_string()), + } + } + + /// Use `::unknown(msg: S)` if possible + pub fn unknown_without_message() -> Self { + BackendError::Unknown { msg: None } + } + + pub fn user_err(msg: S) -> Self { + BackendError::UserErr { + msg: msg.to_string(), + } + } +} + +impl From for BackendError { + fn from(_original: FromUtf8Error) -> Self { + BackendError::InvalidUtf8 {} + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn gas_info_with_cost_works() { + let gas_info = GasInfo::with_cost(21); + assert_eq!(gas_info.cost, 21); + assert_eq!(gas_info.externally_used, 0); + } + + #[test] + fn gas_info_with_externally_used_works() { + let gas_info = GasInfo::with_externally_used(65); + assert_eq!(gas_info.cost, 0); + assert_eq!(gas_info.externally_used, 65); + } + + #[test] + fn gas_info_free_works() { + let gas_info = GasInfo::free(); + assert_eq!(gas_info.cost, 0); + assert_eq!(gas_info.externally_used, 0); + } + + // constructors + + #[test] + fn ffi_error_foreign_panic() { + let error = BackendError::foreign_panic(); + match error { + BackendError::ForeignPanic { .. } => {} + e => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn ffi_error_bad_argument() { + let error = BackendError::bad_argument(); + match error { + BackendError::BadArgument { .. } => {} + e => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn iterator_does_not_exist_works() { + let error = BackendError::iterator_does_not_exist(15); + match error { + BackendError::IteratorDoesNotExist { id, .. } => assert_eq!(id, 15), + e => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn ffi_error_out_of_gas() { + let error = BackendError::out_of_gas(); + match error { + BackendError::OutOfGas { .. } => {} + e => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn ffi_error_unknown() { + let error = BackendError::unknown("broken"); + match error { + BackendError::Unknown { msg, .. } => assert_eq!(msg.unwrap(), "broken"), + e => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn ffi_error_unknown_without_message() { + let error = BackendError::unknown_without_message(); + match error { + BackendError::Unknown { msg, .. } => assert!(msg.is_none()), + e => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn ffi_error_user_err() { + let error = BackendError::user_err("invalid input"); + match error { + BackendError::UserErr { msg, .. } => assert_eq!(msg, "invalid input"), + e => panic!("Unexpected error: {:?}", e), + } + } + + // conversions + + #[test] + fn convert_from_fromutf8error() { + let error: BackendError = String::from_utf8(vec![0x80]).unwrap_err().into(); + match error { + BackendError::InvalidUtf8 { .. } => {} + e => panic!("Unexpected error: {:?}", e), + } + } +} diff --git a/packages/vm/src/backends/cranelift.rs b/packages/vm/src/backends/cranelift.rs index 573bcdb80..7f9e30beb 100644 --- a/packages/vm/src/backends/cranelift.rs +++ b/packages/vm/src/backends/cranelift.rs @@ -8,6 +8,8 @@ use crate::errors::VmResult; const FAKE_GAS_AVAILABLE: u64 = 1_000_000; +pub const BACKEND_NAME: &str = "cranelift"; + pub fn compile(code: &[u8]) -> VmResult { let config = CompilerConfig { enable_verification: false, // As discussed in https://github.com/CosmWasm/cosmwasm/issues/155 @@ -21,10 +23,6 @@ pub fn compiler() -> Box { Box::new(CraneliftCompiler::new()) } -pub fn backend() -> &'static str { - "cranelift" -} - /// Set the amount of gas units that can be used in the context. pub fn set_gas_left(_ctx: &mut Ctx, _amount: u64) {} diff --git a/packages/vm/src/backends/mod.rs b/packages/vm/src/backends/mod.rs index 350298273..c21865f6f 100644 --- a/packages/vm/src/backends/mod.rs +++ b/packages/vm/src/backends/mod.rs @@ -34,16 +34,15 @@ pub fn decrease_gas_left(ctx: &mut Ctx, amount: u64) -> Result<(), InsufficientG } #[cfg(feature = "default-cranelift")] -pub use cranelift::{backend, compile, get_gas_left, set_gas_left}; +pub use cranelift::{compile, get_gas_left, set_gas_left, BACKEND_NAME}; #[cfg(feature = "default-singlepass")] -pub use singlepass::{backend, compile, get_gas_left, set_gas_left}; +pub use singlepass::{compile, get_gas_left, set_gas_left, BACKEND_NAME}; #[cfg(test)] #[cfg(feature = "default-singlepass")] mod test { use super::*; - use wabt::wat2wasm; use wasmer_runtime_core::{imports, Instance as WasmerInstance}; fn instantiate(code: &[u8]) -> WasmerInstance { @@ -54,7 +53,7 @@ mod test { #[test] fn decrease_gas_left_works() { - let wasm = wat2wasm("(module)").unwrap(); + let wasm = wat::parse_str("(module)").unwrap(); let mut instance = instantiate(&wasm); let before = get_gas_left(instance.context()); @@ -65,7 +64,7 @@ mod test { #[test] fn decrease_gas_left_can_consume_all_gas() { - let wasm = wat2wasm("(module)").unwrap(); + let wasm = wat::parse_str("(module)").unwrap(); let mut instance = instantiate(&wasm); let before = get_gas_left(instance.context()); @@ -76,7 +75,7 @@ mod test { #[test] fn decrease_gas_left_errors_for_amount_greater_than_remaining() { - let wasm = wat2wasm("(module)").unwrap(); + let wasm = wat::parse_str("(module)").unwrap(); let mut instance = instantiate(&wasm); let before = get_gas_left(instance.context()); diff --git a/packages/vm/src/backends/singlepass.rs b/packages/vm/src/backends/singlepass.rs index ebc32ca29..8d33dd920 100644 --- a/packages/vm/src/backends/singlepass.rs +++ b/packages/vm/src/backends/singlepass.rs @@ -23,6 +23,8 @@ use crate::middleware::DeterministicMiddleware; /// far below u64::MAX. const MAX_GAS_LIMIT: u64 = u64::MAX / 2; +pub const BACKEND_NAME: &str = "singlepass"; + pub fn compile(code: &[u8]) -> VmResult { let module = compile_with(code, compiler().as_ref())?; Ok(module) @@ -38,10 +40,6 @@ pub fn compiler() -> Box { Box::new(c) } -pub fn backend() -> &'static str { - "singlepass" -} - /// Set the amount of gas units that can be used in the context. pub fn set_gas_left(ctx: &mut Ctx, amount: u64) { if amount > MAX_GAS_LIMIT { @@ -65,7 +63,6 @@ pub fn get_gas_left(ctx: &Ctx) -> u64 { #[cfg(test)] mod test { use super::*; - use wabt::wat2wasm; use wasmer_runtime_core::{imports, Instance as WasmerInstance}; fn instantiate(code: &[u8]) -> WasmerInstance { @@ -76,7 +73,7 @@ mod test { #[test] fn get_gas_left_defaults_to_constant() { - let wasm = wat2wasm("(module)").unwrap(); + let wasm = wat::parse_str("(module)").unwrap(); let instance = instantiate(&wasm); let gas_left = get_gas_left(instance.context()); assert_eq!(gas_left, MAX_GAS_LIMIT); @@ -84,7 +81,7 @@ mod test { #[test] fn set_gas_left_works() { - let wasm = wat2wasm("(module)").unwrap(); + let wasm = wat::parse_str("(module)").unwrap(); let mut instance = instantiate(&wasm); let limit = 3456789; @@ -109,7 +106,7 @@ mod test { expected = "Attempted to set gas limit larger than max gas limit (got: 9223372036854775808; maximum: 9223372036854775807)." )] fn set_gas_left_panic_for_values_too_large() { - let wasm = wat2wasm("(module)").unwrap(); + let wasm = wat::parse_str("(module)").unwrap(); let mut instance = instantiate(&wasm); let limit = MAX_GAS_LIMIT + 1; diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 07fed8e49..e7042b94d 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -4,27 +4,37 @@ use std::io::{Read, Write}; use std::marker::PhantomData; use std::path::PathBuf; -use crate::backends::{backend, compile}; +use crate::backend::{Api, Backend, Querier, Storage}; +use crate::backends::compile; use crate::checksum::Checksum; -use crate::compatability::check_wasm; +use crate::compatibility::check_wasm; use crate::errors::{VmError, VmResult}; -use crate::instance::Instance; -use crate::modules::FileSystemCache; -use crate::traits::{Api, Extern, Querier, Storage}; +use crate::instance::{Instance, InstanceOptions}; +use crate::modules::{FileSystemCache, InMemoryCache}; +use crate::size::Size; const WASM_DIR: &str = "wasm"; const MODULES_DIR: &str = "modules"; #[derive(Debug, Default, Clone)] struct Stats { - hits_module: u32, + hits_memory_cache: u32, + hits_fs_cache: u32, misses: u32, } -pub struct CosmCache { +#[derive(Clone, Debug)] +pub struct CacheOptions { + pub base_dir: PathBuf, + pub supported_features: HashSet, + pub memory_cache_size: Size, +} + +pub struct Cache { wasm_path: PathBuf, supported_features: HashSet, - modules: FileSystemCache, + memory_cache: InMemoryCache, + fs_cache: FileSystemCache, stats: Stats, // Those two don't store data but only fix type information type_storage: PhantomData, @@ -32,11 +42,11 @@ pub struct CosmCache, } -impl CosmCache +impl Cache where - S: Storage + 'static, - A: Api + 'static, - Q: Querier + 'static, + S: Storage, + A: Api + 'static, // 'static is needed by `impl<…> Instance` + Q: Querier, { /// new stores the data for cache under base_dir /// @@ -47,21 +57,23 @@ where /// This function is marked unsafe due to `FileSystemCache::new`, which implicitly /// assumes the disk contents are correct, and there's no way to ensure the artifacts // stored in the cache haven't been corrupted or tampered with. - pub unsafe fn new>( - base_dir: P, - supported_features: HashSet, - ) -> VmResult { - let base = base_dir.into(); - let wasm_path = base.join(WASM_DIR); + pub unsafe fn new(options: CacheOptions) -> VmResult { + let CacheOptions { + base_dir, + supported_features, + memory_cache_size, + } = options; + let wasm_path = base_dir.join(WASM_DIR); create_dir_all(&wasm_path) .map_err(|e| VmError::cache_err(format!("Error creating Wasm dir for cache: {}", e)))?; - let modules = FileSystemCache::new(base.join(MODULES_DIR)) + let fs_cache = FileSystemCache::new(base_dir.join(MODULES_DIR)) .map_err(|e| VmError::cache_err(format!("Error file system cache: {}", e)))?; - Ok(CosmCache { + Ok(Cache { wasm_path, supported_features, - modules, + memory_cache: InMemoryCache::new(memory_cache_size), + fs_cache, stats: Stats::default(), type_storage: PhantomData::, type_api: PhantomData::, @@ -73,7 +85,7 @@ where check_wasm(wasm, &self.supported_features)?; let checksum = save_wasm_to_disk(&self.wasm_path, wasm)?; let module = compile(wasm)?; - self.modules.store(&checksum, module)?; + self.fs_cache.store(&checksum, &module)?; Ok(checksum) } @@ -97,20 +109,35 @@ where pub fn get_instance( &mut self, checksum: &Checksum, - deps: Extern, - gas_limit: u64, + backend: Backend, + options: InstanceOptions, ) -> VmResult> { - // try from the module cache - let res = self.modules.load_with_backend(checksum, backend()); - if let Ok(module) = res { - self.stats.hits_module += 1; - return Instance::from_module(&module, deps, gas_limit); + // Get module from memory cache + if let Some(module) = self.memory_cache.load(checksum)? { + self.stats.hits_memory_cache += 1; + let instance = + Instance::from_module(module, backend, options.gas_limit, options.print_debug)?; + return Ok(instance); + } + + // Get module from file system cache + if let Some(module) = self.fs_cache.load(checksum)? { + self.stats.hits_fs_cache += 1; + let instance = + Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; + self.memory_cache.store(checksum, module)?; + return Ok(instance); } - // fall back to wasm cache (and re-compiling) - this is for backends that don't support serialization + // Re-compile module from wasm let wasm = self.load_wasm(checksum)?; self.stats.misses += 1; - Instance::from_code(&wasm, deps, gas_limit) + let module = compile(&wasm)?; + let instance = + Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; + self.fs_cache.store(checksum, &module)?; + self.memory_cache.store(checksum, module)?; + Ok(instance) } } @@ -155,34 +182,45 @@ mod test { use crate::calls::{call_handle, call_init}; use crate::errors::VmError; use crate::features::features_from_csv; - use crate::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; + use crate::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage}; use cosmwasm_std::{coins, Empty}; use std::fs::OpenOptions; use std::io::Write; use tempfile::TempDir; - use wabt::wat2wasm; const TESTING_GAS_LIMIT: u64 = 400_000; + const TESTING_OPTIONS: InstanceOptions = InstanceOptions { + gas_limit: TESTING_GAS_LIMIT, + print_debug: false, + }; + const TESTING_MEMORY_CACHE_SIZE: Size = Size::mebi(200); + static CONTRACT: &[u8] = include_bytes!("../testdata/contract.wasm"); fn default_features() -> HashSet { features_from_csv("staking") } + fn make_testing_options() -> CacheOptions { + CacheOptions { + base_dir: TempDir::new().unwrap().into_path(), + supported_features: default_features(), + memory_cache_size: TESTING_MEMORY_CACHE_SIZE, + } + } + #[test] fn save_wasm_works() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache: CosmCache = - unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache: Cache = + unsafe { Cache::new(make_testing_options()).unwrap() }; cache.save_wasm(CONTRACT).unwrap(); } #[test] // This property is required when the same bytecode is uploaded multiple times fn save_wasm_allows_saving_multiple_times() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache: CosmCache = - unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache: Cache = + unsafe { Cache::new(make_testing_options()).unwrap() }; cache.save_wasm(CONTRACT).unwrap(); cache.save_wasm(CONTRACT).unwrap(); } @@ -190,7 +228,7 @@ mod test { #[test] fn save_wasm_rejects_invalid_contract() { // Invalid because it doesn't contain required memory and exports - let wasm = wat2wasm( + let wasm = wat::parse_str( r#"(module (type $t0 (func (param i32) (result i32))) (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32) @@ -201,9 +239,8 @@ mod test { ) .unwrap(); - let tmp_dir = TempDir::new().unwrap(); - let mut cache: CosmCache = - unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache: Cache = + unsafe { Cache::new(make_testing_options()).unwrap() }; let save_result = cache.save_wasm(&wasm); match save_result.unwrap_err() { VmError::StaticValidationErr { msg, .. } => { @@ -213,11 +250,27 @@ mod test { } } + #[test] + fn save_wasm_fills_file_system_but_not_memory_cache() { + // Who knows if and when the uploaded contract will be executed. Don't pollute + // memory cache before the init call. + + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; + let checksum = cache.save_wasm(CONTRACT).unwrap(); + + let backend = mock_backend(&[]); + let _ = cache + .get_instance(&checksum, backend, TESTING_OPTIONS) + .unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 0); + assert_eq!(cache.stats.hits_fs_cache, 1); + assert_eq!(cache.stats.misses, 0); + } + #[test] fn load_wasm_works() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache: CosmCache = - unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache: Cache = + unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); let restored = cache.load_wasm(&id).unwrap(); @@ -227,18 +280,27 @@ mod test { #[test] fn load_wasm_works_across_multiple_cache_instances() { let tmp_dir = TempDir::new().unwrap(); - let tmp_path = tmp_dir.path(); let id: Checksum; { - let mut cache1: CosmCache = - unsafe { CosmCache::new(tmp_path, default_features()).unwrap() }; + let options1 = CacheOptions { + base_dir: tmp_dir.path().to_path_buf(), + supported_features: default_features(), + memory_cache_size: TESTING_MEMORY_CACHE_SIZE, + }; + let mut cache1: Cache = + unsafe { Cache::new(options1).unwrap() }; id = cache1.save_wasm(CONTRACT).unwrap(); } { - let cache2: CosmCache = - unsafe { CosmCache::new(tmp_path, default_features()).unwrap() }; + let options2 = CacheOptions { + base_dir: tmp_dir.path().to_path_buf(), + supported_features: default_features(), + memory_cache_size: TESTING_MEMORY_CACHE_SIZE, + }; + let cache2: Cache = + unsafe { Cache::new(options2).unwrap() }; let restored = cache2.load_wasm(&id).unwrap(); assert_eq!(restored, CONTRACT); } @@ -246,9 +308,8 @@ mod test { #[test] fn load_wasm_errors_for_non_existent_id() { - let tmp_dir = TempDir::new().unwrap(); - let cache: CosmCache = - unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let cache: Cache = + unsafe { Cache::new(make_testing_options()).unwrap() }; let checksum = Checksum::from([ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, @@ -266,8 +327,13 @@ mod test { #[test] fn load_wasm_errors_for_corrupted_wasm() { let tmp_dir = TempDir::new().unwrap(); - let mut cache: CosmCache = - unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let options = CacheOptions { + base_dir: tmp_dir.path().to_path_buf(), + supported_features: default_features(), + memory_cache_size: TESTING_MEMORY_CACHE_SIZE, + }; + let mut cache: Cache = + unsafe { Cache::new(options).unwrap() }; let checksum = cache.save_wasm(CONTRACT).unwrap(); // Corrupt cache file @@ -285,113 +351,122 @@ mod test { #[test] fn get_instance_finds_cached_module() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache = unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); - let deps = mock_dependencies(20, &[]); - let _instance = cache.get_instance(&id, deps, TESTING_GAS_LIMIT).unwrap(); - assert_eq!(cache.stats.hits_module, 1); + let backend = mock_backend(&[]); + let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 0); + assert_eq!(cache.stats.hits_fs_cache, 1); assert_eq!(cache.stats.misses, 0); } #[test] - fn get_instance_finds_cached_instance() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache = unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + fn get_instance_finds_cached_modules_and_stores_to_memory() { + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); - let deps1 = mock_dependencies(20, &[]); - let deps2 = mock_dependencies(20, &[]); - let deps3 = mock_dependencies(20, &[]); - let _instance1 = cache.get_instance(&id, deps1, TESTING_GAS_LIMIT).unwrap(); - let _instance2 = cache.get_instance(&id, deps2, TESTING_GAS_LIMIT).unwrap(); - let _instance3 = cache.get_instance(&id, deps3, TESTING_GAS_LIMIT).unwrap(); - assert_eq!(cache.stats.hits_module, 3); + let backend1 = mock_backend(&[]); + let backend2 = mock_backend(&[]); + let backend3 = mock_backend(&[]); + + // from file system + let _instance1 = cache.get_instance(&id, backend1, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 0); + assert_eq!(cache.stats.hits_fs_cache, 1); + assert_eq!(cache.stats.misses, 0); + + // from memory + let _instance2 = cache.get_instance(&id, backend2, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 1); + assert_eq!(cache.stats.hits_fs_cache, 1); + assert_eq!(cache.stats.misses, 0); + + // from memory again + let _instance3 = cache.get_instance(&id, backend3, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 2); + assert_eq!(cache.stats.hits_fs_cache, 1); assert_eq!(cache.stats.misses, 0); } #[test] fn init_cached_contract() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache = unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); - let deps = mock_dependencies(20, &[]); - let mut instance = cache.get_instance(&id, deps, TESTING_GAS_LIMIT).unwrap(); + let backend = mock_backend(&[]); + let mut instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); // run contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); // call and check - let res = call_init::<_, _, _, Empty>(&mut instance, &env, msg).unwrap(); + let res = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); let msgs = res.unwrap().messages; assert_eq!(msgs.len(), 0); } #[test] fn run_cached_contract() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache = unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); // TODO: contract balance - let deps = mock_dependencies(20, &[]); - let mut instance = cache.get_instance(&id, deps, TESTING_GAS_LIMIT).unwrap(); + let backend = mock_backend(&[]); + let mut instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); // init contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); - let res = call_init::<_, _, _, Empty>(&mut instance, &env, msg).unwrap(); + let res = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); let msgs = res.unwrap().messages; assert_eq!(msgs.len(), 0); // run contract - just sanity check - results validate in contract unit tests - let env = mock_env("verifies", &coins(15, "earth")); + let info = mock_info("verifies", &coins(15, "earth")); let msg = br#"{"release":{}}"#; - let res = call_handle::<_, _, _, Empty>(&mut instance, &env, msg).unwrap(); + let res = call_handle::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); let msgs = res.unwrap().messages; assert_eq!(1, msgs.len()); } #[test] fn use_multiple_cached_instances_of_same_contract() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache = unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); // these differentiate the two instances of the same contract - let deps1 = mock_dependencies(20, &[]); - let deps2 = mock_dependencies(20, &[]); + let backend1 = mock_backend(&[]); + let backend2 = mock_backend(&[]); // init instance 1 - let mut instance = cache.get_instance(&id, deps1, TESTING_GAS_LIMIT).unwrap(); - let env = mock_env("owner1", &coins(1000, "earth")); + let mut instance = cache.get_instance(&id, backend1, TESTING_OPTIONS).unwrap(); + let info = mock_info("owner1", &coins(1000, "earth")); let msg = r#"{"verifier": "sue", "beneficiary": "mary"}"#.as_bytes(); - let res = call_init::<_, _, _, Empty>(&mut instance, &env, msg).unwrap(); + let res = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); let msgs = res.unwrap().messages; assert_eq!(msgs.len(), 0); - let deps1 = instance.recycle().unwrap(); + let backend1 = instance.recycle().unwrap(); // init instance 2 - let mut instance = cache.get_instance(&id, deps2, TESTING_GAS_LIMIT).unwrap(); - let env = mock_env("owner2", &coins(500, "earth")); + let mut instance = cache.get_instance(&id, backend2, TESTING_OPTIONS).unwrap(); + let info = mock_info("owner2", &coins(500, "earth")); let msg = r#"{"verifier": "bob", "beneficiary": "john"}"#.as_bytes(); - let res = call_init::<_, _, _, Empty>(&mut instance, &env, msg).unwrap(); + let res = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); let msgs = res.unwrap().messages; assert_eq!(msgs.len(), 0); - let deps2 = instance.recycle().unwrap(); + let backend2 = instance.recycle().unwrap(); // run contract 2 - just sanity check - results validate in contract unit tests - let mut instance = cache.get_instance(&id, deps2, TESTING_GAS_LIMIT).unwrap(); - let env = mock_env("bob", &coins(15, "earth")); + let mut instance = cache.get_instance(&id, backend2, TESTING_OPTIONS).unwrap(); + let info = mock_info("bob", &coins(15, "earth")); let msg = br#"{"release":{}}"#; - let res = call_handle::<_, _, _, Empty>(&mut instance, &env, msg).unwrap(); + let res = call_handle::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); let msgs = res.unwrap().messages; assert_eq!(1, msgs.len()); // run contract 1 - just sanity check - results validate in contract unit tests - let mut instance = cache.get_instance(&id, deps1, TESTING_GAS_LIMIT).unwrap(); - let env = mock_env("sue", &coins(15, "earth")); + let mut instance = cache.get_instance(&id, backend1, TESTING_OPTIONS).unwrap(); + let info = mock_info("sue", &coins(15, "earth")); let msg = br#"{"release":{}}"#; - let res = call_handle::<_, _, _, Empty>(&mut instance, &env, msg).unwrap(); + let res = call_handle::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); let msgs = res.unwrap().messages; assert_eq!(1, msgs.len()); } @@ -399,30 +474,31 @@ mod test { #[test] #[cfg(feature = "default-singlepass")] fn resets_gas_when_reusing_instance() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache = unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); - let deps1 = mock_dependencies(20, &[]); - let deps2 = mock_dependencies(20, &[]); + let backend1 = mock_backend(&[]); + let backend2 = mock_backend(&[]); // Init from module cache - let mut instance1 = cache.get_instance(&id, deps1, TESTING_GAS_LIMIT).unwrap(); - assert_eq!(cache.stats.hits_module, 1); + let mut instance1 = cache.get_instance(&id, backend1, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 0); + assert_eq!(cache.stats.hits_fs_cache, 1); assert_eq!(cache.stats.misses, 0); let original_gas = instance1.get_gas_left(); // Consume some gas - let env = mock_env("owner1", &coins(1000, "earth")); + let info = mock_info("owner1", &coins(1000, "earth")); let msg = r#"{"verifier": "sue", "beneficiary": "mary"}"#.as_bytes(); - call_init::<_, _, _, Empty>(&mut instance1, &env, msg) + call_init::<_, _, _, Empty>(&mut instance1, &mock_env(), &info, msg) .unwrap() .unwrap(); assert!(instance1.get_gas_left() < original_gas); - // Init from instance cache - let instance2 = cache.get_instance(&id, deps2, TESTING_GAS_LIMIT).unwrap(); - assert_eq!(cache.stats.hits_module, 2); + // Init from memory cache + let instance2 = cache.get_instance(&id, backend2, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 1); + assert_eq!(cache.stats.hits_fs_cache, 1); assert_eq!(cache.stats.misses, 0); assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT); } @@ -430,37 +506,45 @@ mod test { #[test] #[cfg(feature = "default-singlepass")] fn recovers_from_out_of_gas() { - let tmp_dir = TempDir::new().unwrap(); - let mut cache = unsafe { CosmCache::new(tmp_dir.path(), default_features()).unwrap() }; + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; let id = cache.save_wasm(CONTRACT).unwrap(); - let deps1 = mock_dependencies(20, &[]); - let deps2 = mock_dependencies(20, &[]); + let backend1 = mock_backend(&[]); + let backend2 = mock_backend(&[]); // Init from module cache - let mut instance1 = cache.get_instance(&id, deps1, 10).unwrap(); - assert_eq!(cache.stats.hits_module, 1); + let options = InstanceOptions { + gas_limit: 10, + print_debug: false, + }; + let mut instance1 = cache.get_instance(&id, backend1, options).unwrap(); + assert_eq!(cache.stats.hits_fs_cache, 1); assert_eq!(cache.stats.misses, 0); // Consume some gas. This fails - let env1 = mock_env("owner1", &coins(1000, "earth")); + let info1 = mock_info("owner1", &coins(1000, "earth")); let msg1 = r#"{"verifier": "sue", "beneficiary": "mary"}"#.as_bytes(); - match call_init::<_, _, _, Empty>(&mut instance1, &env1, msg1).unwrap_err() { + match call_init::<_, _, _, Empty>(&mut instance1, &mock_env(), &info1, msg1).unwrap_err() { VmError::GasDepletion { .. } => (), // all good, continue e => panic!("unexpected error, {:?}", e), } assert_eq!(instance1.get_gas_left(), 0); - // Init from instance cache - let mut instance2 = cache.get_instance(&id, deps2, TESTING_GAS_LIMIT).unwrap(); - assert_eq!(cache.stats.hits_module, 2); + // Init from memory cache + let options = InstanceOptions { + gas_limit: TESTING_GAS_LIMIT, + print_debug: false, + }; + let mut instance2 = cache.get_instance(&id, backend2, options).unwrap(); + assert_eq!(cache.stats.hits_memory_cache, 1); + assert_eq!(cache.stats.hits_fs_cache, 1); assert_eq!(cache.stats.misses, 0); assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT); // Now it works - let env2 = mock_env("owner2", &coins(500, "earth")); + let info2 = mock_info("owner2", &coins(500, "earth")); let msg2 = r#"{"verifier": "bob", "beneficiary": "john"}"#.as_bytes(); - call_init::<_, _, _, Empty>(&mut instance2, &env2, msg2) + call_init::<_, _, _, Empty>(&mut instance2, &mock_env(), &info2, msg2) .unwrap() .unwrap(); } diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs index d39fda592..b1933fb16 100644 --- a/packages/vm/src/calls.rs +++ b/packages/vm/src/calls.rs @@ -1,12 +1,14 @@ use serde::de::DeserializeOwned; use std::fmt; -use cosmwasm_std::{Env, HandleResult, InitResult, MigrateResult, QueryResult}; +use cosmwasm_std::{ + ContractResult, Env, HandleResponse, InitResponse, MessageInfo, MigrateResponse, QueryResponse, +}; +use crate::backend::{Api, Querier, Storage}; use crate::errors::{VmError, VmResult}; use crate::instance::{Func, Instance}; use crate::serde::{from_slice, to_vec}; -use crate::traits::{Api, Querier, Storage}; use schemars::JsonSchema; const MAX_LENGTH_INIT: usize = 100_000; @@ -17,63 +19,71 @@ const MAX_LENGTH_QUERY: usize = 100_000; pub fn call_init( instance: &mut Instance, env: &Env, + info: &MessageInfo, msg: &[u8], -) -> VmResult> +) -> VmResult>> where - S: Storage + 'static, + S: Storage, A: Api + 'static, - Q: Querier + 'static, + Q: Querier, U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, { let env = to_vec(env)?; - let data = call_init_raw(instance, &env, msg)?; - let result: InitResult = from_slice(&data)?; + let info = to_vec(info)?; + let data = call_init_raw(instance, &env, &info, msg)?; + let result: ContractResult> = from_slice(&data)?; Ok(result) } pub fn call_handle( instance: &mut Instance, env: &Env, + info: &MessageInfo, msg: &[u8], -) -> VmResult> +) -> VmResult>> where - S: Storage + 'static, + S: Storage, A: Api + 'static, - Q: Querier + 'static, + Q: Querier, U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, { let env = to_vec(env)?; - let data = call_handle_raw(instance, &env, msg)?; - let result: HandleResult = from_slice(&data)?; + let info = to_vec(info)?; + let data = call_handle_raw(instance, &env, &info, msg)?; + let result: ContractResult> = from_slice(&data)?; Ok(result) } pub fn call_migrate( instance: &mut Instance, env: &Env, + info: &MessageInfo, msg: &[u8], -) -> VmResult> +) -> VmResult>> where - S: Storage + 'static, + S: Storage, A: Api + 'static, - Q: Querier + 'static, + Q: Querier, U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, { let env = to_vec(env)?; - let data = call_migrate_raw(instance, &env, msg)?; - let result: MigrateResult = from_slice(&data)?; + let info = to_vec(info)?; + let data = call_migrate_raw(instance, &env, &info, msg)?; + let result: ContractResult> = from_slice(&data)?; Ok(result) } -pub fn call_query( +pub fn call_query( instance: &mut Instance, + env: &Env, msg: &[u8], -) -> VmResult { - let data = call_query_raw(instance, msg)?; - let result: QueryResult = from_slice(&data)?; +) -> VmResult> { + let env = to_vec(env)?; + let data = call_query_raw(instance, &env, msg)?; + let result: ContractResult = from_slice(&data)?; // Ensure query response is valid JSON - if let Ok(binary_response) = &result { + if let ContractResult::Ok(binary_response) = &result { serde_json::from_slice::(binary_response.as_slice()).map_err(|e| { VmError::generic_err(format!("Query response must be valid JSON. {}", e)) })?; @@ -84,48 +94,52 @@ pub fn call_query( /// Calls Wasm export "init" and returns raw data from the contract. /// The result is length limited to prevent abuse but otherwise unchecked. -pub fn call_init_raw( +pub fn call_init_raw( instance: &mut Instance, env: &[u8], + info: &[u8], msg: &[u8], ) -> VmResult> { instance.set_storage_readonly(false); - call_raw(instance, "init", &[env, msg], MAX_LENGTH_INIT) + call_raw(instance, "init", &[env, info, msg], MAX_LENGTH_INIT) } /// Calls Wasm export "handle" and returns raw data from the contract. /// The result is length limited to prevent abuse but otherwise unchecked. -pub fn call_handle_raw( +pub fn call_handle_raw( instance: &mut Instance, env: &[u8], + info: &[u8], msg: &[u8], ) -> VmResult> { instance.set_storage_readonly(false); - call_raw(instance, "handle", &[env, msg], MAX_LENGTH_HANDLE) + call_raw(instance, "handle", &[env, info, msg], MAX_LENGTH_HANDLE) } /// Calls Wasm export "migrate" and returns raw data from the contract. /// The result is length limited to prevent abuse but otherwise unchecked. -pub fn call_migrate_raw( +pub fn call_migrate_raw( instance: &mut Instance, env: &[u8], + info: &[u8], msg: &[u8], ) -> VmResult> { instance.set_storage_readonly(false); - call_raw(instance, "migrate", &[env, msg], MAX_LENGTH_MIGRATE) + call_raw(instance, "migrate", &[env, info, msg], MAX_LENGTH_MIGRATE) } /// Calls Wasm export "query" and returns raw data from the contract. /// The result is length limited to prevent abuse but otherwise unchecked. -pub fn call_query_raw( +pub fn call_query_raw( instance: &mut Instance, + env: &[u8], msg: &[u8], ) -> VmResult> { instance.set_storage_readonly(true); - call_raw(instance, "query", &[msg], MAX_LENGTH_QUERY) + call_raw(instance, "query", &[env, msg], MAX_LENGTH_QUERY) } -fn call_raw( +fn call_raw( instance: &mut Instance, name: &str, args: &[&[u8]], @@ -147,6 +161,10 @@ fn call_raw( let func: Func<(u32, u32), u32> = instance.func(name)?; func.call(arg_region_ptrs[0], arg_region_ptrs[1])? } + 3 => { + let func: Func<(u32, u32, u32), u32> = instance.func(name)?; + func.call(arg_region_ptrs[0], arg_region_ptrs[1], arg_region_ptrs[2])? + } _ => panic!("call_raw called with unsupported number of arguments"), }; diff --git a/packages/vm/src/compatability.rs b/packages/vm/src/compatibility.rs similarity index 69% rename from packages/vm/src/compatability.rs rename to packages/vm/src/compatibility.rs index 1035fd26a..971675c88 100644 --- a/packages/vm/src/compatability.rs +++ b/packages/vm/src/compatibility.rs @@ -5,6 +5,7 @@ use std::iter::FromIterator; use crate::errors::{VmError, VmResult}; use crate::features::required_features_from_module; +use crate::limited::LimitedDisplay; /// Lists all imports we provide upon instantiating the instance in Instance::from_module() /// This should be updated when new imports are added @@ -14,6 +15,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[ "env.db_remove", "env.canonicalize_address", "env.humanize_address", + "env.debug", "env.query_chain", #[cfg(feature = "iterator")] "env.db_scan", @@ -25,7 +27,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[ /// Basically, anything that is used in calls.rs /// This is unlikely to change much, must be frozen at 1.0 to avoid breaking existing contracts const REQUIRED_EXPORTS: &[&str] = &[ - "cosmwasm_vm_version_3", + "cosmwasm_vm_version_4", "query", "init", "handle", @@ -35,20 +37,21 @@ const REQUIRED_EXPORTS: &[&str] = &[ const MEMORY_LIMIT: u32 = 512; // in pages +fn deserialize(wasm_code: &[u8]) -> VmResult { + deserialize_buffer(&wasm_code).map_err(|err| { + VmError::static_validation_err(format!( + "Wasm bytecode could not be deserialized. Deserialization error: \"{}\"", + err + )) + }) +} + /// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports) pub fn check_wasm(wasm_code: &[u8], supported_features: &HashSet) -> VmResult<()> { - let module = match deserialize_buffer(&wasm_code) { - Ok(deserialized) => deserialized, - Err(err) => { - return Err(VmError::static_validation_err(format!( - "Wasm bytecode could not be deserialized. Deserialization error: \"{}\"", - err - ))); - } - }; + let module = deserialize(wasm_code)?; check_wasm_memories(&module)?; check_wasm_exports(&module)?; - check_wasm_imports(&module)?; + check_wasm_imports(&module, SUPPORTED_IMPORTS)?; check_wasm_features(&module, supported_features)?; Ok(()) } @@ -112,17 +115,18 @@ fn check_wasm_exports(module: &Module) -> VmResult<()> { /// Checks if the import requirements of the contract are satisfied. /// When this is not the case, we either have an incompatibility between contract and VM /// or a error in the contract. -fn check_wasm_imports(module: &Module) -> VmResult<()> { +fn check_wasm_imports(module: &Module, supported_imports: &[&str]) -> VmResult<()> { let required_imports: Vec = module .import_section() .map_or(vec![], |import_section| import_section.entries().to_vec()); + let required_import_names = BTreeSet::from_iter(required_imports.iter().map(full_import_name)); for required_import in required_imports { - let full_name = format!("{}.{}", required_import.module(), required_import.field()); - if !SUPPORTED_IMPORTS.contains(&full_name.as_str()) { + let full_name = full_import_name(&required_import); + if !supported_imports.contains(&full_name.as_str()) { return Err(VmError::static_validation_err(format!( - "Wasm contract requires unsupported import: \"{}\". Imports supported by VM: {:?}. Contract version too new for this VM?", - full_name, SUPPORTED_IMPORTS + "Wasm contract requires unsupported import: \"{}\". Required imports: {}. Available imports: {:?}.", + full_name, required_import_names.to_string_limited(200), supported_imports ))); } @@ -137,14 +141,18 @@ fn check_wasm_imports(module: &Module) -> VmResult<()> { Ok(()) } +fn full_import_name(ie: &ImportEntry) -> String { + format!("{}.{}", ie.module(), ie.field()) +} + fn check_wasm_features(module: &Module, supported_features: &HashSet) -> VmResult<()> { let required_features = required_features_from_module(module); if !required_features.is_subset(supported_features) { // We switch to BTreeSet to get a sorted error message let unsupported = BTreeSet::from_iter(required_features.difference(&supported_features)); return Err(VmError::static_validation_err(format!( - "Wasm contract requires unsupported features: {:?}", - unsupported + "Wasm contract requires unsupported features: {}", + unsupported.to_string_limited(200) ))); } Ok(()) @@ -154,8 +162,8 @@ fn check_wasm_features(module: &Module, supported_features: &HashSet) -> mod test { use super::*; use crate::errors::VmError; + use parity_wasm::elements::Internal; use std::iter::FromIterator; - use wabt::wat2wasm; static CONTRACT_0_6: &[u8] = include_bytes!("../testdata/contract_0.6.wasm"); static CONTRACT_0_7: &[u8] = include_bytes!("../testdata/contract_0.7.wasm"); @@ -166,6 +174,51 @@ mod test { HashSet::from_iter(["staking".to_string()].iter().cloned()) } + #[test] + fn test_deserialize_works() { + let module = deserialize(CONTRACT).unwrap(); + assert_eq!(module.version(), 1); + + let exported_functions = + module + .export_section() + .unwrap() + .entries() + .iter() + .filter(|entry| { + if let Internal::Function(_) = entry.internal() { + true + } else { + false + } + }); + assert_eq!(exported_functions.count(), 7); // 6 required export plus "migrate" + + let exported_memories = module + .export_section() + .unwrap() + .entries() + .iter() + .filter(|entry| { + if let Internal::Memory(_) = entry.internal() { + true + } else { + false + } + }); + assert_eq!(exported_memories.count(), 1); + } + + #[test] + fn test_deserialize_corrupted_data() { + match deserialize(CORRUPTED).unwrap_err() { + VmError::StaticValidationErr { msg, .. } => { + assert!(msg.starts_with("Wasm bytecode could not be deserialized.")) + } + err => panic!("Unexpected error: {:?}", err), + } + } + #[test] fn test_check_wasm() { // this is our reference check, must pass @@ -176,7 +229,7 @@ mod test { fn test_check_wasm_old_contract() { match check_wasm(CONTRACT_0_7, &default_features()) { Err(VmError::StaticValidationErr { msg, .. }) => assert!(msg.starts_with( - "Wasm contract doesn't have required export: \"cosmwasm_vm_version_3\"" + "Wasm contract doesn't have required export: \"cosmwasm_vm_version_4\"" )), Err(e) => panic!("Unexpected error {:?}", e), Ok(_) => panic!("This must not succeeed"), @@ -184,34 +237,23 @@ mod test { match check_wasm(CONTRACT_0_6, &default_features()) { Err(VmError::StaticValidationErr { msg, .. }) => assert!(msg.starts_with( - "Wasm contract doesn't have required export: \"cosmwasm_vm_version_3\"" + "Wasm contract doesn't have required export: \"cosmwasm_vm_version_4\"" )), Err(e) => panic!("Unexpected error {:?}", e), Ok(_) => panic!("This must not succeeed"), }; } - #[test] - fn test_check_wasm_corrupted_data() { - match check_wasm(CORRUPTED, &default_features()) { - Err(VmError::StaticValidationErr { msg, .. }) => { - assert!(msg.starts_with("Wasm bytecode could not be deserialized.")) - } - Err(e) => panic!("Unexpected error {:?}", e), - Ok(_) => panic!("This must not succeeed"), - } - } - #[test] fn test_check_wasm_memories_ok() { - let wasm = wat2wasm("(module (memory 1))").unwrap(); - check_wasm_memories(&deserialize_buffer(&wasm).unwrap()).unwrap() + let wasm = wat::parse_str("(module (memory 1))").unwrap(); + check_wasm_memories(&deserialize(&wasm).unwrap()).unwrap() } #[test] fn test_check_wasm_memories_no_memory() { - let wasm = wat2wasm("(module)").unwrap(); - match check_wasm_memories(&deserialize_buffer(&wasm).unwrap()) { + let wasm = wat::parse_str("(module)").unwrap(); + match check_wasm_memories(&deserialize(&wasm).unwrap()) { Err(VmError::StaticValidationErr { msg, .. }) => { assert!(msg.starts_with("Wasm contract doesn't have a memory section")); } @@ -235,7 +277,7 @@ mod test { )) .unwrap(); - match check_wasm_memories(&deserialize_buffer(&wasm).unwrap()) { + match check_wasm_memories(&deserialize(&wasm).unwrap()) { Err(VmError::StaticValidationErr { msg, .. }) => { assert!(msg.starts_with("Wasm contract must contain exactly one memory")); } @@ -256,7 +298,7 @@ mod test { )) .unwrap(); - match check_wasm_memories(&deserialize_buffer(&wasm).unwrap()) { + match check_wasm_memories(&deserialize(&wasm).unwrap()) { Err(VmError::StaticValidationErr { msg, .. }) => { assert!(msg.starts_with("Wasm contract must contain exactly one memory")); } @@ -267,11 +309,11 @@ mod test { #[test] fn test_check_wasm_memories_initial_size() { - let wasm_ok = wat2wasm("(module (memory 512))").unwrap(); - check_wasm_memories(&deserialize_buffer(&wasm_ok).unwrap()).unwrap(); + let wasm_ok = wat::parse_str("(module (memory 512))").unwrap(); + check_wasm_memories(&deserialize(&wasm_ok).unwrap()).unwrap(); - let wasm_too_big = wat2wasm("(module (memory 513))").unwrap(); - match check_wasm_memories(&deserialize_buffer(&wasm_too_big).unwrap()) { + let wasm_too_big = wat::parse_str("(module (memory 513))").unwrap(); + match check_wasm_memories(&deserialize(&wasm_too_big).unwrap()) { Err(VmError::StaticValidationErr { msg, .. }) => { assert!(msg.starts_with("Wasm contract memory's minimum must not exceed 512 pages")); } @@ -282,8 +324,8 @@ mod test { #[test] fn test_check_wasm_memories_maximum_size() { - let wasm_max = wat2wasm("(module (memory 1 5))").unwrap(); - match check_wasm_memories(&deserialize_buffer(&wasm_max).unwrap()) { + let wasm_max = wat::parse_str("(module (memory 1 5))").unwrap(); + match check_wasm_memories(&deserialize(&wasm_max).unwrap()) { Err(VmError::StaticValidationErr { msg, .. }) => { assert!(msg.starts_with("Wasm contract memory's maximum must be unset")); } @@ -303,13 +345,13 @@ mod test { i32.const 1 i32.add)) "#; - let wasm_missing_exports = wat2wasm(WAT_MISSING_EXPORTS).unwrap(); + let wasm_missing_exports = wat::parse_str(WAT_MISSING_EXPORTS).unwrap(); - let module = deserialize_buffer(&wasm_missing_exports).unwrap(); + let module = deserialize(&wasm_missing_exports).unwrap(); match check_wasm_exports(&module) { Err(VmError::StaticValidationErr { msg, .. }) => { assert!(msg.starts_with( - "Wasm contract doesn't have required export: \"cosmwasm_vm_version_3\"" + "Wasm contract doesn't have required export: \"cosmwasm_vm_version_4\"" )); } Err(e) => panic!("Unexpected error {:?}", e), @@ -319,11 +361,11 @@ mod test { #[test] fn test_check_wasm_exports_of_old_contract() { - let module = deserialize_buffer(CONTRACT_0_7).unwrap(); + let module = deserialize(CONTRACT_0_7).unwrap(); match check_wasm_exports(&module) { Err(VmError::StaticValidationErr { msg, .. }) => { assert!(msg.starts_with( - "Wasm contract doesn't have required export: \"cosmwasm_vm_version_3\"" + "Wasm contract doesn't have required export: \"cosmwasm_vm_version_4\"" )); } Err(e) => panic!("Unexpected error {:?}", e), @@ -333,7 +375,7 @@ mod test { #[test] fn check_wasm_imports_ok() { - let wasm = wat2wasm( + let wasm = wat::parse_str( r#"(module (import "env" "db_read" (func (param i32 i32) (result i32))) (import "env" "db_write" (func (param i32 i32) (result i32))) @@ -343,40 +385,81 @@ mod test { )"#, ) .unwrap(); - check_wasm_imports(&deserialize_buffer(&wasm).unwrap()).unwrap(); + check_wasm_imports(&deserialize(&wasm).unwrap(), SUPPORTED_IMPORTS).unwrap(); + } + + #[test] + fn test_check_wasm_imports_missing() { + let wasm = wat::parse_str( + r#"(module + (import "env" "foo" (func (param i32 i32) (result i32))) + (import "env" "bar" (func (param i32 i32) (result i32))) + (import "env" "spammyspam01" (func (param i32 i32) (result i32))) + (import "env" "spammyspam02" (func (param i32 i32) (result i32))) + (import "env" "spammyspam03" (func (param i32 i32) (result i32))) + (import "env" "spammyspam04" (func (param i32 i32) (result i32))) + (import "env" "spammyspam05" (func (param i32 i32) (result i32))) + (import "env" "spammyspam06" (func (param i32 i32) (result i32))) + (import "env" "spammyspam07" (func (param i32 i32) (result i32))) + (import "env" "spammyspam08" (func (param i32 i32) (result i32))) + (import "env" "spammyspam09" (func (param i32 i32) (result i32))) + (import "env" "spammyspam10" (func (param i32 i32) (result i32))) + )"#, + ) + .unwrap(); + let supported_imports: &[&str] = &[ + "env.db_read", + "env.db_write", + "env.db_remove", + "env.canonicalize_address", + "env.humanize_address", + "env.debug", + "env.query_chain", + ]; + let result = check_wasm_imports(&deserialize(&wasm).unwrap(), supported_imports); + match result.unwrap_err() { + VmError::StaticValidationErr { msg, .. } => { + println!("{}", msg); + assert_eq!( + msg, + r#"Wasm contract requires unsupported import: "env.foo". Required imports: {"env.bar", "env.foo", "env.spammyspam01", "env.spammyspam02", "env.spammyspam03", "env.spammyspam04", "env.spammyspam05", "env.spammyspam06", "env.spammyspam07", "env.spammyspam08", ... 2 more}. Available imports: ["env.db_read", "env.db_write", "env.db_remove", "env.canonicalize_address", "env.humanize_address", "env.debug", "env.query_chain"]."# + ); + } + err => panic!("Unexpected error: {:?}", err), + } } #[test] fn test_check_wasm_imports_of_old_contract() { - let module = deserialize_buffer(CONTRACT_0_7).unwrap(); - match check_wasm_imports(&module) { - Err(VmError::StaticValidationErr { msg, .. }) => { + let module = deserialize(CONTRACT_0_7).unwrap(); + let result = check_wasm_imports(&module, SUPPORTED_IMPORTS); + match result.unwrap_err() { + VmError::StaticValidationErr { msg, .. } => { assert!( msg.starts_with("Wasm contract requires unsupported import: \"env.read_db\"") ); } - Err(e) => panic!("Unexpected error {:?}", e), - Ok(_) => panic!("Didn't reject wasm with invalid api"), + err => panic!("Unexpected error: {:?}", err), } } #[test] fn test_check_wasm_imports_wrong_type() { - let wasm = wat2wasm(r#"(module (import "env" "db_read" (memory 1 1)))"#).unwrap(); - match check_wasm_imports(&deserialize_buffer(&wasm).unwrap()) { - Err(VmError::StaticValidationErr { msg, .. }) => { + let wasm = wat::parse_str(r#"(module (import "env" "db_read" (memory 1 1)))"#).unwrap(); + let result = check_wasm_imports(&deserialize(&wasm).unwrap(), SUPPORTED_IMPORTS); + match result.unwrap_err() { + VmError::StaticValidationErr { msg, .. } => { assert!( msg.starts_with("Wasm contract requires non-function import: \"env.db_read\"") ); } - Err(e) => panic!("Unexpected error {:?}", e), - Ok(_) => panic!("Didn't reject wasm with invalid api"), + err => panic!("Unexpected error: {:?}", err), } } #[test] fn check_wasm_features_ok() { - let wasm = wat2wasm( + let wasm = wat::parse_str( r#"(module (type (func)) (func (type 0) nop) @@ -389,7 +472,7 @@ mod test { )"#, ) .unwrap(); - let module = deserialize_buffer(&wasm).unwrap(); + let module = deserialize(&wasm).unwrap(); let supported = HashSet::from_iter( [ "water".to_string(), @@ -405,7 +488,7 @@ mod test { #[test] fn check_wasm_features_fails_for_missing() { - let wasm = wat2wasm( + let wasm = wat::parse_str( r#"(module (type (func)) (func (type 0) nop) @@ -418,7 +501,7 @@ mod test { )"#, ) .unwrap(); - let module = deserialize_buffer(&wasm).unwrap(); + let module = deserialize(&wasm).unwrap(); // Support set 1 let supported = HashSet::from_iter( diff --git a/packages/vm/src/context.rs b/packages/vm/src/context.rs index 624cc58e0..a588910ba 100644 --- a/packages/vm/src/context.rs +++ b/packages/vm/src/context.rs @@ -1,11 +1,6 @@ //! Internal details to be used by instance.rs only -#[cfg(feature = "iterator")] -use std::collections::HashMap; -#[cfg(feature = "iterator")] -use std::convert::TryInto; + use std::ffi::c_void; -#[cfg(not(feature = "iterator"))] -use std::marker::PhantomData; use std::ptr::NonNull; use wasmer_runtime_core::{ @@ -14,12 +9,9 @@ use wasmer_runtime_core::{ Instance as WasmerInstance, }; +use crate::backend::{GasInfo, Querier, Storage}; use crate::backends::decrease_gas_left; use crate::errors::{VmError, VmResult}; -use crate::ffi::GasInfo; -#[cfg(feature = "iterator")] -use crate::traits::StorageIterator; -use crate::traits::{Querier, Storage}; /** context data **/ @@ -70,17 +62,13 @@ impl GasState { } } -struct ContextData<'a, S: Storage, Q: Querier> { +struct ContextData { gas_state: GasState, storage: Option, storage_readonly: bool, querier: Option, /// A non-owning link to the wasmer instance wasmer_instance: Option>, - #[cfg(feature = "iterator")] - iterators: HashMap>, - #[cfg(not(feature = "iterator"))] - iterators: PhantomData<&'a mut ()>, } pub fn setup_context(gas_limit: u64) -> (*mut c_void, fn(*mut c_void)) { @@ -97,10 +85,6 @@ fn create_unmanaged_context_data(gas_limit: u64) -> *mut storage_readonly: true, querier: None, wasmer_instance: None, - #[cfg(feature = "iterator")] - iterators: HashMap::new(), - #[cfg(not(feature = "iterator"))] - iterators: PhantomData::default(), }; let heap_data = Box::new(data); // move from stack to heap Box::into_raw(heap_data) as *mut c_void // give up ownership @@ -109,35 +93,14 @@ fn create_unmanaged_context_data(gas_limit: u64) -> *mut fn destroy_unmanaged_context_data(ptr: *mut c_void) { if !ptr.is_null() { // obtain ownership and drop instance of ContextData when box gets out of scope - let mut dying = unsafe { Box::from_raw(ptr as *mut ContextData) }; - // Ensure all iterators are dropped before the storage - destroy_iterators(&mut dying); + let _dying = unsafe { Box::from_raw(ptr as *mut ContextData) }; } } /// Get a mutable reference to the context's data. Ownership remains in the Context. -// NOTE: This is actually not really implemented safely at the moment. I did this as a -// nicer and less-terrible version of the previous solution to the following issue: -// -// +--->> Go pointer -// | -// Ctx ->> ContextData +-> iterators: Box --+ -// | | -// +-> storage: impl Storage <<------------+ -// | -// +-> querier: impl Querier -// -// -> : Ownership -// ->> : Mutable borrow -// -// As you can see, there's a cyclical reference here... changing this function to return the same lifetime as it -// returns (and adjusting a few other functions to only have one lifetime instead of two) triggers an error -// elsewhere where we try to add iterators to the context. That's not legal according to Rust's rules, and it -// complains that we're trying to borrow ctx mutably twice. This needs a better solution because this function -// probably triggers unsoundness. fn get_context_data_mut<'a, 'b, S: Storage, Q: Querier>( ctx: &'a mut Ctx, -) -> &'b mut ContextData<'b, S, Q> { +) -> &'b mut ContextData { unsafe { let ptr = ctx.data as *mut ContextData; ptr.as_mut() @@ -145,7 +108,7 @@ fn get_context_data_mut<'a, 'b, S: Storage, Q: Querier>( } } -fn get_context_data<'a, 'b, S: Storage, Q: Querier>(ctx: &'a Ctx) -> &'b ContextData<'b, S, Q> { +fn get_context_data<'a, 'b, S: Storage, Q: Querier>(ctx: &'a Ctx) -> &'b ContextData { unsafe { let ptr = ctx.data as *mut ContextData; ptr.as_ref() @@ -164,25 +127,12 @@ pub fn set_wasmer_instance( } } -#[cfg(feature = "iterator")] -fn destroy_iterators(context: &mut ContextData) { - context.iterators.clear(); -} - -#[cfg(not(feature = "iterator"))] -fn destroy_iterators(_context: &mut ContextData) {} - /// Returns the original storage and querier as owned instances, and closes any remaining /// iterators. This is meant to be called when recycling the instance. pub(crate) fn move_out_of_context( source: &mut Ctx, ) -> (Option, Option) { - let mut b = get_context_data_mut::(source); - // Destroy all existing iterators which are (in contrast to the storage) - // not reused between different instances. - // This is also important because the iterators are pointers to Go memory which should not be stored long term - // Paragraphs 5-7: https://golang.org/cmd/cgo/#hdr-Passing_pointers - destroy_iterators(&mut b); + let b = get_context_data_mut::(source); (b.storage.take(), b.querier.take()) } @@ -194,13 +144,13 @@ pub(crate) fn move_into_context(target: &mut Ctx, storag b.querier = Some(querier); } -pub fn get_gas_state_mut<'a, 'b, S: Storage, Q: Querier + 'b>( +pub fn get_gas_state_mut<'a, 'b, S: Storage + 'b, Q: Querier + 'b>( ctx: &'a mut Ctx, ) -> &'b mut GasState { &mut get_context_data_mut::(ctx).gas_state } -pub fn get_gas_state<'a, 'b, S: Storage, Q: Querier + 'b>(ctx: &'a Ctx) -> &'b GasState { +pub fn get_gas_state<'a, 'b, S: Storage + 'b, Q: Querier + 'b>(ctx: &'a Ctx) -> &'b GasState { &get_context_data::(ctx).gas_state } @@ -270,28 +220,6 @@ pub fn set_storage_readonly(ctx: &mut Ctx, new_value: bo context_data.storage_readonly = new_value; } -/// Add the iterator to the context's data. A new ID is assigned and returned. -/// IDs are guaranteed to be in the range [0, 2**31-1], i.e. fit in the non-negative part if type i32. -#[cfg(feature = "iterator")] -#[must_use = "without the returned iterator ID, the iterator cannot be accessed"] -pub fn add_iterator<'a, S: Storage, Q: Querier>( - ctx: &mut Ctx, - iter: Box, -) -> u32 { - let b = get_context_data_mut::(ctx); - let last_id: u32 = b - .iterators - .len() - .try_into() - .expect("Found more iterator IDs than supported"); - let new_id = last_id + 1; - if new_id > (i32::MAX as u32) { - panic!("Iterator ID exceeded i32::MAX. This must not happen."); - } - b.iterators.insert(new_id, iter); - new_id -} - pub(crate) fn with_func_from_context( ctx: &mut Ctx, name: &str, @@ -314,7 +242,7 @@ where } } -pub(crate) fn with_storage_from_context<'a, 'b, S, Q: 'b, F, T>( +pub(crate) fn with_storage_from_context<'a, 'b, S: 'b, Q: 'b, F, T>( ctx: &'a mut Ctx, func: F, ) -> VmResult @@ -330,7 +258,7 @@ where } } -pub(crate) fn with_querier_from_context<'a, 'b, S, Q: 'b, F, T>( +pub(crate) fn with_querier_from_context<'a, 'b, S: 'b, Q: 'b, F, T>( ctx: &'a mut Ctx, func: F, ) -> VmResult @@ -346,33 +274,13 @@ where } } -#[cfg(feature = "iterator")] -pub(crate) fn with_iterator_from_context<'a, 'b, S, Q: 'b, F, T>( - ctx: &'a mut Ctx, - iterator_id: u32, - func: F, -) -> VmResult -where - S: Storage, - Q: Querier, - F: FnOnce(&'b mut (dyn StorageIterator + 'b)) -> VmResult, -{ - let b = get_context_data_mut::(ctx); - match b.iterators.get_mut(&iterator_id) { - Some(iterator) => func(iterator), - None => Err(VmError::iterator_does_not_exist(iterator_id)), - } -} - #[cfg(test)] mod test { use super::*; + use crate::backend::Storage; use crate::backends::{compile, decrease_gas_left, set_gas_left}; use crate::errors::VmError; - #[cfg(feature = "iterator")] - use crate::testing::MockIterator; use crate::testing::{MockQuerier, MockStorage}; - use crate::traits::Storage; use cosmwasm_std::{ coins, from_binary, to_vec, AllBalanceResponse, BankQuery, Empty, HumanAddr, QueryRequest, }; @@ -409,6 +317,7 @@ mod test { "query_chain" => Func::new(|_a: u32| -> u32 { 0 }), "canonicalize_address" => Func::new(|_a: u32, _b: u32| -> u32 { 0 }), "humanize_address" => Func::new(|_a: u32, _b: u32| -> u32 { 0 }), + "debug" => Func::new(|_a: u32| {}), }, }; let mut instance = Box::from(module.instantiate(&import_obj).unwrap()); @@ -535,29 +444,6 @@ mod test { assert_eq!(is_storage_readonly::(ctx), true); } - #[test] - #[cfg(feature = "iterator")] - fn add_iterator_works() { - let mut instance = make_instance(); - let ctx = instance.context_mut(); - leave_default_data(ctx); - - assert_eq!(get_context_data_mut::(ctx).iterators.len(), 0); - let id1 = add_iterator::(ctx, Box::new(MockIterator::empty())); - let id2 = add_iterator::(ctx, Box::new(MockIterator::empty())); - let id3 = add_iterator::(ctx, Box::new(MockIterator::empty())); - assert_eq!(get_context_data_mut::(ctx).iterators.len(), 3); - assert!(get_context_data_mut::(ctx) - .iterators - .contains_key(&id1)); - assert!(get_context_data_mut::(ctx) - .iterators - .contains_key(&id2)); - assert!(get_context_data_mut::(ctx) - .iterators - .contains_key(&id3)); - } - #[test] fn with_func_from_context_works() { let mut instance = make_instance(); @@ -586,7 +472,7 @@ mod test { }); match res.unwrap_err() { VmError::UninitializedContextData { kind, .. } => assert_eq!(kind, "wasmer_instance"), - e => panic!("Unexpected error: {}", e), + err => panic!("Unexpected error: {:?}", err), } } @@ -606,7 +492,7 @@ mod test { "Wasmer resolve error: ExportNotFound { name: \"doesnt_exist\" }" ); } - e => panic!("Unexpected error: {}", e), + err => panic!("Unexpected error: {:?}", err), } } @@ -689,36 +575,4 @@ mod test { }) .unwrap(); } - - #[test] - #[cfg(feature = "iterator")] - fn with_iterator_from_context_works() { - let mut instance = make_instance(); - let ctx = instance.context_mut(); - leave_default_data(ctx); - - let id = add_iterator::(ctx, Box::new(MockIterator::empty())); - with_iterator_from_context::(ctx, id, |iter| { - assert!(iter.next().0.unwrap().is_none()); - Ok(()) - }) - .expect("must not error"); - } - - #[test] - #[cfg(feature = "iterator")] - fn with_iterator_from_context_errors_for_non_existent_iterator_id() { - let mut instance = make_instance(); - let ctx = instance.context_mut(); - leave_default_data(ctx); - - let missing_id = 42u32; - let result = with_iterator_from_context::(ctx, missing_id, |_iter| { - panic!("this should not be called"); - }); - match result.unwrap_err() { - VmError::IteratorDoesNotExist { id, .. } => assert_eq!(id, missing_id), - e => panic!("Unexpected error: {}", e), - } - } } diff --git a/packages/vm/src/errors/communication_error.rs b/packages/vm/src/errors/communication_error.rs index b33fa38b0..bd15dd19c 100644 --- a/packages/vm/src/errors/communication_error.rs +++ b/packages/vm/src/errors/communication_error.rs @@ -1,96 +1,71 @@ -use snafu::Snafu; use std::fmt::Debug; +use thiserror::Error; use super::region_validation_error::RegionValidationError; /// An error in the communcation between contract and host. Those happen around imports and exports. -#[derive(Debug, Snafu)] +#[derive(Error, Debug)] #[non_exhaustive] pub enum CommunicationError { - #[snafu(display( + #[error( "The Wasm memory address {} provided by the contract could not be dereferenced: {}", offset, msg - ))] + )] DerefErr { /// the position in a Wasm linear memory offset: u32, msg: String, - backtrace: snafu::Backtrace, }, - #[snafu(display("Got an invalid value for iteration order: {}", value))] - InvalidOrder { - value: i32, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Got an invalid region: {}", source))] + #[error("Got an invalid value for iteration order: {}", value)] + InvalidOrder { value: i32 }, + #[error("Got an invalid region: {}", source)] InvalidRegion { - #[snafu(backtrace)] + #[from] source: RegionValidationError, }, /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8. - #[snafu(display("Cannot decode UTF8 bytes into string: {}", msg))] - InvalidUtf8 { - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Region length too big. Got {}, limit {}", length, max_length))] + #[error("Cannot decode UTF8 bytes into string: {}", msg)] + InvalidUtf8 { msg: String }, + #[error("Region length too big. Got {}, limit {}", length, max_length)] // Note: this only checks length, not capacity - RegionLengthTooBig { - length: usize, - max_length: usize, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Region too small. Got {}, required {}", size, required))] - RegionTooSmall { - size: usize, - required: usize, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Got a zero Wasm address"))] - ZeroAddress { backtrace: snafu::Backtrace }, + RegionLengthTooBig { length: usize, max_length: usize }, + #[error("Region too small. Got {}, required {}", size, required)] + RegionTooSmall { size: usize, required: usize }, + #[error("Got a zero Wasm address")] + ZeroAddress {}, } impl CommunicationError { pub(crate) fn deref_err>(offset: u32, msg: S) -> Self { - DerefErr { + CommunicationError::DerefErr { offset, msg: msg.into(), } - .build() } #[allow(dead_code)] pub(crate) fn invalid_order(value: i32) -> Self { - InvalidOrder { value }.build() + CommunicationError::InvalidOrder { value } } #[allow(dead_code)] pub(crate) fn invalid_utf8(msg: S) -> Self { - InvalidUtf8 { + CommunicationError::InvalidUtf8 { msg: msg.to_string(), } - .build() } pub(crate) fn region_length_too_big(length: usize, max_length: usize) -> Self { - RegionLengthTooBig { length, max_length }.build() + CommunicationError::RegionLengthTooBig { length, max_length } } pub(crate) fn region_too_small(size: usize, required: usize) -> Self { - RegionTooSmall { size, required }.build() + CommunicationError::RegionTooSmall { size, required } } pub(crate) fn zero_address() -> Self { - ZeroAddress {}.build() - } -} - -impl From for CommunicationError { - fn from(region_validation_error: RegionValidationError) -> Self { - CommunicationError::InvalidRegion { - source: region_validation_error, - } + CommunicationError::ZeroAddress {} } } diff --git a/packages/vm/src/errors/region_validation_error.rs b/packages/vm/src/errors/region_validation_error.rs index 24dca4d3b..afd5d48ea 100644 --- a/packages/vm/src/errors/region_validation_error.rs +++ b/packages/vm/src/errors/region_validation_error.rs @@ -1,45 +1,37 @@ -use snafu::Snafu; use std::fmt::Debug; +use thiserror::Error; /// An error validating a Region -#[derive(Debug, Snafu)] +#[derive(Error, Debug)] #[non_exhaustive] pub enum RegionValidationError { - #[snafu(display( + #[error( "Region length exceeds capacity. Length {}, capacity {}", length, capacity - ))] - LengthExceedsCapacity { - length: u32, - capacity: u32, - backtrace: snafu::Backtrace, - }, - #[snafu(display( + )] + LengthExceedsCapacity { length: u32, capacity: u32 }, + #[error( "Region exceeds address space. Offset {}, capacity {}", offset, capacity - ))] - OutOfRange { - offset: u32, - capacity: u32, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Got a zero Wasm address in the offset"))] - ZeroOffset { backtrace: snafu::Backtrace }, + )] + OutOfRange { offset: u32, capacity: u32 }, + #[error("Got a zero Wasm address in the offset")] + ZeroOffset {}, } impl RegionValidationError { pub(crate) fn length_exceeds_capacity(length: u32, capacity: u32) -> Self { - LengthExceedsCapacity { length, capacity }.build() + RegionValidationError::LengthExceedsCapacity { length, capacity } } pub(crate) fn out_of_range(offset: u32, capacity: u32) -> Self { - OutOfRange { offset, capacity }.build() + RegionValidationError::OutOfRange { offset, capacity } } pub(crate) fn zero_offset() -> Self { - ZeroOffset {}.build() + RegionValidationError::ZeroOffset {} } } diff --git a/packages/vm/src/errors/vm_error.rs b/packages/vm/src/errors/vm_error.rs index 83d6b4ef9..f1dce0878 100644 --- a/packages/vm/src/errors/vm_error.rs +++ b/packages/vm/src/errors/vm_error.rs @@ -1,106 +1,70 @@ -use snafu::Snafu; use std::fmt::{Debug, Display}; +use thiserror::Error; use super::communication_error::CommunicationError; +use crate::backend::BackendError; use crate::backends::InsufficientGasLeft; -use crate::ffi::FfiError; -#[derive(Debug, Snafu)] +#[derive(Error, Debug)] #[non_exhaustive] pub enum VmError { - #[snafu(display("Cache error: {}", msg))] - CacheErr { - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Error in guest/host communication: {}", source))] + #[error("Cache error: {msg}")] + CacheErr { msg: String }, + #[error("Error in guest/host communication: {source}")] CommunicationErr { - #[snafu(backtrace)] + #[from] source: CommunicationError, }, - #[snafu(display("Error compiling Wasm: {}", msg))] - CompileErr { - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Couldn't convert from {} to {}. Input: {}", from_type, to_type, input))] + #[error("Error compiling Wasm: {msg}")] + CompileErr { msg: String }, + #[error("Couldn't convert from {} to {}. Input: {}", from_type, to_type, input)] ConversionErr { from_type: String, to_type: String, input: String, - backtrace: snafu::Backtrace, }, /// Whenever there is no specific error type available - #[snafu(display("Generic error: {}", msg))] - GenericErr { - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Error instantiating a Wasm module: {}", msg))] - InstantiationErr { - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Hash doesn't match stored data"))] - IntegrityErr { backtrace: snafu::Backtrace }, - #[snafu(display("Iterator with ID {} does not exist", id))] - IteratorDoesNotExist { - id: u32, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Error parsing into type {}: {}", target, msg))] + #[error("Generic error: {msg}")] + GenericErr { msg: String }, + #[error("Error instantiating a Wasm module: {msg}")] + InstantiationErr { msg: String }, + #[error("Hash doesn't match stored data")] + IntegrityErr {}, + #[error("Error parsing into type {target_type}: {msg}")] ParseErr { /// the target type that was attempted - target: String, + target_type: String, msg: String, - backtrace: snafu::Backtrace, }, - #[snafu(display("Error serializing type {}: {}", source, msg))] + #[error("Error serializing type {source_type}: {msg}")] SerializeErr { /// the source type that was attempted - #[snafu(source(false))] - source: String, - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Error resolving Wasm function: {}", msg))] - ResolveErr { - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Error executing Wasm: {}", msg))] - RuntimeErr { - msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Error during static Wasm validation: {}", msg))] - StaticValidationErr { + source_type: String, msg: String, - backtrace: snafu::Backtrace, - }, - #[snafu(display("Uninitialized Context Data: {}", kind))] - UninitializedContextData { - kind: String, - backtrace: snafu::Backtrace, }, - #[snafu(display("Calling external function through FFI: {}", source))] - FfiErr { - #[snafu(backtrace)] - source: FfiError, - }, - #[snafu(display("Ran out of gas during contract execution"))] + #[error("Error resolving Wasm function: {}", msg)] + ResolveErr { msg: String }, + #[error("Error executing Wasm: {}", msg)] + RuntimeErr { msg: String }, + #[error("Error during static Wasm validation: {}", msg)] + StaticValidationErr { msg: String }, + #[error("Uninitialized Context Data: {}", kind)] + UninitializedContextData { kind: String }, + #[error("Error calling into the VM's backend: {}", source)] + BackendErr { source: BackendError }, + #[error("Ran out of gas during contract execution")] GasDepletion, - #[snafu(display("Must not call a writing storage function in this context."))] - WriteAccessDenied { backtrace: snafu::Backtrace }, + #[error("Must not call a writing storage function in this context.")] + WriteAccessDenied {}, } impl VmError { pub(crate) fn cache_err>(msg: S) -> Self { - CacheErr { msg: msg.into() }.build() + VmError::CacheErr { msg: msg.into() } } pub(crate) fn compile_err>(msg: S) -> Self { - CompileErr { msg: msg.into() }.build() + VmError::CompileErr { msg: msg.into() } } pub(crate) fn conversion_err, T: Into, U: Into>( @@ -108,81 +72,65 @@ impl VmError { to_type: T, input: U, ) -> Self { - ConversionErr { + VmError::ConversionErr { from_type: from_type.into(), to_type: to_type.into(), input: input.into(), } - .build() } pub(crate) fn generic_err>(msg: S) -> Self { - GenericErr { msg: msg.into() }.build() + VmError::GenericErr { msg: msg.into() } } pub(crate) fn instantiation_err>(msg: S) -> Self { - InstantiationErr { msg: msg.into() }.build() + VmError::InstantiationErr { msg: msg.into() } } pub(crate) fn integrity_err() -> Self { - IntegrityErr {}.build() - } - - #[cfg(feature = "iterator")] - pub(crate) fn iterator_does_not_exist(iterator_id: u32) -> Self { - IteratorDoesNotExist { id: iterator_id }.build() + VmError::IntegrityErr {} } pub(crate) fn parse_err, M: Display>(target: T, msg: M) -> Self { - ParseErr { - target: target.into(), + VmError::ParseErr { + target_type: target.into(), msg: msg.to_string(), } - .build() } pub(crate) fn serialize_err, M: Display>(source: S, msg: M) -> Self { - SerializeErr { - source: source.into(), + VmError::SerializeErr { + source_type: source.into(), msg: msg.to_string(), } - .build() } pub(crate) fn resolve_err>(msg: S) -> Self { - ResolveErr { msg: msg.into() }.build() + VmError::ResolveErr { msg: msg.into() } } pub(crate) fn runtime_err>(msg: S) -> Self { - RuntimeErr { msg: msg.into() }.build() + VmError::RuntimeErr { msg: msg.into() } } pub(crate) fn static_validation_err>(msg: S) -> Self { - StaticValidationErr { msg: msg.into() }.build() + VmError::StaticValidationErr { msg: msg.into() } } pub(crate) fn uninitialized_context_data>(kind: S) -> Self { - UninitializedContextData { kind: kind.into() }.build() + VmError::UninitializedContextData { kind: kind.into() } } pub(crate) fn write_access_denied() -> Self { - WriteAccessDenied {}.build() - } -} - -impl From for VmError { - fn from(communication_error: CommunicationError) -> Self { - VmError::CommunicationErr { - source: communication_error, - } + VmError::WriteAccessDenied {} } } -impl From for VmError { - fn from(ffi_error: FfiError) -> Self { - match ffi_error { - FfiError::OutOfGas {} => VmError::GasDepletion, - _ => VmError::FfiErr { source: ffi_error }, +impl From for VmError { + fn from(original: BackendError) -> Self { + match original { + BackendError::OutOfGas {} => VmError::GasDepletion, + _ => VmError::BackendErr { source: original }, } } } @@ -308,22 +256,14 @@ mod test { } } - #[test] - #[cfg(feature = "iterator")] - fn iterator_does_not_exist_works() { - let error = VmError::iterator_does_not_exist(15); - match error { - VmError::IteratorDoesNotExist { id, .. } => assert_eq!(id, 15), - e => panic!("Unexpected error: {:?}", e), - } - } - #[test] fn parse_err_works() { let error = VmError::parse_err("Book", "Missing field: title"); match error { - VmError::ParseErr { target, msg, .. } => { - assert_eq!(target, "Book"); + VmError::ParseErr { + target_type, msg, .. + } => { + assert_eq!(target_type, "Book"); assert_eq!(msg, "Missing field: title"); } e => panic!("Unexpected error: {:?}", e), @@ -334,8 +274,10 @@ mod test { fn serialize_err_works() { let error = VmError::serialize_err("Book", "Content too long"); match error { - VmError::SerializeErr { source, msg, .. } => { - assert_eq!(source, "Book"); + VmError::SerializeErr { + source_type, msg, .. + } => { + assert_eq!(source_type, "Book"); assert_eq!(msg, "Content too long"); } e => panic!("Unexpected error: {:?}", e), diff --git a/packages/vm/src/features.rs b/packages/vm/src/features.rs index b7f3611fd..bb38e4d2d 100644 --- a/packages/vm/src/features.rs +++ b/packages/vm/src/features.rs @@ -51,7 +51,6 @@ pub fn required_features_from_module(module: &Module) -> HashSet { mod test { use super::*; use parity_wasm::elements::deserialize_buffer; - use wabt::wat2wasm; #[test] fn features_from_csv_works() { @@ -78,7 +77,7 @@ mod test { #[test] fn required_features_from_module_works() { - let wasm = wat2wasm( + let wasm = wat::parse_str( r#"(module (type (func)) (func (type 0) nop) @@ -102,7 +101,7 @@ mod test { #[test] fn required_features_from_module_works_without_exports_section() { - let wasm = wat2wasm(r#"(module)"#).unwrap(); + let wasm = wat::parse_str(r#"(module)"#).unwrap(); let module = deserialize_buffer(&wasm).unwrap(); let required_features = required_features_from_module(&module); assert_eq!(required_features.len(), 0); diff --git a/packages/vm/src/ffi.rs b/packages/vm/src/ffi.rs deleted file mode 100644 index 0e4fb4077..000000000 --- a/packages/vm/src/ffi.rs +++ /dev/null @@ -1,199 +0,0 @@ -use snafu::Snafu; -use std::fmt::Debug; -use std::string::FromUtf8Error; - -/// A result type for calling into the backend via FFI. Such a call causes -/// non-negligible computational cost and must always have gas information -/// attached. In order to prevent new calls from forgetting such gas information -/// to be passed, the inner success and failure types contain gas information. -pub type FfiResult = (core::result::Result, GasInfo); - -#[derive(Copy, Clone, Debug)] -pub struct GasInfo { - /// The gas cost of a computation that was executed already but not yet charged - pub cost: u64, - /// Gas that was used and charged externally. This is needed to - /// adjust the VM's gas limit but does not affect the gas usage. - pub externally_used: u64, -} - -impl GasInfo { - pub fn with_cost(amount: u64) -> Self { - GasInfo { - cost: amount, - externally_used: 0, - } - } - - pub fn with_externally_used(amount: u64) -> Self { - GasInfo { - cost: 0, - externally_used: amount, - } - } - - /// Creates a gas information with no cost for the caller and with zero externally used gas. - /// - /// Caution: when using this you need to make sure no gas was metered externally to keep the gas values in sync. - pub fn free() -> Self { - GasInfo { - cost: 0, - externally_used: 0, - } - } -} - -#[derive(Debug, Snafu)] -pub enum FfiError { - #[snafu(display("Panic in FFI call"))] - ForeignPanic { backtrace: snafu::Backtrace }, - #[snafu(display("bad argument passed to FFI"))] - BadArgument { backtrace: snafu::Backtrace }, - #[snafu(display("VM received invalid UTF-8 data from backend"))] - InvalidUtf8 { backtrace: snafu::Backtrace }, - #[snafu(display("Ran out of gas during FFI call"))] - OutOfGas {}, - #[snafu(display("Unknown error during FFI call: {:?}", msg))] - Unknown { - msg: Option, - backtrace: snafu::Backtrace, - }, - // This is the only error case of FfiError that is reported back to the contract. - #[snafu(display("User error during FFI call: {}", msg))] - UserErr { - msg: String, - backtrace: snafu::Backtrace, - }, -} - -impl FfiError { - pub fn foreign_panic() -> Self { - ForeignPanic {}.build() - } - - pub fn bad_argument() -> Self { - BadArgument {}.build() - } - - pub fn out_of_gas() -> Self { - OutOfGas {}.build() - } - - pub fn unknown(msg: S) -> Self { - Unknown { - msg: Some(msg.to_string()), - } - .build() - } - - /// Use `::unknown(msg: S)` if possible - pub fn unknown_without_message() -> Self { - Unknown { msg: None }.build() - } - - pub fn user_err(msg: S) -> Self { - UserErr { - msg: msg.to_string(), - } - .build() - } -} - -impl From for FfiError { - fn from(_original: FromUtf8Error) -> Self { - InvalidUtf8 {}.build() - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn gas_info_with_cost_works() { - let gas_info = GasInfo::with_cost(21); - assert_eq!(gas_info.cost, 21); - assert_eq!(gas_info.externally_used, 0); - } - - #[test] - fn gas_info_with_externally_used_works() { - let gas_info = GasInfo::with_externally_used(65); - assert_eq!(gas_info.cost, 0); - assert_eq!(gas_info.externally_used, 65); - } - - #[test] - fn gas_info_free_works() { - let gas_info = GasInfo::free(); - assert_eq!(gas_info.cost, 0); - assert_eq!(gas_info.externally_used, 0); - } - - // constructors - - #[test] - fn ffi_error_foreign_panic() { - let error = FfiError::foreign_panic(); - match error { - FfiError::ForeignPanic { .. } => {} - e => panic!("Unexpected error: {:?}", e), - } - } - - #[test] - fn ffi_error_bad_argument() { - let error = FfiError::bad_argument(); - match error { - FfiError::BadArgument { .. } => {} - e => panic!("Unexpected error: {:?}", e), - } - } - - #[test] - fn ffi_error_out_of_gas() { - let error = FfiError::out_of_gas(); - match error { - FfiError::OutOfGas { .. } => {} - e => panic!("Unexpected error: {:?}", e), - } - } - - #[test] - fn ffi_error_unknown() { - let error = FfiError::unknown("broken"); - match error { - FfiError::Unknown { msg, .. } => assert_eq!(msg.unwrap(), "broken"), - e => panic!("Unexpected error: {:?}", e), - } - } - - #[test] - fn ffi_error_unknown_without_message() { - let error = FfiError::unknown_without_message(); - match error { - FfiError::Unknown { msg, .. } => assert!(msg.is_none()), - e => panic!("Unexpected error: {:?}", e), - } - } - - #[test] - fn ffi_error_user_err() { - let error = FfiError::user_err("invalid input"); - match error { - FfiError::UserErr { msg, .. } => assert_eq!(msg, "invalid input"), - e => panic!("Unexpected error: {:?}", e), - } - } - - // conversions - - #[test] - fn convert_from_fromutf8error() { - let error: FfiError = String::from_utf8(vec![0x80]).unwrap_err().into(); - match error { - FfiError::InvalidUtf8 { .. } => {} - e => panic!("Unexpected error: {:?}", e), - } - } -} diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index b58de59ef..4c48cdeee 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -8,24 +8,23 @@ use cosmwasm_std::Order; use cosmwasm_std::{Binary, CanonicalAddr, HumanAddr}; use wasmer_runtime_core::vm::Ctx; +use crate::backend::{Api, BackendError, Querier, Storage}; use crate::backends::get_gas_left; -#[cfg(feature = "iterator")] -use crate::context::{add_iterator, with_iterator_from_context}; use crate::context::{ is_storage_readonly, process_gas_info, with_func_from_context, with_querier_from_context, with_storage_from_context, }; use crate::conversion::to_u32; use crate::errors::{CommunicationError, VmError, VmResult}; -use crate::ffi::FfiError; #[cfg(feature = "iterator")] use crate::memory::maybe_read_region; use crate::memory::{read_region, write_region}; use crate::serde::to_vec; -use crate::traits::{Api, Querier, Storage}; /// A kibi (kilo binary) const KI: usize = 1024; +/// A mibi (mega binary) +const MI: usize = 1024 * 1024; /// Max key length for db_write (i.e. when VM reads from Wasm memory) const MAX_LENGTH_DB_KEY: usize = 64 * KI; /// Max key length for db_write (i.e. when VM reads from Wasm memory) @@ -35,6 +34,8 @@ const MAX_LENGTH_CANONICAL_ADDRESS: usize = 32; /// The maximum allowed size for bech32 (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) const MAX_LENGTH_HUMAN_ADDRESS: usize = 90; const MAX_LENGTH_QUERY_CHAIN_REQUEST: usize = 64 * KI; +/// Max length for a debug message +const MAX_LENGTH_DEBUG: usize = 2 * MI; /// Reads a storage entry from the VM's storage into Wasm memory pub fn do_read(ctx: &mut Ctx, key_ptr: u32) -> VmResult { @@ -112,7 +113,9 @@ pub fn do_canonicalize_address( write_region(ctx, destination_ptr, canonical.as_slice())?; Ok(0) } - Err(FfiError::UserErr { msg, .. }) => Ok(write_to_contract::(ctx, msg.as_bytes())?), + Err(BackendError::UserErr { msg, .. }) => { + Ok(write_to_contract::(ctx, msg.as_bytes())?) + } Err(err) => Err(VmError::from(err)), } } @@ -132,11 +135,22 @@ pub fn do_humanize_address( write_region(ctx, destination_ptr, human.as_str().as_bytes())?; Ok(0) } - Err(FfiError::UserErr { msg, .. }) => Ok(write_to_contract::(ctx, msg.as_bytes())?), + Err(BackendError::UserErr { msg, .. }) => { + Ok(write_to_contract::(ctx, msg.as_bytes())?) + } Err(err) => Err(VmError::from(err)), } } +/// Prints a debug message to console. +/// This does not charge gas, so debug printing should be disabled when used in a blockchain module. +pub fn print_debug_message(ctx: &mut Ctx, message_ptr: u32) -> VmResult<()> { + let message_data = read_region(ctx, message_ptr, MAX_LENGTH_DEBUG)?; + let msg = String::from_utf8_lossy(&message_data); + println!("{}", msg); + Ok(()) +} + /// Creates a Region in the contract, writes the given data to it and returns the memory location fn write_to_contract(ctx: &mut Ctx, input: &[u8]) -> VmResult { let target_ptr = with_func_from_context::(ctx, "allocate", |allocate| { @@ -164,7 +178,7 @@ pub fn do_query_chain(ctx: &mut Ctx, request_ptr: u32) - } #[cfg(feature = "iterator")] -pub fn do_scan( +pub fn do_scan( ctx: &mut Ctx, start_ptr: u32, end_ptr: u32, @@ -177,18 +191,17 @@ pub fn do_scan( .map_err(|_| CommunicationError::invalid_order(order))?; let (result, gas_info) = with_storage_from_context::(ctx, |store| { - Ok(store.range(start.as_deref(), end.as_deref(), order)) + Ok(store.scan(start.as_deref(), end.as_deref(), order)) })?; process_gas_info::(ctx, gas_info)?; - let iterator = result?; - let iterator_id = add_iterator::(ctx, iterator); + let iterator_id = result?; Ok(iterator_id) } #[cfg(feature = "iterator")] pub fn do_next(ctx: &mut Ctx, iterator_id: u32) -> VmResult { let (result, gas_info) = - with_iterator_from_context::(ctx, iterator_id, |iter| Ok(iter.next()))?; + with_storage_from_context::(ctx, |store| Ok(store.next(iterator_id)))?; process_gas_info::(ctx, gas_info)?; // Empty key will later be treated as _no more element_. @@ -209,18 +222,17 @@ mod test { use super::*; use cosmwasm_std::{ coins, from_binary, AllBalanceResponse, BankQuery, Empty, HumanAddr, QueryRequest, - SystemError, WasmQuery, + SystemError, SystemResult, WasmQuery, }; use std::ptr::NonNull; use wasmer_runtime_core::{imports, typed_func::Func, Instance as WasmerInstance}; + use crate::backend::{BackendError, Storage}; use crate::backends::compile; use crate::context::{ move_into_context, set_storage_readonly, set_wasmer_instance, setup_context, }; use crate::testing::{MockApi, MockQuerier, MockStorage}; - use crate::traits::Storage; - use crate::FfiError; static CONTRACT: &[u8] = include_bytes!("../testdata/contract.wasm"); @@ -256,6 +268,7 @@ mod test { "query_chain" => Func::new(|_a: u32| -> u32 { 0 }), "canonicalize_address" => Func::new(|_a: i32, _b: i32| -> u32 { 0 }), "humanize_address" => Func::new(|_a: i32, _b: i32| -> u32 { 0 }), + "debug" => Func::new(|_a: u32| {}), }, }; let mut instance = Box::from(module.instantiate(&import_obj).unwrap()); @@ -557,16 +570,17 @@ mod test { #[test] fn do_canonicalize_address_works() { let mut instance = make_instance(); + let api = MockApi::default(); let source_ptr = write_data(&mut instance, b"foo"); - let dest_ptr = create_empty(&mut instance, 8); + let dest_ptr = create_empty(&mut instance, api.canonical_length as u32); let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); do_canonicalize_address::(api, ctx, source_ptr, dest_ptr).unwrap(); - assert_eq!(force_read(ctx, dest_ptr), b"foo\0\0\0\0\0"); + let data = force_read(ctx, dest_ptr); + assert_eq!(data.len(), api.canonical_length); } #[test] @@ -580,7 +594,7 @@ mod test { let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); + let api = MockApi::default(); let res = do_canonicalize_address::(api, ctx, source_ptr1, dest_ptr).unwrap(); assert_ne!(res, 0); @@ -608,11 +622,11 @@ mod test { let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new_failing(8, "Temporarily unavailable"); + let api = MockApi::new_failing("Temporarily unavailable"); let result = do_canonicalize_address::(api, ctx, source_ptr, dest_ptr); match result.unwrap_err() { - VmError::FfiErr { - source: FfiError::Unknown { msg, .. }, + VmError::BackendErr { + source: BackendError::Unknown { msg, .. }, } => { assert_eq!(msg.unwrap(), "Temporarily unavailable"); } @@ -630,7 +644,7 @@ mod test { let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); + let api = MockApi::default(); let result = do_canonicalize_address::(api, ctx, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { @@ -656,14 +670,14 @@ mod test { let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); + let api = MockApi::default(); let result = do_canonicalize_address::(api, ctx, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { source: CommunicationError::RegionTooSmall { size, required, .. }, } => { assert_eq!(size, 7); - assert_eq!(required, 8); + assert_eq!(required, api.canonical_length); } err => panic!("Incorrect error returned: {:?}", err), } @@ -672,17 +686,18 @@ mod test { #[test] fn do_humanize_address_works() { let mut instance = make_instance(); + let api = MockApi::default(); - let source_ptr = write_data(&mut instance, b"foo\0\0\0\0\0"); + let source_data = vec![0x22; api.canonical_length]; + let source_ptr = write_data(&mut instance, &source_data); let dest_ptr = create_empty(&mut instance, 50); let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); let error_ptr = do_humanize_address::(api, ctx, source_ptr, dest_ptr).unwrap(); assert_eq!(error_ptr, 0); - assert_eq!(force_read(ctx, dest_ptr), b"foo"); + assert_eq!(force_read(ctx, dest_ptr), source_data); } #[test] @@ -695,7 +710,7 @@ mod test { let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); + let api = MockApi::default(); let res = do_humanize_address::(api, ctx, source_ptr, dest_ptr).unwrap(); assert_ne!(res, 0); let err = String::from_utf8(force_read(ctx, res)).unwrap(); @@ -712,11 +727,11 @@ mod test { let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new_failing(8, "Temporarily unavailable"); + let api = MockApi::new_failing("Temporarily unavailable"); let result = do_humanize_address::(api, ctx, source_ptr, dest_ptr); match result.unwrap_err() { - VmError::FfiErr { - source: FfiError::Unknown { msg, .. }, + VmError::BackendErr { + source: BackendError::Unknown { msg, .. }, } => assert_eq!(msg.unwrap(), "Temporarily unavailable"), err => panic!("Incorrect error returned: {:?}", err), }; @@ -732,7 +747,7 @@ mod test { let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); + let api = MockApi::default(); let result = do_humanize_address::(api, ctx, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { @@ -751,21 +766,22 @@ mod test { #[test] fn do_humanize_address_fails_for_destination_region_too_small() { let mut instance = make_instance(); + let api = MockApi::default(); - let source_ptr = write_data(&mut instance, b"foo\0\0\0\0\0"); + let source_data = vec![0x22; api.canonical_length]; + let source_ptr = write_data(&mut instance, &source_data); let dest_ptr = create_empty(&mut instance, 2); let ctx = instance.context_mut(); leave_default_data(ctx); - let api = MockApi::new(8); let result = do_humanize_address::(api, ctx, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { source: CommunicationError::RegionTooSmall { size, required, .. }, } => { assert_eq!(size, 2); - assert_eq!(required, 3); + assert_eq!(required, api.canonical_length); } err => panic!("Incorrect error returned: {:?}", err), } @@ -811,11 +827,11 @@ mod test { let query_result: cosmwasm_std::QuerierResult = cosmwasm_std::from_slice(&response).unwrap(); match query_result { - Ok(_) => panic!("This must not succeed"), - Err(SystemError::InvalidRequest { request: err, .. }) => { + SystemResult::Ok(_) => panic!("This must not succeed"), + SystemResult::Err(SystemError::InvalidRequest { request: err, .. }) => { assert_eq!(err.as_slice(), request) } - Err(error) => panic!("Unexpeted error: {:?}", error), + SystemResult::Err(err) => panic!("Unexpected error: {:?}", err), } } @@ -839,11 +855,11 @@ mod test { let query_result: cosmwasm_std::QuerierResult = cosmwasm_std::from_slice(&response).unwrap(); match query_result { - Ok(_) => panic!("This must not succeed"), - Err(SystemError::NoSuchContract { addr }) => { + SystemResult::Ok(_) => panic!("This must not succeed"), + SystemResult::Err(SystemError::NoSuchContract { addr }) => { assert_eq!(addr, HumanAddr::from("non-existent")) } - Err(error) => panic!("Unexpeted error: {:?}", error), + SystemResult::Err(err) => panic!("Unexpected error: {:?}", err), } } @@ -859,15 +875,15 @@ mod test { assert_eq!(1, id); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY1.to_vec(), VALUE1.to_vec())); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY2.to_vec(), VALUE2.to_vec())); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert!(item.0.unwrap().is_none()); } @@ -883,15 +899,15 @@ mod test { assert_eq!(1, id); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY2.to_vec(), VALUE2.to_vec())); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY1.to_vec(), VALUE1.to_vec())); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert!(item.0.unwrap().is_none()); } @@ -909,11 +925,11 @@ mod test { let id = do_scan::(ctx, start, end, Order::Ascending.into()).unwrap(); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY1.to_vec(), VALUE1.to_vec())); let item = - with_iterator_from_context::(ctx, id, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id))).unwrap(); assert!(item.0.unwrap().is_none()); } @@ -932,27 +948,27 @@ mod test { // first item, first iterator let item = - with_iterator_from_context::(ctx, id1, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id1))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY1.to_vec(), VALUE1.to_vec())); // second item, first iterator let item = - with_iterator_from_context::(ctx, id1, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id1))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY2.to_vec(), VALUE2.to_vec())); // first item, second iterator let item = - with_iterator_from_context::(ctx, id2, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id2))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY2.to_vec(), VALUE2.to_vec())); // end, first iterator let item = - with_iterator_from_context::(ctx, id1, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id1))).unwrap(); assert!(item.0.unwrap().is_none()); // second item, second iterator let item = - with_iterator_from_context::(ctx, id2, |iter| Ok(iter.next())).unwrap(); + with_storage_from_context::(ctx, |store| Ok(store.next(id2))).unwrap(); assert_eq!(item.0.unwrap().unwrap(), (KEY1.to_vec(), VALUE1.to_vec())); } @@ -1014,7 +1030,9 @@ mod test { let non_existent_id = 42u32; let result = do_next::(ctx, non_existent_id); match result.unwrap_err() { - VmError::IteratorDoesNotExist { id, .. } => assert_eq!(id, non_existent_id), + VmError::BackendErr { + source: BackendError::IteratorDoesNotExist { id, .. }, + } => assert_eq!(id, non_existent_id), e => panic!("Unexpected error: {:?}", e), } } diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index 982452c33..594398be7 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -11,6 +11,7 @@ use wasmer_runtime_core::{ Instance as WasmerInstance, }; +use crate::backend::{Api, Backend, Querier, Storage}; use crate::backends::{compile, get_gas_left, set_gas_left}; use crate::context::{ get_gas_state, get_gas_state_mut, move_into_context, move_out_of_context, set_storage_readonly, @@ -21,11 +22,11 @@ use crate::errors::{CommunicationError, VmError, VmResult}; use crate::features::required_features_from_wasmer_instance; use crate::imports::{ do_canonicalize_address, do_humanize_address, do_query_chain, do_read, do_remove, do_write, + print_debug_message, }; #[cfg(feature = "iterator")] use crate::imports::{do_next, do_scan}; use crate::memory::{get_memory_info, read_region, write_region}; -use crate::traits::{Api, Extern, Querier, Storage}; const WASM_PAGE_SIZE: u64 = 64 * 1024; @@ -42,7 +43,13 @@ pub struct GasReport { pub used_internally: u64, } -pub struct Instance { +#[derive(Copy, Clone, Debug)] +pub struct InstanceOptions { + pub gas_limit: u64, + pub print_debug: bool, +} + +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 @@ -56,27 +63,32 @@ pub struct Instance Instance where - S: Storage + 'static, - A: Api + 'static, - Q: Querier + 'static, + S: Storage, + A: Api + 'static, // 'static is needed here to allow copying API instances into closures + Q: Querier, { /// This is the only Instance constructor that can be called from outside of cosmwasm-vm, /// e.g. in test code that needs a customized variant of cosmwasm_vm::testing::mock_instance*. - pub fn from_code(code: &[u8], deps: Extern, gas_limit: u64) -> VmResult { + pub fn from_code( + code: &[u8], + backend: Backend, + options: InstanceOptions, + ) -> VmResult { let module = compile(code)?; - Instance::from_module(&module, deps, gas_limit) + Instance::from_module(&module, backend, options.gas_limit, options.print_debug) } pub(crate) fn from_module( module: &Module, - deps: Extern, + backend: Backend, gas_limit: u64, + print_debug: bool, ) -> VmResult { let mut import_obj = imports! { move || { setup_context::(gas_limit) }, "env" => {}, }; // copy this so it can be moved into the closures, without pulling in deps - let api = deps.api; + let api = backend.api; import_obj.extend(imports! { "env" => { // Reads the database entry at the given key into the the value. @@ -112,6 +124,16 @@ where "humanize_address" => Func::new(move |ctx: &mut Ctx, source_ptr: u32, destination_ptr: u32| -> VmResult { do_humanize_address::(api, ctx, source_ptr, destination_ptr) }), + // Allows the contract to emit debug logs that the host can either process or ignore. + // This is never written to chain. + // Takes a pointer argument of a memory region that must contain an UTF-8 encoded string. + // Ownership of both input and output pointer is not transferred to the host. + "debug" => Func::new(move |ctx: &mut Ctx, message_ptr: u32|-> VmResult<()> { + if print_debug { + print_debug_message(ctx, message_ptr)?; + } + Ok(()) + }), "query_chain" => Func::new(move |ctx: &mut Ctx, request_ptr: u32| -> VmResult { do_query_chain::(ctx, request_ptr) }), @@ -141,37 +163,36 @@ where }, }); - let wasmer_instance = Box::from(module.instantiate(&import_obj).map_err(|original| { - VmError::instantiation_err(format!("Error instantiating module: {:?}", original)) - })?); - Ok(Instance::from_wasmer(wasmer_instance, deps, gas_limit)) - } + let mut wasmer_instance = + Box::from(module.instantiate(&import_obj).map_err(|original| { + VmError::instantiation_err(format!("Error instantiating module: {:?}", original)) + })?); - pub(crate) fn from_wasmer( - mut wasmer_instance: Box, - deps: Extern, - gas_limit: u64, - ) -> Self { set_gas_left(wasmer_instance.context_mut(), gas_limit); get_gas_state_mut::(wasmer_instance.context_mut()).set_gas_limit(gas_limit); let required_features = required_features_from_wasmer_instance(wasmer_instance.as_ref()); let instance_ptr = NonNull::from(wasmer_instance.as_ref()); set_wasmer_instance::(wasmer_instance.context_mut(), Some(instance_ptr)); - move_into_context(wasmer_instance.context_mut(), deps.storage, deps.querier); - Instance { + move_into_context( + wasmer_instance.context_mut(), + backend.storage, + backend.querier, + ); + let instance = Instance { inner: wasmer_instance, - api: deps.api, + api: backend.api, required_features, type_storage: PhantomData:: {}, type_querier: PhantomData:: {}, - } + }; + Ok(instance) } /// Decomposes this instance into its components. /// External dependencies are returned for reuse, the rest is dropped. - pub fn recycle(mut self) -> Option> { + pub fn recycle(mut self) -> Option> { if let (Some(storage), Some(querier)) = move_out_of_context(self.inner.context_mut()) { - Some(Extern { + Some(Backend { storage, api: self.api, querier, @@ -267,23 +288,22 @@ where #[cfg(test)] mod test { use super::*; + use crate::backend::Storage; use crate::context::is_storage_readonly; use crate::errors::VmError; use crate::testing::{ - mock_dependencies, mock_env, mock_instance, mock_instance_with_balances, - mock_instance_with_failing_api, mock_instance_with_gas_limit, MockQuerier, MockStorage, + mock_backend, mock_env, mock_info, mock_instance, mock_instance_options, + mock_instance_with_balances, mock_instance_with_failing_api, mock_instance_with_gas_limit, + MockQuerier, MockStorage, }; - use crate::traits::Storage; - use crate::{call_init, FfiError}; + use crate::{call_init, BackendError}; use cosmwasm_std::{ coin, coins, from_binary, AllBalanceResponse, BalanceResponse, BankQuery, Empty, HumanAddr, QueryRequest, }; - use wabt::wat2wasm; const KIB: usize = 1024; const MIB: usize = 1024 * 1024; - const DEFAULT_GAS_LIMIT: u64 = 500_000; const DEFAULT_QUERY_GAS_LIMIT: u64 = 300_000; static CONTRACT: &[u8] = include_bytes!("../testdata/contract.wasm"); @@ -293,14 +313,14 @@ mod test { #[test] fn required_features_works() { - let deps = mock_dependencies(20, &[]); - let instance = Instance::from_code(CONTRACT, deps, DEFAULT_GAS_LIMIT).unwrap(); + let backend = mock_backend(&[]); + let instance = Instance::from_code(CONTRACT, backend, mock_instance_options()).unwrap(); assert_eq!(instance.required_features.len(), 0); } #[test] fn required_features_works_for_many_exports() { - let wasm = wat2wasm( + let wasm = wat::parse_str( r#"(module (type (func)) (func (type 0) nop) @@ -314,8 +334,8 @@ mod test { ) .unwrap(); - let deps = mock_dependencies(20, &[]); - let instance = Instance::from_code(&wasm, deps, DEFAULT_GAS_LIMIT).unwrap(); + let backend = mock_backend(&[]); + let instance = Instance::from_code(&wasm, backend, mock_instance_options()).unwrap(); assert_eq!(instance.required_features.len(), 3); assert!(instance.required_features.contains("nutrients")); assert!(instance.required_features.contains("sun")); @@ -420,15 +440,16 @@ mod test { let mut instance = mock_instance_with_failing_api(&CONTRACT, &[], error_message); let init_result = call_init::<_, _, _, serde_json::Value>( &mut instance, - &mock_env("someone", &[]), + &mock_env(), + &mock_info("someone", &[]), b"{\"verifier\": \"some1\", \"beneficiary\": \"some2\"}", ); - // in this case we get a `VmError::FfiError` rather than a `VmError::RuntimeErr` because the conversion + // in this case we get a `VmError::BackendErr` rather than a `VmError::RuntimeErr` because the conversion // from wasmer `RuntimeError` to `VmError` unwraps errors that happen in WASM imports. match init_result.unwrap_err() { - VmError::FfiErr { - source: FfiError::Unknown { msg, .. }, + VmError::BackendErr { + source: BackendError::Unknown { msg, .. }, } if msg == Some(error_message.to_string()) => {} other => panic!("unexpected error: {:?}", other), } @@ -511,9 +532,9 @@ mod test { assert_eq!(report1.remaining, FAKE_REMANING); // init contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); - call_init::<_, _, _, Empty>(&mut instance, &env, msg) + call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) .unwrap() .unwrap(); @@ -537,15 +558,15 @@ mod test { assert_eq!(report1.remaining, LIMIT); // init contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); - call_init::<_, _, _, Empty>(&mut instance, &env, msg) + call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) .unwrap() .unwrap(); let report2 = instance.create_gas_report(); - assert_eq!(report2.used_externally, 134); - assert_eq!(report2.used_internally, 63027); + assert_eq!(report2.used_externally, 146); + assert_eq!(report2.used_internally, 76371); assert_eq!(report2.limit, LIMIT); assert_eq!( report2.remaining, @@ -740,7 +761,7 @@ mod singlepass_test { use cosmwasm_std::{coins, Empty}; use crate::calls::{call_handle, call_init, call_query}; - use crate::testing::{mock_env, mock_instance, mock_instance_with_gas_limit}; + use crate::testing::{mock_env, mock_info, mock_instance, mock_instance_with_gas_limit}; static CONTRACT: &[u8] = include_bytes!("../testdata/contract.wasm"); @@ -750,15 +771,14 @@ mod singlepass_test { let orig_gas = instance.get_gas_left(); // init contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); - call_init::<_, _, _, Empty>(&mut instance, &env, msg) + call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) .unwrap() .unwrap(); let init_used = orig_gas - instance.get_gas_left(); - println!("init used: {}", init_used); - assert_eq!(init_used, 63161); + assert_eq!(init_used, 76517); } #[test] @@ -766,23 +786,22 @@ mod singlepass_test { let mut instance = mock_instance(&CONTRACT, &[]); // init contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); - call_init::<_, _, _, Empty>(&mut instance, &env, msg) + call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) .unwrap() .unwrap(); // run contract - just sanity check - results validate in contract unit tests let gas_before_handle = instance.get_gas_left(); - let env = mock_env("verifies", &coins(15, "earth")); + let info = mock_info("verifies", &coins(15, "earth")); let msg = br#"{"release":{}}"#; - call_handle::<_, _, _, Empty>(&mut instance, &env, msg) + call_handle::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) .unwrap() .unwrap(); let handle_used = gas_before_handle - instance.get_gas_left(); - println!("handle used: {}", handle_used); - assert_eq!(handle_used, 193119); + assert_eq!(handle_used, 208653); } #[test] @@ -790,9 +809,9 @@ mod singlepass_test { let mut instance = mock_instance_with_gas_limit(&CONTRACT, 20_000); // init contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); - let res = call_init::<_, _, _, Empty>(&mut instance, &env, msg); + let res = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg); assert!(res.is_err()); } @@ -801,9 +820,9 @@ mod singlepass_test { let mut instance = mock_instance(&CONTRACT, &[]); // init contract - let env = mock_env("creator", &coins(1000, "earth")); + let info = mock_info("creator", &coins(1000, "earth")); let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); - let _res = call_init::<_, _, _, Empty>(&mut instance, &env, msg) + let _res = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) .unwrap() .unwrap(); @@ -811,12 +830,11 @@ mod singlepass_test { let gas_before_query = instance.get_gas_left(); // we need to encode the key in base64 let msg = r#"{"verifier":{}}"#.as_bytes(); - let res = call_query(&mut instance, msg).unwrap(); + let res = call_query(&mut instance, &mock_env(), msg).unwrap(); let answer = res.unwrap(); assert_eq!(answer.as_slice(), b"{\"verifier\":\"verifies\"}"); let query_used = gas_before_query - instance.get_gas_left(); - println!("query used: {}", query_used); - assert_eq!(query_used, 32070); + assert_eq!(query_used, 61219); } } diff --git a/packages/vm/src/lib.rs b/packages/vm/src/lib.rs index 75301acbf..21413e717 100644 --- a/packages/vm/src/lib.rs +++ b/packages/vm/src/lib.rs @@ -1,23 +1,25 @@ +mod backend; mod backends; mod cache; mod calls; mod checksum; -mod compatability; +mod compatibility; mod context; mod conversion; mod errors; mod features; -mod ffi; mod imports; mod instance; +mod limited; mod memory; mod middleware; mod modules; mod serde; +mod size; pub mod testing; -mod traits; -pub use crate::cache::CosmCache; +pub use crate::backend::{Api, Backend, BackendError, BackendResult, GasInfo, Querier, Storage}; +pub use crate::cache::{Cache, CacheOptions}; pub use crate::calls::{ call_handle, call_handle_raw, call_init, call_init_raw, call_migrate, call_migrate_raw, call_query, call_query_raw, @@ -28,11 +30,6 @@ pub use crate::errors::{ VmError, VmResult, }; pub use crate::features::features_from_csv; -pub use crate::ffi::{FfiError, FfiResult, GasInfo}; -pub use crate::instance::{GasReport, Instance}; -pub use crate::modules::FileSystemCache; +pub use crate::instance::{GasReport, Instance, InstanceOptions}; pub use crate::serde::{from_slice, to_vec}; -pub use crate::traits::{Api, Extern, Querier, Storage}; - -#[cfg(feature = "iterator")] -pub use crate::traits::StorageIterator; +pub use crate::size::Size; diff --git a/packages/vm/src/limited.rs b/packages/vm/src/limited.rs new file mode 100644 index 000000000..d7814cd2a --- /dev/null +++ b/packages/vm/src/limited.rs @@ -0,0 +1,226 @@ +//! A set of tools designed for processing user defined contract data, +//! which can potientially have abusive size. + +use std::collections::{BTreeSet, HashSet}; +use std::iter::FromIterator; + +pub trait LimitedDisplay { + /// Returns a string representationof the object, which is shorter than or equal to `max_length`. + /// Implementations must panic if `max_length` is not reasonably large. + fn to_string_limited(&self, max_length: usize) -> String; +} + +impl> LimitedDisplay for BTreeSet { + fn to_string_limited(&self, max_length: usize) -> String { + collection_to_string_limited(self.iter(), max_length, "{", "}") + } +} + +impl> LimitedDisplay for HashSet { + fn to_string_limited(&self, max_length: usize) -> String { + // Iteration order in HashSet is undeterminstic. We sort + // here to be on the safe side and to simplify testing. + let sorted = BTreeSet::from_iter(self); + sorted.to_string_limited(max_length) + } +} + +impl> LimitedDisplay for Vec { + fn to_string_limited(&self, max_length: usize) -> String { + collection_to_string_limited(self.iter(), max_length, "[", "]") + } +} + +/// Iterates over a collection and returns a length limited +/// string representation of it, using `opening` and `closing` +/// to surround the collection's content. +fn collection_to_string_limited, I: ExactSizeIterator>( + iter: I, + max_length: usize, + opening: &str, + closing: &str, +) -> String { + let elements_count = iter.len(); + let mut out = String::with_capacity(max_length * 130 / 100); + + let mut first = true; + out.push_str(opening); + let mut lengths_stack = Vec::::new(); + for element in iter { + lengths_stack.push(out.len()); + + if first { + out.push('"'); + first = false; + } else { + out.push_str(", \""); + } + out.push_str(element.as_ref()); + out.push('"'); + + if out.len() > max_length { + break; + }; + } + + if out.len() + closing.len() <= max_length { + out.push_str(closing); + out + } else { + loop { + let previous_length = lengths_stack + .pop() + .expect("Cannot remove hide enough elements to fit in length limit."); + let skipped = elements_count - lengths_stack.len(); + let remaining = elements_count - skipped; + let skipped_text = if remaining == 0 { + format!("... {} elements", skipped) + } else { + format!(", ... {} more", skipped) + }; + if previous_length + skipped_text.len() + closing.len() <= max_length { + out.truncate(previous_length); + out.push_str(&skipped_text); + out.push_str(closing); + return out; + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn works_for_btreeset() { + let set = BTreeSet::::new(); + assert_eq!(set.to_string_limited(100), "{}"); + assert_eq!(set.to_string_limited(20), "{}"); + assert_eq!(set.to_string_limited(2), "{}"); + + let fruits = BTreeSet::from_iter( + [ + "watermelon".to_string(), + "apple".to_string(), + "banana".to_string(), + ] + .iter() + .cloned(), + ); + assert_eq!( + fruits.to_string_limited(100), + "{\"apple\", \"banana\", \"watermelon\"}" + ); + assert_eq!( + fruits.to_string_limited(33), + "{\"apple\", \"banana\", \"watermelon\"}" + ); + assert_eq!( + fruits.to_string_limited(32), + "{\"apple\", \"banana\", ... 1 more}" + ); + assert_eq!( + fruits.to_string_limited(31), + "{\"apple\", \"banana\", ... 1 more}" + ); + assert_eq!(fruits.to_string_limited(30), "{\"apple\", ... 2 more}"); + assert_eq!(fruits.to_string_limited(21), "{\"apple\", ... 2 more}"); + assert_eq!(fruits.to_string_limited(20), "{... 3 elements}"); + assert_eq!(fruits.to_string_limited(16), "{... 3 elements}"); + } + + #[test] + fn works_for_hashset() { + let set = HashSet::::new(); + assert_eq!(set.to_string_limited(100), "{}"); + assert_eq!(set.to_string_limited(20), "{}"); + assert_eq!(set.to_string_limited(2), "{}"); + + let fruits = HashSet::from_iter( + [ + "watermelon".to_string(), + "apple".to_string(), + "banana".to_string(), + ] + .iter() + .cloned(), + ); + assert_eq!( + fruits.to_string_limited(100), + "{\"apple\", \"banana\", \"watermelon\"}" + ); + assert_eq!( + fruits.to_string_limited(33), + "{\"apple\", \"banana\", \"watermelon\"}" + ); + assert_eq!( + fruits.to_string_limited(32), + "{\"apple\", \"banana\", ... 1 more}" + ); + assert_eq!( + fruits.to_string_limited(31), + "{\"apple\", \"banana\", ... 1 more}" + ); + assert_eq!(fruits.to_string_limited(30), "{\"apple\", ... 2 more}"); + assert_eq!(fruits.to_string_limited(21), "{\"apple\", ... 2 more}"); + assert_eq!(fruits.to_string_limited(20), "{... 3 elements}"); + assert_eq!(fruits.to_string_limited(16), "{... 3 elements}"); + } + + #[test] + #[should_panic(expected = "Cannot remove hide enough elements to fit in length limit.")] + fn panics_if_limit_is_too_small_empty() { + let set = HashSet::::new(); + assert_eq!(set.to_string_limited(1), "{}"); + } + + #[test] + #[should_panic(expected = "Cannot remove hide enough elements to fit in length limit.")] + fn panics_if_limit_is_too_small_nonempty() { + let fruits = HashSet::from_iter( + [ + "watermelon".to_string(), + "apple".to_string(), + "banana".to_string(), + ] + .iter() + .cloned(), + ); + assert_eq!(fruits.to_string_limited(15), "{... 3 elements}"); + } + + #[test] + fn works_for_vectors() { + let list = Vec::::new(); + assert_eq!(list.to_string_limited(100), "[]"); + assert_eq!(list.to_string_limited(20), "[]"); + assert_eq!(list.to_string_limited(2), "[]"); + + let fruits = vec![ + "banana".to_string(), + "apple".to_string(), + "watermelon".to_string(), + ]; + assert_eq!( + fruits.to_string_limited(100), + "[\"banana\", \"apple\", \"watermelon\"]" + ); + assert_eq!( + fruits.to_string_limited(33), + "[\"banana\", \"apple\", \"watermelon\"]" + ); + assert_eq!( + fruits.to_string_limited(32), + "[\"banana\", \"apple\", ... 1 more]" + ); + assert_eq!( + fruits.to_string_limited(31), + "[\"banana\", \"apple\", ... 1 more]" + ); + assert_eq!(fruits.to_string_limited(30), "[\"banana\", ... 2 more]"); + assert_eq!(fruits.to_string_limited(22), "[\"banana\", ... 2 more]"); + assert_eq!(fruits.to_string_limited(21), "[... 3 elements]"); + assert_eq!(fruits.to_string_limited(16), "[... 3 elements]"); + } +} diff --git a/packages/vm/src/middleware/deterministic.rs b/packages/vm/src/middleware/deterministic.rs index 0158722c1..26d8e852f 100644 --- a/packages/vm/src/middleware/deterministic.rs +++ b/packages/vm/src/middleware/deterministic.rs @@ -168,7 +168,6 @@ mod tests { // No 'use super::*;' here. This is strange and means we are not testing the functions in this module directly. use crate::backends::compile; use crate::errors::VmError; - use wabt::wat2wasm; use wasmer_runtime_core::{imports, typed_func::Func}; #[test] @@ -181,7 +180,7 @@ mod tests { i32.add )) "#; - let wasm = wat2wasm(input).unwrap(); + let wasm = wat::parse_str(input).unwrap(); let module = compile(&wasm).unwrap(); let instance = module.instantiate(&imports! {}).unwrap(); @@ -200,7 +199,7 @@ mod tests { f32.convert_u/i32 )) "#; - let wasm = wat2wasm(input).unwrap(); + let wasm = wat::parse_str(input).unwrap(); let res = compile(&wasm); match res.err().unwrap() { diff --git a/packages/vm/src/modules.rs b/packages/vm/src/modules/file_system_cache.rs similarity index 67% rename from packages/vm/src/modules.rs rename to packages/vm/src/modules/file_system_cache.rs index d133313eb..e77a4def1 100644 --- a/packages/vm/src/modules.rs +++ b/packages/vm/src/modules/file_system_cache.rs @@ -4,13 +4,13 @@ use memmap::Mmap; use std::{ fs::{self, File}, - io::{self, Write}, + io::{self, ErrorKind, Write}, path::PathBuf, }; use wasmer_runtime_core::{cache::Artifact, module::Module}; -use crate::backends::{backend, compiler_for_backend}; +use crate::backends::{compiler_for_backend, BACKEND_NAME}; use crate::checksum::Checksum; use crate::errors::{VmError, VmResult}; @@ -58,15 +58,25 @@ impl FileSystemCache { } } - pub fn load(&self, checksum: &Checksum) -> VmResult { - self.load_with_backend(checksum, backend()) - } + pub fn load(&self, checksum: &Checksum) -> VmResult> { + let backend = BACKEND_NAME; - pub fn load_with_backend(&self, checksum: &Checksum, backend: &str) -> VmResult { let filename = checksum.to_hex(); let file_path = self.path.clone().join(backend).join(filename); - let file = File::open(file_path) - .map_err(|e| VmError::cache_err(format!("Error opening module file: {}", e)))?; + + let file = match File::open(file_path) { + Ok(file) => file, + Err(err) => match err.kind() { + ErrorKind::NotFound => return Ok(None), + _ => { + return Err(VmError::cache_err(format!( + "Error opening module file: {}", + err + ))) + } + }, + }; + let mmap = unsafe { Mmap::map(&file) } .map_err(|e| VmError::cache_err(format!("Mmap error: {}", e)))?; @@ -79,10 +89,11 @@ impl FileSystemCache { .as_ref(), ) }?; - Ok(module) + Ok(Some(module)) } - pub fn store(&mut self, checksum: &Checksum, module: Module) -> VmResult<()> { + /// Stores a serialization of the module to the file system + pub fn store(&mut self, checksum: &Checksum, module: &Module) -> VmResult<()> { let backend_str = module.info().backend.to_string(); let modules_dir = self.path.clone().join(backend_str); fs::create_dir_all(&modules_dir) @@ -105,14 +116,17 @@ impl FileSystemCache { mod tests { use super::*; use crate::backends::compile; - use std::env; - use wabt::wat2wasm; + use tempfile::TempDir; #[test] fn test_file_system_cache_run() { use wasmer_runtime_core::{imports, typed_func::Func}; - let wasm = wat2wasm( + let tmp_dir = TempDir::new().unwrap(); + let mut cache = unsafe { FileSystemCache::new(tmp_dir.path()).unwrap() }; + + // Create module + let wasm = wat::parse_str( r#"(module (type $t0 (func (param i32) (result i32))) (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32) @@ -123,29 +137,29 @@ mod tests { ) .unwrap(); let checksum = Checksum::generate(&wasm); - let module = compile(&wasm).unwrap(); - // assert we are using the proper backend - assert_eq!(backend().to_string(), module.info().backend.to_string()); - - let cache_dir = env::temp_dir(); - let mut fs_cache = unsafe { FileSystemCache::new(cache_dir).unwrap() }; - - // store module - fs_cache.store(&checksum, module.clone()).unwrap(); - - // load module - let cached_result = fs_cache.load(&checksum); - - let cached_module = cached_result.unwrap(); - let import_object = imports! {}; - let instance = cached_module.instantiate(&import_object).unwrap(); - let add_one: Func = instance.exports.get("add_one").unwrap(); - - let value = add_one.call(42).unwrap(); - - // verify it works - assert_eq!(value, 43); + // Module does not exist + let cached = cache.load(&checksum).unwrap(); + assert!(cached.is_none()); + + // Store module + cache.store(&checksum, &module).unwrap(); + + // Load module + let cached = cache.load(&checksum).unwrap(); + assert!(cached.is_some()); + + // Check the returned module is functional. + // This is not really testing the cache API but better safe than sorry. + { + assert_eq!(module.info().backend.to_string(), BACKEND_NAME.to_string()); + let cached_module = cached.unwrap(); + let import_object = imports! {}; + let instance = cached_module.instantiate(&import_object).unwrap(); + let add_one: Func = instance.exports.get("add_one").unwrap(); + let value = add_one.call(42).unwrap(); + assert_eq!(value, 43); + } } } diff --git a/packages/vm/src/modules/in_memory_cache.rs b/packages/vm/src/modules/in_memory_cache.rs new file mode 100644 index 000000000..e72eb899a --- /dev/null +++ b/packages/vm/src/modules/in_memory_cache.rs @@ -0,0 +1,81 @@ +use clru::CLruCache; +use wasmer_runtime_core::module::Module; + +use crate::{Checksum, Size, VmResult}; + +const ESTIMATED_MODULE_SIZE: Size = Size::mebi(10); + +/// An in-memory module cache +pub struct InMemoryCache { + lru: CLruCache, +} + +impl InMemoryCache { + /// Creates a new cache with the given size (in bytes) + pub fn new(size: Size) -> Self { + let max_entries = size.0 / ESTIMATED_MODULE_SIZE.0; + InMemoryCache { + lru: CLruCache::new(max_entries), + } + } + + pub fn store(&mut self, checksum: &Checksum, module: Module) -> VmResult<()> { + self.lru.put(*checksum, module); + Ok(()) + } + + pub fn load(&mut self, checksum: &Checksum) -> VmResult> { + let optional = self.lru.get(checksum); + Ok(optional) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backends::{compile, BACKEND_NAME}; + + #[test] + fn test_in_memory_cache_run() { + use wasmer_runtime_core::{imports, typed_func::Func}; + + let mut cache = InMemoryCache::new(Size::mebi(200)); + + // Create module + let wasm = wat::parse_str( + r#"(module + (type $t0 (func (param i32) (result i32))) + (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32) + get_local $p0 + i32.const 1 + i32.add)) + "#, + ) + .unwrap(); + let checksum = Checksum::generate(&wasm); + let module = compile(&wasm).unwrap(); + + // Module does not exist + let cached = cache.load(&checksum).unwrap(); + assert!(cached.is_none()); + + // Store module + cache.store(&checksum, module.clone()).unwrap(); + + // Load module + let cached = cache.load(&checksum).unwrap(); + assert!(cached.is_some()); + + // Check the returned module is functional. + // This is not really testing the cache API but better safe than sorry. + { + assert_eq!(module.info().backend.to_string(), BACKEND_NAME.to_string()); + let cached_module = cached.unwrap(); + let import_object = imports! {}; + let instance = cached_module.instantiate(&import_object).unwrap(); + let add_one: Func = instance.exports.get("add_one").unwrap(); + let value = add_one.call(42).unwrap(); + assert_eq!(value, 43); + } + } +} diff --git a/packages/vm/src/modules/mod.rs b/packages/vm/src/modules/mod.rs new file mode 100644 index 000000000..343057c08 --- /dev/null +++ b/packages/vm/src/modules/mod.rs @@ -0,0 +1,5 @@ +mod file_system_cache; +mod in_memory_cache; + +pub use file_system_cache::FileSystemCache; +pub use in_memory_cache::InMemoryCache; diff --git a/packages/vm/src/size.rs b/packages/vm/src/size.rs new file mode 100644 index 000000000..e58bf038b --- /dev/null +++ b/packages/vm/src/size.rs @@ -0,0 +1,71 @@ +#[derive(Copy, Clone, Debug)] +pub struct Size(pub usize); + +impl Size { + /// Creates a size of `n` kilo + pub const fn kilo(n: usize) -> Self { + Size(n * 1000) + } + + /// Creates a size of `n` kibi + pub const fn kibi(n: usize) -> Self { + Size(n * 1024) + } + + /// Creates a size of `n` mega + pub const fn mega(n: usize) -> Self { + Size(n * 1000 * 1000) + } + + /// Creates a size of `n` mebi + pub const fn mebi(n: usize) -> Self { + Size(n * 1024 * 1024) + } + + /// Creates a size of `n` giga + pub const fn giga(n: usize) -> Self { + Size(n * 1000 * 1000 * 1000) + } + + /// Creates a size of `n` gibi + pub const fn gibi(n: usize) -> Self { + Size(n * 1024 * 1024 * 1024) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn constructors_work() { + assert_eq!(Size(0).0, 0); + assert_eq!(Size(3).0, 3); + + assert_eq!(Size::kilo(0).0, 0); + assert_eq!(Size::kilo(3).0, 3000); + + assert_eq!(Size::kibi(0).0, 0); + assert_eq!(Size::kibi(3).0, 3072); + + assert_eq!(Size::mega(0).0, 0); + assert_eq!(Size::mega(3).0, 3000000); + + assert_eq!(Size::mebi(0).0, 0); + assert_eq!(Size::mebi(3).0, 3145728); + + assert_eq!(Size::giga(0).0, 0); + assert_eq!(Size::giga(3).0, 3000000000); + + assert_eq!(Size::gibi(0).0, 0); + assert_eq!(Size::gibi(3).0, 3221225472); + } + + #[test] + fn implements_debug() { + assert_eq!(format!("{:?}", Size(0)), "Size(0)"); + assert_eq!(format!("{:?}", Size(123)), "Size(123)"); + assert_eq!(format!("{:?}", Size::kibi(2)), "Size(2048)"); + assert_eq!(format!("{:?}", Size::mebi(1)), "Size(1048576)"); + } +} diff --git a/packages/vm/src/testing/calls.rs b/packages/vm/src/testing/calls.rs index 6b0e955a8..76834df12 100644 --- a/packages/vm/src/testing/calls.rs +++ b/packages/vm/src/testing/calls.rs @@ -6,17 +6,23 @@ use serde::{de::DeserializeOwned, Serialize}; use std::fmt; use cosmwasm_std::{ - to_vec, Env, HandleResult, InitResult, MigrateResult, QueryResponse, StdResult, + ContractResult, Env, HandleResponse, InitResponse, MessageInfo, MigrateResponse, QueryResponse, }; use crate::calls::{call_handle, call_init, call_migrate, call_query}; use crate::instance::Instance; +use crate::serde::to_vec; use crate::{Api, Querier, Storage}; // init mimicks the call signature of the smart contracts. // thus it moves env and msg rather than take them as reference. // this is inefficient here, but only used in test code -pub fn init(instance: &mut Instance, env: Env, msg: M) -> InitResult +pub fn init( + instance: &mut Instance, + env: Env, + info: MessageInfo, + msg: M, +) -> ContractResult> where S: Storage + 'static, A: Api + 'static, @@ -24,14 +30,19 @@ where M: Serialize + JsonSchema, U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, { - let serialized_msg = to_vec(&msg)?; - call_init(instance, &env, &serialized_msg).expect("VM error") + let serialized_msg = to_vec(&msg).expect("Testing error: Could not seralize request message"); + call_init(instance, &env, &info, &serialized_msg).expect("VM error") } // handle mimicks the call signature of the smart contracts. // thus it moves env and msg rather than take them as reference. // this is inefficient here, but only used in test code -pub fn handle(instance: &mut Instance, env: Env, msg: M) -> HandleResult +pub fn handle( + instance: &mut Instance, + env: Env, + info: MessageInfo, + msg: M, +) -> ContractResult> where S: Storage + 'static, A: Api + 'static, @@ -39,8 +50,8 @@ where M: Serialize + JsonSchema, U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, { - let serialized_msg = to_vec(&msg)?; - call_handle(instance, &env, &serialized_msg).expect("VM error") + let serialized_msg = to_vec(&msg).expect("Testing error: Could not seralize request message"); + call_handle(instance, &env, &info, &serialized_msg).expect("VM error") } // migrate mimicks the call signature of the smart contracts. @@ -49,8 +60,9 @@ where pub fn migrate( instance: &mut Instance, env: Env, + info: MessageInfo, msg: M, -) -> MigrateResult +) -> ContractResult> where S: Storage + 'static, A: Api + 'static, @@ -58,20 +70,24 @@ where M: Serialize + JsonSchema, U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, { - let serialized_msg = to_vec(&msg)?; - call_migrate(instance, &env, &serialized_msg).expect("VM error") + let serialized_msg = to_vec(&msg).expect("Testing error: Could not seralize request message"); + call_migrate(instance, &env, &info, &serialized_msg).expect("VM error") } // query mimicks the call signature of the smart contracts. // thus it moves env and msg rather than take them as reference. // this is inefficient here, but only used in test code -pub fn query(instance: &mut Instance, msg: M) -> StdResult +pub fn query( + instance: &mut Instance, + env: Env, + msg: M, +) -> ContractResult where S: Storage + 'static, A: Api + 'static, Q: Querier + 'static, M: Serialize + JsonSchema, { - let serialized_msg = to_vec(&msg)?; - call_query(instance, &serialized_msg).expect("VM error") + let serialized_msg = to_vec(&msg).expect("Testing error: Could not seralize request message"); + call_query(instance, &env, &serialized_msg).expect("VM error") } diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index 0ef56c0f0..7798bf9db 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -4,15 +4,18 @@ use cosmwasm_std::{Coin, HumanAddr}; use std::collections::HashSet; -use crate::compatability::check_wasm; +use crate::compatibility::check_wasm; use crate::features::features_from_csv; -use crate::instance::Instance; -use crate::{Api, Extern, Querier, Storage}; +use crate::instance::{Instance, InstanceOptions}; +use crate::{Api, Backend, Querier, Storage}; use super::mock::{MockApi, MOCK_CONTRACT_ADDR}; use super::querier::MockQuerier; use super::storage::MockStorage; +const DEFAULT_GAS_LIMIT: u64 = 500_000; +const DEFAULT_PRINT_DEBUG: bool = true; + pub fn mock_instance( wasm: &[u8], contract_balance: &[Coin], @@ -70,30 +73,30 @@ pub fn mock_instance_with_gas_limit( #[derive(Debug)] pub struct MockInstanceOptions<'a> { // dependencies - pub canonical_address_length: usize, pub balances: &'a [(&'a HumanAddr, &'a [Coin])], /// This option is merged into balances and might override an existing value pub contract_balance: Option<&'a [Coin]>, - /// When set, all calls to the API fail with FfiError::Unknown containing this message + /// When set, all calls to the API fail with BackendError::Unknown containing this message pub backend_error: Option<&'static str>, // instance pub supported_features: HashSet, pub gas_limit: u64, + pub print_debug: bool, } impl Default for MockInstanceOptions<'_> { fn default() -> Self { Self { // dependencies - canonical_address_length: 20, balances: Default::default(), contract_balance: Default::default(), backend_error: None, // instance supported_features: features_from_csv("staking"), - gas_limit: 500_000, + gas_limit: DEFAULT_GAS_LIMIT, + print_debug: DEFAULT_PRINT_DEBUG, } } } @@ -116,24 +119,34 @@ pub fn mock_instance_with_options( } let api = if let Some(backend_error) = options.backend_error { - MockApi::new_failing(options.canonical_address_length, backend_error) + MockApi::new_failing(backend_error) } else { - MockApi::new(options.canonical_address_length) + MockApi::default() }; - let deps = Extern { + let backend = Backend { storage: MockStorage::default(), querier: MockQuerier::new(&balances), api, }; - Instance::from_code(wasm, deps, options.gas_limit).unwrap() + let options = InstanceOptions { + gas_limit: options.gas_limit, + print_debug: options.print_debug, + }; + Instance::from_code(wasm, backend, options).unwrap() +} + +/// Creates InstanceOptions for testing +pub fn mock_instance_options() -> InstanceOptions { + InstanceOptions { + gas_limit: DEFAULT_GAS_LIMIT, + print_debug: DEFAULT_PRINT_DEBUG, + } } /// Runs a series of IO tests, hammering especially on allocate and deallocate. /// This could be especially useful when run with some kind of leak detector. -pub fn test_io( - instance: &mut Instance, -) { +pub fn test_io(instance: &mut Instance) { let sizes: Vec = vec![0, 1, 3, 10, 200, 2000, 5 * 1024]; let bytes: Vec = vec![0x00, 0xA5, 0xFF]; diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index 8286e2f09..87451e7ec 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -1,10 +1,9 @@ -use cosmwasm_std::{ - Binary, BlockInfo, CanonicalAddr, Coin, ContractInfo, Env, HumanAddr, MessageInfo, -}; +use cosmwasm_std::testing::{digit_sum, riffle_shuffle}; +use cosmwasm_std::{BlockInfo, CanonicalAddr, Coin, ContractInfo, Env, HumanAddr, MessageInfo}; use super::querier::MockQuerier; use super::storage::MockStorage; -use crate::{Api, Extern, FfiError, FfiResult, GasInfo}; +use crate::{Api, Backend, BackendError, BackendResult, GasInfo}; pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; const GAS_COST_HUMANIZE: u64 = 44; @@ -12,27 +11,23 @@ const GAS_COST_CANONICALIZE: u64 = 55; /// All external requirements that can be injected for unit tests. /// It sets the given balance for the contract itself, nothing else -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { +pub fn mock_backend(contract_balance: &[Coin]) -> Backend { let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - Extern { + Backend { storage: MockStorage::default(), - api: MockApi::new(canonical_length), + api: MockApi::default(), querier: MockQuerier::new(&[(&contract_addr, contract_balance)]), } } /// Initializes the querier along with the mock_dependencies. /// Sets all balances provided (yoy must explicitly set contract balance if desired) -pub fn mock_dependencies_with_balances( - canonical_length: usize, +pub fn mock_backend_with_balances( balances: &[(&HumanAddr, &[Coin])], -) -> Extern { - Extern { +) -> Backend { + Backend { storage: MockStorage::default(), - api: MockApi::new(canonical_length), + api: MockApi::default(), querier: MockQuerier::new(balances), } } @@ -42,87 +37,104 @@ pub fn mock_dependencies_with_balances( /// This is not really smart, but allows us to see a difference (and consistent length for canonical adddresses). #[derive(Copy, Clone)] pub struct MockApi { - canonical_length: usize, - /// When set, all calls to the API fail with FfiError::Unknown containing this message + /// Length of canonical addresses created with this API. Contracts should not make any assumtions + /// what this value is. + pub canonical_length: usize, + /// When set, all calls to the API fail with BackendError::Unknown containing this message backend_error: Option<&'static str>, } impl MockApi { - pub fn new(canonical_length: usize) -> Self { - MockApi { - canonical_length, - backend_error: None, - } + #[deprecated( + since = "0.11.0", + note = "The canonical length argument is unused. Use MockApi::default() instead." + )] + pub fn new(_canonical_length: usize) -> Self { + MockApi::default() } - pub fn new_failing(canonical_length: usize, backend_error: &'static str) -> Self { + pub fn new_failing(backend_error: &'static str) -> Self { MockApi { - canonical_length, backend_error: Some(backend_error), + ..MockApi::default() } } } impl Default for MockApi { fn default() -> Self { - Self::new(20) + MockApi { + canonical_length: 24, + backend_error: None, + } } } impl Api for MockApi { - fn canonical_address(&self, human: &HumanAddr) -> FfiResult { + fn canonical_address(&self, human: &HumanAddr) -> BackendResult { let gas_info = GasInfo::with_cost(GAS_COST_CANONICALIZE); if let Some(backend_error) = self.backend_error { - return (Err(FfiError::unknown(backend_error)), gas_info); + return (Err(BackendError::unknown(backend_error)), gas_info); } // Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated. if human.len() < 3 { return ( - Err(FfiError::user_err("Invalid input: human address too short")), + Err(BackendError::user_err( + "Invalid input: human address too short", + )), gas_info, ); } if human.len() > self.canonical_length { return ( - Err(FfiError::user_err("Invalid input: human address too long")), + Err(BackendError::user_err( + "Invalid input: human address too long", + )), gas_info, ); } let mut out = Vec::from(human.as_str()); - let append = self.canonical_length - out.len(); - if append > 0 { - out.extend(vec![0u8; append]); + // pad to canonical length with NULL bytes + out.resize(self.canonical_length, 0x00); + // content-dependent rotate followed by shuffle to destroy + // the most obvious structure (https://github.com/CosmWasm/cosmwasm/issues/552) + let rotate_by = digit_sum(&out) % self.canonical_length; + out.rotate_left(rotate_by); + for _ in 0..18 { + out = riffle_shuffle(&out); } - - (Ok(CanonicalAddr(Binary(out))), gas_info) + (Ok(out.into()), gas_info) } - fn human_address(&self, canonical: &CanonicalAddr) -> FfiResult { + fn human_address(&self, canonical: &CanonicalAddr) -> BackendResult { let gas_info = GasInfo::with_cost(GAS_COST_HUMANIZE); if let Some(backend_error) = self.backend_error { - return (Err(FfiError::unknown(backend_error)), gas_info); + return (Err(BackendError::unknown(backend_error)), gas_info); } if canonical.len() != self.canonical_length { return ( - Err(FfiError::user_err( + Err(BackendError::user_err( "Invalid input: canonical address length not correct", )), gas_info, ); } - // remove trailing 0's (TODO: fix this - but fine for first tests) - let trimmed: Vec = canonical - .as_slice() - .iter() - .cloned() - .filter(|&x| x != 0) - .collect(); + let mut tmp: Vec = canonical.clone().into(); + // Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds) + for _ in 0..2 { + tmp = riffle_shuffle(&tmp); + } + // Rotate back + let rotate_by = digit_sum(&tmp) % self.canonical_length; + tmp.rotate_right(rotate_by); + // Remove NULL bytes (i.e. the padding) + let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect(); let result = match String::from_utf8(trimmed) { Ok(human) => Ok(HumanAddr(human)), @@ -132,40 +144,48 @@ impl Api for MockApi { } } -/// Just set sender and sent funds for the message. The rest uses defaults. -/// The sender will be canonicalized internally to allow developers pasing in human readable senders. +/// Returns a default enviroment with height, time, chain_id, and contract address +/// You can submit as is to most contracts, or modify height/time if you want to +/// test for expiration. +/// /// This is intended for use in test code only. -pub fn mock_env>(sender: U, sent: &[Coin]) -> Env { +pub fn mock_env() -> Env { Env { block: BlockInfo { height: 12_345, time: 1_571_797_419, + time_nanos: 879305533, chain_id: "cosmos-testnet-14002".to_string(), }, - message: MessageInfo { - sender: sender.into(), - sent_funds: sent.to_vec(), - }, contract: ContractInfo { address: HumanAddr::from(MOCK_CONTRACT_ADDR), }, } } +/// Just set sender and sent funds for the message. The essential for +/// This is intended for use in test code only. +pub fn mock_info>(sender: U, sent: &[Coin]) -> MessageInfo { + MessageInfo { + sender: sender.into(), + sent_funds: sent.to_vec(), + } +} + #[cfg(test)] mod test { use super::*; - use crate::FfiError; - use cosmwasm_std::coins; + use crate::BackendError; + use cosmwasm_std::{coins, Binary}; #[test] - fn mock_env_arguments() { + fn mock_info_arguments() { let name = HumanAddr("my name".to_string()); // make sure we can generate with &str, &HumanAddr, and HumanAddr - let a = mock_env("my name", &coins(100, "atom")); - let b = mock_env(&name, &coins(100, "atom")); - let c = mock_env(name, &coins(100, "atom")); + let a = mock_info("my name", &coins(100, "atom")); + let b = mock_info(&name, &coins(100, "atom")); + let c = mock_info(name, &coins(100, "atom")); // and the results are the same assert_eq!(a, b); @@ -173,45 +193,42 @@ mod test { } #[test] - fn flip_addresses() { - let api = MockApi::new(20); - let human = HumanAddr("shorty".to_string()); - let canon = api.canonical_address(&human).0.unwrap(); - assert_eq!(canon.len(), 20); - assert_eq!(&canon.as_slice()[0..6], human.as_str().as_bytes()); - assert_eq!(&canon.as_slice()[6..], &[0u8; 14]); - - let (recovered, _gas_cost) = api.human_address(&canon); - assert_eq!(recovered.unwrap(), human); + fn canonicalize_and_humanize_restores_original() { + let api = MockApi::default(); + + let original = HumanAddr::from("shorty"); + let canonical = api.canonical_address(&original).0.unwrap(); + let (recovered, _gas_cost) = api.human_address(&canonical); + assert_eq!(recovered.unwrap(), original); } #[test] fn human_address_input_length() { - let api = MockApi::new(10); + let api = MockApi::default(); let input = CanonicalAddr(Binary(vec![61; 11])); let (result, _gas_info) = api.human_address(&input); match result.unwrap_err() { - FfiError::UserErr { .. } => {} + BackendError::UserErr { .. } => {} err => panic!("Unexpected error: {:?}", err), } } #[test] fn canonical_address_min_input_length() { - let api = MockApi::new(10); - let human = HumanAddr("1".to_string()); + let api = MockApi::default(); + let human = HumanAddr::from("1"); match api.canonical_address(&human).0.unwrap_err() { - FfiError::UserErr { .. } => {} + BackendError::UserErr { .. } => {} err => panic!("Unexpected error: {:?}", err), } } #[test] fn canonical_address_max_input_length() { - let api = MockApi::new(10); - let human = HumanAddr("longer-than-10".to_string()); + let api = MockApi::default(); + let human = HumanAddr::from("longer-than-the-address-length-supported-by-this-api"); match api.canonical_address(&human).0.unwrap_err() { - FfiError::UserErr { .. } => {} + BackendError::UserErr { .. } => {} err => panic!("Unexpected error: {:?}", err), } } diff --git a/packages/vm/src/testing/mod.rs b/packages/vm/src/testing/mod.rs index 03b023f03..d47c13c09 100644 --- a/packages/vm/src/testing/mod.rs +++ b/packages/vm/src/testing/mod.rs @@ -8,13 +8,12 @@ mod storage; pub use calls::{handle, init, migrate, query}; pub use instance::{ - mock_instance, mock_instance_with_balances, mock_instance_with_failing_api, - mock_instance_with_gas_limit, mock_instance_with_options, test_io, MockInstanceOptions, + mock_instance, mock_instance_options, mock_instance_with_balances, + mock_instance_with_failing_api, mock_instance_with_gas_limit, mock_instance_with_options, + test_io, MockInstanceOptions, }; pub use mock::{ - mock_dependencies, mock_dependencies_with_balances, mock_env, MockApi, MOCK_CONTRACT_ADDR, + mock_backend, mock_backend_with_balances, mock_env, mock_info, MockApi, MOCK_CONTRACT_ADDR, }; pub use querier::MockQuerier; -#[cfg(feature = "iterator")] -pub use storage::MockIterator; pub use storage::MockStorage; diff --git a/packages/vm/src/testing/querier.rs b/packages/vm/src/testing/querier.rs index 7cf92a12c..9a347764d 100644 --- a/packages/vm/src/testing/querier.rs +++ b/packages/vm/src/testing/querier.rs @@ -1,12 +1,12 @@ -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; use cosmwasm_std::testing::{MockQuerier as StdMockQuerier, MockQuerierCustomHandlerResult}; use cosmwasm_std::{ - to_binary, to_vec, Binary, Coin, Empty, HumanAddr, Querier as _, QueryRequest, StdResult, - SystemError, SystemResult, + to_binary, to_vec, Binary, Coin, ContractResult, CustomQuery, Empty, HumanAddr, Querier as _, + QueryRequest, SystemError, SystemResult, }; -use crate::{FfiError, FfiResult, GasInfo, Querier}; +use crate::{BackendError, BackendResult, GasInfo, Querier}; const GAS_COST_QUERY_FLAT: u64 = 100_000; /// Gas per request byte @@ -16,11 +16,11 @@ const GAS_COST_QUERY_RESPONSE_MULTIPLIER: u64 = 100; /// MockQuerier holds an immutable table of bank balances /// TODO: also allow querying contracts -pub struct MockQuerier { +pub struct MockQuerier { querier: StdMockQuerier, } -impl MockQuerier { +impl MockQuerier { pub fn new(balances: &[(&HumanAddr, &[Coin])]) -> Self { MockQuerier { querier: StdMockQuerier::new(balances), @@ -55,12 +55,12 @@ impl MockQuerier { } } -impl Querier for MockQuerier { +impl Querier for MockQuerier { fn query_raw( &self, bin_request: &[u8], gas_limit: u64, - ) -> FfiResult>> { + ) -> BackendResult>> { let response = self.querier.raw_query(bin_request); let gas_info = GasInfo::with_externally_used( GAS_COST_QUERY_FLAT @@ -72,27 +72,27 @@ impl Querier for MockQuerier { // In a production implementation, this should stop the query execution in the middle of the computation. // Thus no query response is returned to the caller. if gas_info.externally_used > gas_limit { - return (Err(FfiError::out_of_gas()), gas_info); + return (Err(BackendError::out_of_gas()), gas_info); } - // We don't use FFI in the mock implementation, so FfiResult is always Ok() regardless of error on other levels + // We don't use FFI in the mock implementation, so BackendResult is always Ok() regardless of error on other levels (Ok(response), gas_info) } } impl MockQuerier { - pub fn query( + pub fn query( &self, - request: &QueryRequest, + request: &QueryRequest, gas_limit: u64, - ) -> FfiResult>> { + ) -> BackendResult>> { // encode the request, then call raw_query let request_binary = match to_vec(request) { Ok(raw) => raw, Err(err) => { let gas_info = GasInfo::with_externally_used(err.to_string().len() as u64); return ( - Ok(Err(SystemError::InvalidRequest { + Ok(SystemResult::Err(SystemError::InvalidRequest { error: format!("Serializing query request: {}", err), request: b"N/A".into(), })), @@ -120,7 +120,7 @@ mod test { let gas_limit = 20; let (result, _gas_info) = querier.query_raw(b"broken request", gas_limit); match result.unwrap_err() { - FfiError::OutOfGas {} => {} + BackendError::OutOfGas {} => {} err => panic!("Unexpected error: {:?}", err), } } diff --git a/packages/vm/src/testing/storage.rs b/packages/vm/src/testing/storage.rs index ef6e8eef6..232ab195b 100644 --- a/packages/vm/src/testing/storage.rs +++ b/packages/vm/src/testing/storage.rs @@ -1,13 +1,17 @@ use std::collections::BTreeMap; #[cfg(feature = "iterator")] +use std::collections::HashMap; +#[cfg(feature = "iterator")] +use std::convert::TryInto; +#[cfg(feature = "iterator")] use std::ops::{Bound, RangeBounds}; #[cfg(feature = "iterator")] use cosmwasm_std::{Order, KV}; #[cfg(feature = "iterator")] -use crate::traits::StorageIterator; -use crate::{FfiResult, GasInfo, Storage}; +use crate::BackendError; +use crate::{BackendResult, GasInfo, Storage}; #[cfg(feature = "iterator")] const GAS_COST_LAST_ITERATION: u64 = 37; @@ -15,94 +19,119 @@ const GAS_COST_LAST_ITERATION: u64 = 37; #[cfg(feature = "iterator")] const GAS_COST_RANGE: u64 = 11; -/// A storage iterator for testing only. This type uses a Rust iterator -/// as a data source, which does not provide a gas value for the last iteration. -#[cfg(feature = "iterator")] -pub struct MockIterator<'a> { - source: Box + 'a>, -} - #[cfg(feature = "iterator")] -impl MockIterator<'_> { - pub fn empty() -> Self { - MockIterator { - source: Box::new(std::iter::empty()), - } - } -} - -#[cfg(feature = "iterator")] -impl StorageIterator for MockIterator<'_> { - fn next(&mut self) -> FfiResult> { - match self.source.next() { - Some((kv, gas_used)) => (Ok(Some(kv)), GasInfo::with_externally_used(gas_used)), - None => ( - Ok(None), - GasInfo::with_externally_used(GAS_COST_LAST_ITERATION), - ), - } - } +#[derive(Default, Debug)] +struct Iter { + data: Vec, + position: usize, } #[derive(Default, Debug)] pub struct MockStorage { data: BTreeMap, Vec>, + #[cfg(feature = "iterator")] + iterators: HashMap, } impl MockStorage { pub fn new() -> Self { MockStorage::default() } + + #[cfg(feature = "iterator")] + pub fn all(&mut self, iterator_id: u32) -> BackendResult> { + let mut out: Vec = Vec::new(); + let mut total = GasInfo::free(); + loop { + let (result, info) = self.next(iterator_id); + total += info; + match result { + Err(err) => return (Err(err), total), + Ok(ok) => { + if let Some(v) = ok { + out.push(v); + } else { + break; + } + } + } + } + (Ok(out), total) + } } impl Storage for MockStorage { - fn get(&self, key: &[u8]) -> FfiResult>> { + fn get(&self, key: &[u8]) -> BackendResult>> { let gas_info = GasInfo::with_externally_used(key.len() as u64); (Ok(self.data.get(key).cloned()), gas_info) } #[cfg(feature = "iterator")] - /// range allows iteration over a set of keys, either forwards or backwards - /// uses standard rust range notation, and eg db.range(b"foo"..b"bar") also works reverse - fn range<'a>( - &'a self, + fn scan( + &mut self, start: Option<&[u8]>, end: Option<&[u8]>, order: Order, - ) -> FfiResult> { + ) -> BackendResult { let gas_info = GasInfo::with_externally_used(GAS_COST_RANGE); let bounds = range_bounds(start, end); - // BTreeMap.range panics if range is start > end. - // However, this cases represent just empty range and we treat it as such. - match (bounds.start_bound(), bounds.end_bound()) { - (Bound::Included(start), Bound::Excluded(end)) if start > end => { - return (Ok(Box::new(MockIterator::empty())), gas_info); + let values: Vec = match (bounds.start_bound(), bounds.end_bound()) { + // BTreeMap.range panics if range is start > end. + // However, this cases represent just empty range and we treat it as such. + (Bound::Included(start), Bound::Excluded(end)) if start > end => Vec::new(), + _ => match order { + Order::Ascending => self.data.range(bounds).map(clone_item).collect(), + Order::Descending => self.data.range(bounds).rev().map(clone_item).collect(), + }, + }; + + let last_id: u32 = self + .iterators + .len() + .try_into() + .expect("Found more iterator IDs than supported"); + let new_id = last_id + 1; + let iter = Iter { + data: values, + position: 0, + }; + self.iterators.insert(new_id, iter); + + (Ok(new_id), gas_info) + } + + #[cfg(feature = "iterator")] + fn next(&mut self, iterator_id: u32) -> BackendResult> { + let iterator = match self.iterators.get_mut(&iterator_id) { + Some(i) => i, + None => { + return ( + Err(BackendError::iterator_does_not_exist(iterator_id)), + GasInfo::free(), + ) } - _ => {} - } + }; - let original_iter = self.data.range(bounds); - let iter: Box> = match order { - Order::Ascending => Box::new(original_iter.map(clone_item).map(|item| { - let gas_cost = (item.0.len() + item.1.len()) as u64; - (item, gas_cost) - })), - Order::Descending => Box::new(original_iter.rev().map(clone_item).map(|item| { - let gas_cost = (item.0.len() + item.1.len()) as u64; - (item, gas_cost) - })), + let (value, gas_info): (Option, GasInfo) = if iterator.data.len() > iterator.position { + let item = iterator.data[iterator.position].clone(); + iterator.position += 1; + let gas_cost = (item.0.len() + item.1.len()) as u64; + (Some(item), GasInfo::with_cost(gas_cost)) + } else { + (None, GasInfo::with_externally_used(GAS_COST_LAST_ITERATION)) }; - (Ok(Box::new(MockIterator { source: iter })), gas_info) + + (Ok(value), gas_info) } - fn set(&mut self, key: &[u8], value: &[u8]) -> FfiResult<()> { + fn set(&mut self, key: &[u8], value: &[u8]) -> BackendResult<()> { self.data.insert(key.to_vec(), value.to_vec()); let gas_info = GasInfo::with_externally_used((key.len() + value.len()) as u64); (Ok(()), gas_info) } - fn remove(&mut self, key: &[u8]) -> FfiResult<()> { + fn remove(&mut self, key: &[u8]) -> BackendResult<()> { self.data.remove(key); let gas_info = GasInfo::with_externally_used(key.len() as u64); (Ok(()), gas_info) @@ -160,16 +189,8 @@ mod test { // ensure we had previously set "foo" = "bar" assert_eq!(store.get(b"foo").0.unwrap(), Some(b"bar".to_vec())); - assert_eq!( - store - .range(None, None, Order::Ascending) - .0 - .unwrap() - .elements() - .unwrap() - .len(), - 1 - ); + let iter_id = store.scan(None, None, Order::Ascending).0.unwrap(); + assert_eq!(store.all(iter_id).0.unwrap().len(), 1); // setup - add some data, and delete part of it as well store.set(b"ant", b"hill").0.expect("error setting value"); @@ -181,8 +202,8 @@ mod test { // unbounded { - let iter = store.range(None, None, Order::Ascending).0.unwrap(); - let elements = iter.elements().unwrap(); + let iter_id = store.scan(None, None, Order::Ascending).0.unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!( elements, vec![ @@ -195,8 +216,8 @@ mod test { // unbounded (descending) { - let iter = store.range(None, None, Order::Descending).0.unwrap(); - let elements = iter.elements().unwrap(); + let iter_id = store.scan(None, None, Order::Descending).0.unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!( elements, vec![ @@ -209,21 +230,21 @@ mod test { // bounded { - let iter = store - .range(Some(b"f"), Some(b"n"), Order::Ascending) + let iter_id = store + .scan(Some(b"f"), Some(b"n"), Order::Ascending) .0 .unwrap(); - let elements = iter.elements().unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!(elements, vec![(b"foo".to_vec(), b"bar".to_vec())]); } // bounded (descending) { - let iter = store - .range(Some(b"air"), Some(b"loop"), Order::Descending) + let iter_id = store + .scan(Some(b"air"), Some(b"loop"), Order::Descending) .0 .unwrap(); - let elements = iter.elements().unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!( elements, vec![ @@ -235,48 +256,48 @@ mod test { // bounded empty [a, a) { - let iter = store - .range(Some(b"foo"), Some(b"foo"), Order::Ascending) + let iter_id = store + .scan(Some(b"foo"), Some(b"foo"), Order::Ascending) .0 .unwrap(); - let elements = iter.elements().unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!(elements, vec![]); } // bounded empty [a, a) (descending) { - let iter = store - .range(Some(b"foo"), Some(b"foo"), Order::Descending) + let iter_id = store + .scan(Some(b"foo"), Some(b"foo"), Order::Descending) .0 .unwrap(); - let elements = iter.elements().unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!(elements, vec![]); } // bounded empty [a, b) with b < a { - let iter = store - .range(Some(b"z"), Some(b"a"), Order::Ascending) + let iter_id = store + .scan(Some(b"z"), Some(b"a"), Order::Ascending) .0 .unwrap(); - let elements = iter.elements().unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!(elements, vec![]); } // bounded empty [a, b) with b < a (descending) { - let iter = store - .range(Some(b"z"), Some(b"a"), Order::Descending) + let iter_id = store + .scan(Some(b"z"), Some(b"a"), Order::Descending) .0 .unwrap(); - let elements = iter.elements().unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!(elements, vec![]); } // right unbounded { - let iter = store.range(Some(b"f"), None, Order::Ascending).0.unwrap(); - let elements = iter.elements().unwrap(); + let iter_id = store.scan(Some(b"f"), None, Order::Ascending).0.unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!( elements, vec![ @@ -288,8 +309,8 @@ mod test { // right unbounded (descending) { - let iter = store.range(Some(b"f"), None, Order::Descending).0.unwrap(); - let elements = iter.elements().unwrap(); + let iter_id = store.scan(Some(b"f"), None, Order::Descending).0.unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!( elements, vec![ @@ -301,15 +322,15 @@ mod test { // left unbounded { - let iter = store.range(None, Some(b"f"), Order::Ascending).0.unwrap(); - let elements = iter.elements().unwrap(); + let iter_id = store.scan(None, Some(b"f"), Order::Ascending).0.unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!(elements, vec![(b"ant".to_vec(), b"hill".to_vec()),]); } // left unbounded (descending) { - let iter = store.range(None, Some(b"no"), Order::Descending).0.unwrap(); - let elements = iter.elements().unwrap(); + let iter_id = store.scan(None, Some(b"no"), Order::Descending).0.unwrap(); + let elements = store.all(iter_id).0.unwrap(); assert_eq!( elements, vec![ diff --git a/packages/vm/src/traits.rs b/packages/vm/src/traits.rs deleted file mode 100644 index 56564ba35..000000000 --- a/packages/vm/src/traits.rs +++ /dev/null @@ -1,125 +0,0 @@ -use cosmwasm_std::{Binary, CanonicalAddr, HumanAddr, StdResult, SystemResult}; -#[cfg(feature = "iterator")] -use cosmwasm_std::{Order, KV}; - -#[cfg(feature = "iterator")] -use crate::ffi::FfiError; -use crate::ffi::FfiResult; - -/// Holds all external dependencies of the contract. -/// Designed to allow easy dependency injection at runtime. -/// This cannot be copied or cloned since it would behave differently -/// for mock storages and a bridge storage in the VM. -pub struct Extern { - pub storage: S, - pub api: A, - pub querier: Q, -} - -impl Extern { - /// change_querier is a helper mainly for test code when swapping out the Querier - /// from the auto-generated one from mock_dependencies. This changes the type of - /// Extern so replaces requires some boilerplate. - pub fn change_querier T>(self, transform: F) -> Extern { - Extern { - storage: self.storage, - api: self.api, - querier: transform(self.querier), - } - } -} - -#[cfg(feature = "iterator")] -pub trait StorageIterator { - fn next(&mut self) -> FfiResult>; - - /// Collects all elements, ignoring gas costs - fn elements(mut self) -> Result, FfiError> - where - Self: Sized, - { - let mut out: Vec = Vec::new(); - loop { - let (result, _gas_info) = self.next(); - match result { - Ok(Some(kv)) => out.push(kv), - Ok(None) => break, - Err(err) => return Err(err), - } - } - Ok(out) - } -} - -#[cfg(feature = "iterator")] -impl StorageIterator for Box { - fn next(&mut self) -> FfiResult> { - (**self).next() - } -} - -/// Access to the VM's backend storage, i.e. the chain -pub trait Storage -where - Self: 'static, -{ - /// Returns Err on error. - /// Returns Ok(None) when key does not exist. - /// Returns Ok(Some(Vec)) when key exists. - /// - /// Note: Support for differentiating between a non-existent key and a key with empty value - /// is not great yet and might not be possible in all backends. But we're trying to get there. - fn get(&self, key: &[u8]) -> FfiResult>>; - - #[cfg(feature = "iterator")] - /// Allows iteration over a set of key/value pairs, either forwards or backwards. - /// - /// The bound `start` is inclusive and `end` is exclusive. - /// - /// If `start` is lexicographically greater than or equal to `end`, an empty range is described, mo matter of the order. - fn range<'a>( - &'a self, - start: Option<&[u8]>, - end: Option<&[u8]>, - order: Order, - ) -> FfiResult>; - - fn set(&mut self, key: &[u8], value: &[u8]) -> FfiResult<()>; - - /// Removes a database entry at `key`. - /// - /// The current interface does not allow to differentiate between a key that existed - /// before and one that didn't exist. See https://github.com/CosmWasm/cosmwasm/issues/290 - fn remove(&mut self, key: &[u8]) -> FfiResult<()>; -} - -/// Api are callbacks to system functions defined outside of the wasm modules. -/// This is a trait to allow Mocks in the test code. -/// -/// Currently it just supports address conversion, we could add eg. crypto functions here. -/// These should all be pure (stateless) functions. If you need state, you probably want -/// to use the Querier. -/// -/// We can use feature flags to opt-in to non-essential methods -/// for backwards compatibility in systems that don't have them all. -pub trait Api: Copy + Clone + Send { - fn canonical_address(&self, human: &HumanAddr) -> FfiResult; - fn human_address(&self, canonical: &CanonicalAddr) -> FfiResult; -} - -pub trait Querier { - /// This is all that must be implemented for the Querier. - /// This allows us to pass through binary queries from one level to another without - /// knowing the custom format, or we can decode it, with the knowledge of the allowed - /// types. - /// - /// The gas limit describes how much VM gas this particular query is allowed - /// to comsume when measured separately from the rest of the contract. - /// The returned gas info (in FfiResult) can exceed the gas limit in cases - /// where the query could not be aborted exactly at the limit. - fn query_raw( - &self, - request: &[u8], - gas_limit: u64, - ) -> FfiResult>>; -} diff --git a/packages/vm/testdata/contract.wasm b/packages/vm/testdata/contract.wasm index d59deded5..df561f676 120000 --- a/packages/vm/testdata/contract.wasm +++ b/packages/vm/testdata/contract.wasm @@ -1 +1 @@ -contract_0.10.wasm \ No newline at end of file +contract_0.12.wasm \ No newline at end of file diff --git a/packages/vm/testdata/contract_0.11.wasm b/packages/vm/testdata/contract_0.11.wasm new file mode 100644 index 0000000000000000000000000000000000000000..5277729dbdcf51da1582f27c3808f5f33e51e6f4 GIT binary patch literal 170947 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�n9Gfd zP`e)YEePN+*2VgJV9@=!jq+B(;N zo-gdb+^_ngy036s|B{WtaN*PMs$Cxm`zz1t3WVEN8PUSg^9Q$8Yz&1rtY5e>xNULQ z{=kOtV*R3?4!tTE_M_DP_W6acsMXDt2R}TNj}*Rj>ybl^cYN=_Mfd*wfv&*0hSqmZ zctg0(ch-T2uCXsMx&kxSaH&7^`{1D;o*Vka@AfZk(q|aK{qKLDG3u+-LxJcA`wB0G zv%%Ed4TXDVRa_aGUwH5Si}ahit7qS|Wo6+9_4)!s+dmQasW&Nwr&nBRgbnTBjCGe@ zQ1~C``&DCyZ(*ypziO2+SaC(T@P*FBm8*;pP}*O(=ioEJO1)CgRa_OomN7ap{<>i8 zrv2M1X6w2y5Skwdg!Ib5io$o#4j=MQM}{48+{>3!g|zVT3!=hiOlt^fubZZIg*?Yi zQ_8szKd+QYNx_Y97t(Z5#}MyU_2wsfb7`Sg^g{M2t-BX>X-+z5n95zgKIOWz4l6U~ z9$r#m9$)gSRp!cbV~1C!Ki#)VyZ`vAB`+VZS6)5b_OX-4+g?!3cVQ^hnKS`+LuBR(@QwMLGWL<*{cI+kW}nz1xrd z{MqfM(y-%%`p6Dt0^w)%5I!{gQ#VFpv!kkwQO5lN=1LZh`KDK4jHN+L(=AJh8{KW@o3Gj;%z8h(HHBBwI$jb9L*e5wMa;< z*Y{|(>Tcc8LaJZ?38s&c2H2h!^oKPyHetU<^Qj-w7R;U2Z>6xvs`zluDh zwpVxRO=xSqzRafvFT_V^T0_`XYOhWJ`qjg#9-J+psj73-s(|kMW{^{uOGsjMSW4A? zf${ipU8wB{^2ts@0l79lN{N|;RPDD|Yv6!-C;Bo}GhF8vtK--71^`(J01WEZIa+|N zz1xot0B2wqyHYWJuqH)aZCuWK%+v4%gIZvT@#nhIVKfKTDs_%etwt*|L<>G(sG$VL S14>`v1OjLdd_#$->i-2wNAFtz literal 0 HcmV?d00001 diff --git a/packages/vm/testdata/contract_0.12.wasm b/packages/vm/testdata/contract_0.12.wasm new file mode 100644 index 0000000000000000000000000000000000000000..249a76ecfff5ce48011661545b33ac36fed3e25a GIT binary patch literal 182254 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

CT=*AVyYLVS>5XUbJf> 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 ydUD(g<5%jQamEwrw3k{He Date: Tue, 19 Jan 2021 10:39:25 +0900 Subject: [PATCH 09/12] docs: Add how to build collection-tester to README.md (#56) --- contracts/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/README.md b/contracts/README.md index 436597b50..5a84d6b23 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -45,4 +45,9 @@ docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="devcontract_token_tester",target=/code/contracts/token-tester/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer:0.10.5 ./contracts/token-tester + +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="devcontract_collection_tester",target=/code/contracts/collection-tester/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.10.5 ./contracts/collection-tester ``` From a64314a8d04118e0c74b139df455e6713faf35d1 Mon Sep 17 00:00:00 2001 From: loloicci Date: Tue, 19 Jan 2021 10:50:13 +0900 Subject: [PATCH 10/12] docs: replace CosmWasm's CircleCI badge with ours (#49) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7653e1694..6cb728eec 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CosmWasm -[![CircleCI](https://circleci.com/gh/CosmWasm/cosmwasm/tree/master.svg?style=shield)](https://circleci.com/gh/CosmWasm/cosmwasm/tree/master) +[![CircleCI](https://circleci.com/gh/line/cosmwasm/tree/develop.svg?style=shield)](https://circleci.com/gh/line/cosmwasm/tree/develop) | Crate | Download | Docs | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | From 1f120d7471c446126105d710dea62512501dd2a0 Mon Sep 17 00:00:00 2001 From: loloicci Date: Fri, 22 Jan 2021 17:13:05 +0900 Subject: [PATCH 11/12] docs: Add CHANGELOG-ours.md for v0.12.0-0.1.0 (#62) * docs: Add CHANGELOG-ours.md for v0.12.0-0.1.0 * docs: rename and add a note in CHANGELOG-LINK --- CHANGELOG-LINK.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 CHANGELOG-LINK.md diff --git a/CHANGELOG-LINK.md b/CHANGELOG-LINK.md new file mode 100644 index 000000000..f70933ef9 --- /dev/null +++ b/CHANGELOG-LINK.md @@ -0,0 +1,18 @@ +# CHANGELOG LINK +This is CHANGELOG after this repository was forked from CosmWasm/cosmwasm. + +## Unreleased Version +### Add +- Add the ext package and tests for it. It is a wrapper to use token and collection module from contracts. (#6) +- Add features to use token module in ext and add tests for it (#7) +- Add features to use collection module in ext and add tests for it (#8) +- Add approve, burn_from and transfer_from for token/collection (#29) +- Add semantic.yml for CI (#42) + +### Change +- Update upstream CosmWasm/cosmwasm version to 0.12 (#39) + +### Fixes +- Fix bugs on ext packages and refine it (#35) +- Fix contract/Readme.md (#37) +- Fix README.md for our repository (#49, #56) From 3e5b434880a70692d407a9585505e8f23e20abf1 Mon Sep 17 00:00:00 2001 From: loloicci Date: Fri, 22 Jan 2021 17:35:33 +0900 Subject: [PATCH 12/12] chore: fix CHANGELOG for the new version --- CHANGELOG-LINK.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG-LINK.md b/CHANGELOG-LINK.md index f70933ef9..9d00372c8 100644 --- a/CHANGELOG-LINK.md +++ b/CHANGELOG-LINK.md @@ -1,7 +1,7 @@ # CHANGELOG LINK This is CHANGELOG after this repository was forked from CosmWasm/cosmwasm. -## Unreleased Version +## 0.12.0-0.1.0 ### Add - Add the ext package and tests for it. It is a wrapper to use token and collection module from contracts. (#6) - Add features to use token module in ext and add tests for it (#7)