From cc73e9c3e67ff0d6ffd4d4cef36fb82f79482e08 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 18 Oct 2022 22:24:22 +0200 Subject: [PATCH 1/4] Remove storage-plus, multi-test and utils --- Cargo.lock | 632 +--- contracts/cw1-subkeys/Cargo.toml | 4 +- contracts/cw1-whitelist/Cargo.toml | 6 +- contracts/cw1155-base/Cargo.toml | 4 +- contracts/cw20-base/Cargo.toml | 6 +- contracts/cw20-ics20/Cargo.toml | 4 +- contracts/cw3-fixed-multisig/Cargo.toml | 6 +- contracts/cw3-flex-multisig/Cargo.toml | 6 +- contracts/cw4-group/Cargo.toml | 4 +- contracts/cw4-stake/Cargo.toml | 4 +- packages/controllers/Cargo.toml | 8 +- packages/cw1155/Cargo.toml | 2 +- packages/cw2/Cargo.toml | 8 +- packages/cw20/Cargo.toml | 2 +- packages/cw3/Cargo.toml | 2 +- packages/cw4/Cargo.toml | 2 +- packages/multi-test/Cargo.toml | 29 - packages/multi-test/DESIGN.md | 247 -- packages/multi-test/MIGRATING.md | 25 - packages/multi-test/NOTICE | 14 - packages/multi-test/README.md | 19 - packages/multi-test/clippy.toml | 1 - packages/multi-test/src/app.rs | 2804 ----------------- packages/multi-test/src/bank.rs | 473 --- packages/multi-test/src/contracts.rs | 437 --- packages/multi-test/src/custom_handler.rs | 93 - packages/multi-test/src/error.rs | 46 - packages/multi-test/src/executor.rs | 150 - packages/multi-test/src/lib.rs | 32 - packages/multi-test/src/module.rs | 115 - packages/multi-test/src/prefixed_storage.rs | 185 -- .../src/prefixed_storage/length_prefixed.rs | 176 -- .../src/prefixed_storage/namespace_helpers.rs | 220 -- packages/multi-test/src/staking.rs | 1809 ----------- packages/multi-test/src/test_helpers.rs | 20 - .../multi-test/src/test_helpers/contracts.rs | 8 - .../src/test_helpers/contracts/caller.rs | 40 - .../src/test_helpers/contracts/echo.rs | 140 - .../src/test_helpers/contracts/error.rs | 49 - .../src/test_helpers/contracts/hackatom.rs | 91 - .../src/test_helpers/contracts/payout.rs | 90 - .../src/test_helpers/contracts/reflect.rs | 72 - packages/multi-test/src/transactions.rs | 589 ---- packages/multi-test/src/wasm.rs | 1522 --------- packages/storage-macro/Cargo.toml | 20 - packages/storage-macro/NOTICE | 14 - packages/storage-macro/README.md | 22 - packages/storage-macro/src/lib.rs | 44 - packages/storage-plus/Cargo.toml | 35 - packages/storage-plus/NOTICE | 14 - packages/storage-plus/README.md | 702 ----- packages/storage-plus/benches/main.rs | 161 - packages/storage-plus/src/bound.rs | 184 -- packages/storage-plus/src/de.rs | 268 -- packages/storage-plus/src/deque.rs | 627 ---- packages/storage-plus/src/endian.rs | 58 - packages/storage-plus/src/helpers.rs | 194 -- packages/storage-plus/src/indexed_map.rs | 1735 ---------- packages/storage-plus/src/indexed_snapshot.rs | 1224 ------- packages/storage-plus/src/indexes/mod.rs | 38 - packages/storage-plus/src/indexes/multi.rs | 346 -- packages/storage-plus/src/indexes/unique.rs | 257 -- packages/storage-plus/src/int_key.rs | 130 - packages/storage-plus/src/item.rs | 319 -- packages/storage-plus/src/iter_helpers.rs | 222 -- packages/storage-plus/src/keys.rs | 512 --- packages/storage-plus/src/lib.rs | 87 - packages/storage-plus/src/map.rs | 1580 ---------- packages/storage-plus/src/path.rs | 96 - packages/storage-plus/src/prefix.rs | 441 --- packages/storage-plus/src/snapshot/item.rs | 325 -- packages/storage-plus/src/snapshot/map.rs | 644 ---- packages/storage-plus/src/snapshot/mod.rs | 392 --- packages/storage-plus/tests/index_list.rs | 76 - packages/utils/Cargo.toml | 24 - packages/utils/NOTICE | 14 - packages/utils/README.md | 7 - packages/utils/src/balance.rs | 319 -- packages/utils/src/event.rs | 7 - packages/utils/src/expiration.rs | 237 -- packages/utils/src/lib.rs | 34 - packages/utils/src/migrate.rs | 100 - packages/utils/src/pagination.rs | 115 - packages/utils/src/parse_reply.rs | 528 ---- packages/utils/src/payment.rs | 136 - packages/utils/src/scheduled.rs | 115 - packages/utils/src/threshold.rs | 337 -- 87 files changed, 64 insertions(+), 22872 deletions(-) delete mode 100644 packages/multi-test/Cargo.toml delete mode 100644 packages/multi-test/DESIGN.md delete mode 100644 packages/multi-test/MIGRATING.md delete mode 100644 packages/multi-test/NOTICE delete mode 100644 packages/multi-test/README.md delete mode 100644 packages/multi-test/clippy.toml delete mode 100644 packages/multi-test/src/app.rs delete mode 100644 packages/multi-test/src/bank.rs delete mode 100644 packages/multi-test/src/contracts.rs delete mode 100644 packages/multi-test/src/custom_handler.rs delete mode 100644 packages/multi-test/src/error.rs delete mode 100644 packages/multi-test/src/executor.rs delete mode 100644 packages/multi-test/src/lib.rs delete mode 100644 packages/multi-test/src/module.rs delete mode 100644 packages/multi-test/src/prefixed_storage.rs delete mode 100644 packages/multi-test/src/prefixed_storage/length_prefixed.rs delete mode 100644 packages/multi-test/src/prefixed_storage/namespace_helpers.rs delete mode 100644 packages/multi-test/src/staking.rs delete mode 100644 packages/multi-test/src/test_helpers.rs delete mode 100644 packages/multi-test/src/test_helpers/contracts.rs delete mode 100644 packages/multi-test/src/test_helpers/contracts/caller.rs delete mode 100644 packages/multi-test/src/test_helpers/contracts/echo.rs delete mode 100644 packages/multi-test/src/test_helpers/contracts/error.rs delete mode 100644 packages/multi-test/src/test_helpers/contracts/hackatom.rs delete mode 100644 packages/multi-test/src/test_helpers/contracts/payout.rs delete mode 100644 packages/multi-test/src/test_helpers/contracts/reflect.rs delete mode 100644 packages/multi-test/src/transactions.rs delete mode 100644 packages/multi-test/src/wasm.rs delete mode 100644 packages/storage-macro/Cargo.toml delete mode 100644 packages/storage-macro/NOTICE delete mode 100644 packages/storage-macro/README.md delete mode 100644 packages/storage-macro/src/lib.rs delete mode 100644 packages/storage-plus/Cargo.toml delete mode 100644 packages/storage-plus/NOTICE delete mode 100644 packages/storage-plus/README.md delete mode 100644 packages/storage-plus/benches/main.rs delete mode 100644 packages/storage-plus/src/bound.rs delete mode 100644 packages/storage-plus/src/de.rs delete mode 100644 packages/storage-plus/src/deque.rs delete mode 100644 packages/storage-plus/src/endian.rs delete mode 100644 packages/storage-plus/src/helpers.rs delete mode 100644 packages/storage-plus/src/indexed_map.rs delete mode 100644 packages/storage-plus/src/indexed_snapshot.rs delete mode 100644 packages/storage-plus/src/indexes/mod.rs delete mode 100644 packages/storage-plus/src/indexes/multi.rs delete mode 100644 packages/storage-plus/src/indexes/unique.rs delete mode 100644 packages/storage-plus/src/int_key.rs delete mode 100644 packages/storage-plus/src/item.rs delete mode 100644 packages/storage-plus/src/iter_helpers.rs delete mode 100644 packages/storage-plus/src/keys.rs delete mode 100644 packages/storage-plus/src/lib.rs delete mode 100644 packages/storage-plus/src/map.rs delete mode 100644 packages/storage-plus/src/path.rs delete mode 100644 packages/storage-plus/src/prefix.rs delete mode 100644 packages/storage-plus/src/snapshot/item.rs delete mode 100644 packages/storage-plus/src/snapshot/map.rs delete mode 100644 packages/storage-plus/src/snapshot/mod.rs delete mode 100644 packages/storage-plus/tests/index_list.rs delete mode 100644 packages/utils/Cargo.toml delete mode 100644 packages/utils/NOTICE delete mode 100644 packages/utils/README.md delete mode 100644 packages/utils/src/balance.rs delete mode 100644 packages/utils/src/event.rs delete mode 100644 packages/utils/src/expiration.rs delete mode 100644 packages/utils/src/lib.rs delete mode 100644 packages/utils/src/migrate.rs delete mode 100644 packages/utils/src/pagination.rs delete mode 100644 packages/utils/src/parse_reply.rs delete mode 100644 packages/utils/src/payment.rs delete mode 100644 packages/utils/src/scheduled.rs delete mode 100644 packages/utils/src/threshold.rs diff --git a/Cargo.lock b/Cargo.lock index 635c6e731..3527aa4c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,29 +2,11 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "anyhow" version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" -dependencies = [ - "backtrace", -] [[package]] name = "assert_matches" @@ -32,38 +14,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -82,12 +32,6 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "block-buffer" version = "0.9.0" @@ -106,24 +50,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" - [[package]] name = "byteorder" version = "1.4.3" @@ -136,35 +62,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags", - "textwrap", - "unicode-width", -] - [[package]] name = "const-oid" version = "0.9.0" @@ -244,87 +147,6 @@ dependencies = [ "libc", ] -[[package]] -name = "criterion" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" -dependencies = [ - "atty", - "cast", - "clap", - "criterion-plot", - "csv", - "itertools", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" -dependencies = [ - "cast", - "itertools", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "once_cell", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -353,28 +175,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -404,6 +204,8 @@ dependencies = [ [[package]] name = "cw-multi-test" version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7192aec80d0c01a0e5941392eea7e2b7e212ee74ca7f430bfdc899420c055ef6" dependencies = [ "anyhow", "cosmwasm-std", @@ -417,23 +219,13 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-storage-macro" -version = "0.16.0" -dependencies = [ - "cosmwasm-std", - "serde", - "syn", -] - [[package]] name = "cw-storage-plus" version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" dependencies = [ "cosmwasm-std", - "criterion", - "cw-storage-macro", - "rand", "schemars", "serde", ] @@ -441,12 +233,12 @@ dependencies = [ [[package]] name = "cw-utils" version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", - "cw2", - "prost", + "cw2 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "semver", "serde", @@ -473,7 +265,7 @@ dependencies = [ "cw-utils", "cw1", "cw1-whitelist", - "cw2", + "cw2 0.16.0", "schemars", "semver", "serde", @@ -492,7 +284,7 @@ dependencies = [ "cw-storage-plus", "cw-utils", "cw1", - "cw2", + "cw2 0.16.0", "derivative", "schemars", "serde", @@ -519,7 +311,7 @@ dependencies = [ "cw-storage-plus", "cw-utils", "cw1155", - "cw2", + "cw2 0.16.0", "schemars", "serde", "thiserror", @@ -536,6 +328,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cw2" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + [[package]] name = "cw20" version = "0.16.0" @@ -556,7 +361,7 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "cw-utils", - "cw2", + "cw2 0.16.0", "cw20", "schemars", "semver", @@ -573,7 +378,7 @@ dependencies = [ "cw-controllers", "cw-storage-plus", "cw-utils", - "cw2", + "cw2 0.16.0", "cw20", "schemars", "semver", @@ -601,7 +406,7 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "cw-utils", - "cw2", + "cw2 0.16.0", "cw20", "cw20-base", "cw3", @@ -619,7 +424,7 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "cw-utils", - "cw2", + "cw2 0.16.0", "cw3", "cw3-fixed-multisig", "cw4", @@ -649,7 +454,7 @@ dependencies = [ "cw-controllers", "cw-storage-plus", "cw-utils", - "cw2", + "cw2 0.16.0", "cw4", "schemars", "serde", @@ -665,7 +470,7 @@ dependencies = [ "cw-controllers", "cw-storage-plus", "cw-utils", - "cw2", + "cw2 0.16.0", "cw20", "cw4", "schemars", @@ -821,12 +626,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "gimli" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" - [[package]] name = "group" version = "0.12.0" @@ -838,21 +637,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hex" version = "0.4.3" @@ -877,27 +661,12 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" -[[package]] -name = "js-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "k256" version = "0.11.4" @@ -910,91 +679,12 @@ dependencies = [ "sha2 0.10.2", ] -[[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.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" - -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -1011,40 +701,6 @@ dependencies = [ "spki", ] -[[package]] -name = "plotters" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" - -[[package]] -name = "plotters-svg" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.43" @@ -1086,27 +742,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -1125,51 +760,6 @@ dependencies = [ "getrandom 0.2.7", ] -[[package]] -name = "rayon" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - [[package]] name = "rfc6979" version = "0.3.0" @@ -1181,27 +771,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "schemars" version = "0.8.10" @@ -1226,12 +801,6 @@ dependencies = [ "syn", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "sec1" version = "0.3.0" @@ -1270,16 +839,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.144" @@ -1308,7 +867,7 @@ version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ - "itoa 1.0.3", + "itoa", "ryu", "serde", ] @@ -1380,15 +939,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.32" @@ -1409,16 +959,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "typenum" version = "1.15.0" @@ -1443,29 +983,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1478,101 +1001,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" - -[[package]] -name = "web-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[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-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "zeroize" version = "1.5.7" diff --git a/contracts/cw1-subkeys/Cargo.toml b/contracts/cw1-subkeys/Cargo.toml index 3a7220b07..a2ea8749f 100644 --- a/contracts/cw1-subkeys/Cargo.toml +++ b/contracts/cw1-subkeys/Cargo.toml @@ -20,12 +20,12 @@ test-utils = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw1 = { path = "../../packages/cw1", version = "0.16.0" } cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw1-whitelist = { path = "../cw1-whitelist", version = "0.16.0", features = ["library"] } cosmwasm-std = { version = "1.1.0", features = ["staking"] } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" diff --git a/contracts/cw1-whitelist/Cargo.toml b/contracts/cw1-whitelist/Cargo.toml index c459010a6..34dd9d53f 100644 --- a/contracts/cw1-whitelist/Cargo.toml +++ b/contracts/cw1-whitelist/Cargo.toml @@ -20,11 +20,11 @@ test-utils = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw1 = { path = "../../packages/cw1", version = "0.16.0" } cw2 = { path = "../../packages/cw2", version = "0.16.0" } cosmwasm-std = { version = "1.1.0", features = ["staking"] } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } @@ -32,5 +32,5 @@ thiserror = { version = "1.0.23" } [dev-dependencies] anyhow = "1" assert_matches = "1" -cw-multi-test = { path = "../../packages/multi-test", version = "0.16.0" } +cw-multi-test = "0.16.0" derivative = "2" diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml index 20176bfb4..1519faf63 100644 --- a/contracts/cw1155-base/Cargo.toml +++ b/contracts/cw1155-base/Cargo.toml @@ -19,10 +19,10 @@ library = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw1155 = { path = "../../packages/cw1155", version = "0.16.0" } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cosmwasm-std = { version = "1.1.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/cw20-base/Cargo.toml b/contracts/cw20-base/Cargo.toml index 7d1b2420f..bc1683b4c 100644 --- a/contracts/cw20-base/Cargo.toml +++ b/contracts/cw20-base/Cargo.toml @@ -19,10 +19,10 @@ library = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw20 = { path = "../../packages/cw20", version = "0.16.0" } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cosmwasm-std = { version = "1.1.0" } schemars = "0.8.1" semver = "1" @@ -30,4 +30,4 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } [dev-dependencies] -cw-multi-test = { path = "../../packages/multi-test", version = "0.16.0" } +cw-multi-test = "0.16.0" diff --git a/contracts/cw20-ics20/Cargo.toml b/contracts/cw20-ics20/Cargo.toml index b86a0997b..9b58330b5 100644 --- a/contracts/cw20-ics20/Cargo.toml +++ b/contracts/cw20-ics20/Cargo.toml @@ -19,11 +19,11 @@ library = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw20 = { path = "../../packages/cw20", version = "0.16.0" } cosmwasm-std = { version = "1.1.0", features = ["stargate"] } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cw-controllers = { path = "../../packages/controllers", version = "0.16.0" } schemars = "0.8.1" semver = "1" diff --git a/contracts/cw3-fixed-multisig/Cargo.toml b/contracts/cw3-fixed-multisig/Cargo.toml index 65c15a568..f9ab43376 100644 --- a/contracts/cw3-fixed-multisig/Cargo.toml +++ b/contracts/cw3-fixed-multisig/Cargo.toml @@ -19,10 +19,10 @@ library = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw3 = { path = "../../packages/cw3", version = "0.16.0" } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cosmwasm-std = { version = "1.1.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } @@ -31,4 +31,4 @@ thiserror = { version = "1.0.23" } [dev-dependencies] cw20 = { path = "../../packages/cw20", version = "0.16.0" } cw20-base = { path = "../cw20-base", version = "0.16.0", features = ["library"] } -cw-multi-test = { path = "../../packages/multi-test", version = "0.16.0" } +cw-multi-test = "0.16.0" diff --git a/contracts/cw3-flex-multisig/Cargo.toml b/contracts/cw3-flex-multisig/Cargo.toml index 655b1baf0..291f5e51c 100644 --- a/contracts/cw3-flex-multisig/Cargo.toml +++ b/contracts/cw3-flex-multisig/Cargo.toml @@ -19,12 +19,12 @@ library = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw3 = { path = "../../packages/cw3", version = "0.16.0" } cw3-fixed-multisig = { path = "../cw3-fixed-multisig", version = "0.16.0", features = ["library"] } cw4 = { path = "../../packages/cw4", version = "0.16.0" } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cosmwasm-std = { version = "1.1.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } @@ -32,4 +32,4 @@ thiserror = { version = "1.0.23" } [dev-dependencies] cw4-group = { path = "../cw4-group", version = "0.16.0" } -cw-multi-test = { path = "../../packages/multi-test", version = "0.16.0" } +cw-multi-test = "0.16.0" diff --git a/contracts/cw4-group/Cargo.toml b/contracts/cw4-group/Cargo.toml index 27ef51a15..dba3b7f54 100644 --- a/contracts/cw4-group/Cargo.toml +++ b/contracts/cw4-group/Cargo.toml @@ -27,11 +27,11 @@ library = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw4 = { path = "../../packages/cw4", version = "0.16.0" } cw-controllers = { path = "../../packages/controllers", version = "0.16.0" } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cosmwasm-std = { version = "1.1.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/cw4-stake/Cargo.toml b/contracts/cw4-stake/Cargo.toml index 7f520a98a..af8e1f734 100644 --- a/contracts/cw4-stake/Cargo.toml +++ b/contracts/cw4-stake/Cargo.toml @@ -27,12 +27,12 @@ library = [] [dependencies] cosmwasm-schema = { version = "1.1.0" } -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cw2 = { path = "../../packages/cw2", version = "0.16.0" } cw4 = { path = "../../packages/cw4", version = "0.16.0" } cw20 = { path = "../../packages/cw20", version = "0.16.0" } cw-controllers = { path = "../../packages/controllers", version = "0.16.0" } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cosmwasm-std = { version = "1.1.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/controllers/Cargo.toml b/packages/controllers/Cargo.toml index 89e29ad25..e7083ed75 100644 --- a/packages/controllers/Cargo.toml +++ b/packages/controllers/Cargo.toml @@ -11,10 +11,10 @@ homepage = "https://cosmwasm.com" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cosmwasm-schema = "1.1.0" -cosmwasm-std = "1.1.0" -cw-utils = { path = "../utils", version = "0.16.0" } -cw-storage-plus = { path = "../storage-plus", version = "0.16.0" } +cosmwasm-schema = "1.0.0" +cosmwasm-std = "1.0.0" +cw-utils = "0.16.0" +cw-storage-plus = "0.16.0" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.21" } diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml index f85a1e8df..784b5f4c9 100644 --- a/packages/cw1155/Cargo.toml +++ b/packages/cw1155/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/CosmWasm/cw-plus" homepage = "https://cosmwasm.com" [dependencies] -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cosmwasm-schema = "1.1.0" cosmwasm-std = { version = "1.1.0" } schemars = "0.8.1" diff --git a/packages/cw2/Cargo.toml b/packages/cw2/Cargo.toml index 5104c2ed3..feab0a89b 100644 --- a/packages/cw2/Cargo.toml +++ b/packages/cw2/Cargo.toml @@ -9,8 +9,8 @@ repository = "https://github.com/CosmWasm/cw-plus" homepage = "https://cosmwasm.com" [dependencies] -cosmwasm-schema = "1.1.0" -cosmwasm-std = { version = "1.1.0", default-features = false } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } +cosmwasm-schema = "1.0.0" +cosmwasm-std = { version = "1.0.0", default-features = false } +cw-storage-plus = "0.16.0" schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +serde = { version = "1.0.0", default-features = false, features = ["derive"] } diff --git a/packages/cw20/Cargo.toml b/packages/cw20/Cargo.toml index d76d05947..fe912af57 100644 --- a/packages/cw20/Cargo.toml +++ b/packages/cw20/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/CosmWasm/cw-plus" homepage = "https://cosmwasm.com" [dependencies] -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cosmwasm-schema = "1.1.0" cosmwasm-std = "1.1.0" schemars = "0.8.1" diff --git a/packages/cw3/Cargo.toml b/packages/cw3/Cargo.toml index 777f6a0d8..4c21b571f 100644 --- a/packages/cw3/Cargo.toml +++ b/packages/cw3/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/CosmWasm/cw-plus" homepage = "https://cosmwasm.com" [dependencies] -cw-utils = { path = "../../packages/utils", version = "0.16.0" } +cw-utils = "0.16.0" cosmwasm-schema = "1.1.0" cosmwasm-std = "1.1.0" schemars = "0.8.1" diff --git a/packages/cw4/Cargo.toml b/packages/cw4/Cargo.toml index b0ac9b129..adb1ddaed 100644 --- a/packages/cw4/Cargo.toml +++ b/packages/cw4/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/CosmWasm/cw-plus" homepage = "https://cosmwasm.com" [dependencies] -cw-storage-plus = { path = "../storage-plus", version = "0.16.0" } +cw-storage-plus = "0.16.0" cosmwasm-schema = "1.1.0" cosmwasm-std = "1.1.0" schemars = "0.8.1" diff --git a/packages/multi-test/Cargo.toml b/packages/multi-test/Cargo.toml deleted file mode 100644 index 3fee94ea5..000000000 --- a/packages/multi-test/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "cw-multi-test" -version = "0.16.0" -authors = ["Ethan Frey "] -edition = "2018" -description = "Test helpers for multi-contract interactions" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-plus" -homepage = "https://cosmwasm.com" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -default = ["iterator", "staking"] -iterator = ["cosmwasm-std/iterator"] -stargate = ["cosmwasm-std/stargate"] -staking = ["cosmwasm-std/staking"] -backtrace = ["anyhow/backtrace"] - -[dependencies] -cw-utils = { path = "../../packages/utils", version = "0.16.0" } -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0"} -cosmwasm-std = { version = "1.1.0", features = ["staking"] } -itertools = "0.10.1" -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -prost = "0.9" -anyhow = "1" -thiserror = "1" -derivative = "2" diff --git a/packages/multi-test/DESIGN.md b/packages/multi-test/DESIGN.md deleted file mode 100644 index be070ccba..000000000 --- a/packages/multi-test/DESIGN.md +++ /dev/null @@ -1,247 +0,0 @@ -# Multitest Design - -Multitest is a design to simulate a blockchain environment in pure Rust. -This allows us to run unit tests that involve contract -> contract, -and contract -> bank interactions. This is not intended to be a full blockchain app -but to simulate the Cosmos SDK x/wasm module close enough to gain confidence in -multi-contract deployements before testing them on a live blockchain. - -This explains some of the design for those who want to use the API, as well as those -who want to look under the hood. - -## Key APIs - -### App - -The main entry point to the system is called `App`, which represents a blockchain app. -It maintains an idea of block height and time, which you can update to simulate multiple -blocks. You can use `app.update_block(next_block)` to increment timestamp by 5s and height by 1 -(simulating a new block) or you can write any other mutator to advance more. - -It exposes an entry point `App.execute` that allows us to execute any `CosmosMsg` -and it wraps it as an atomic transaction. That is, only if `execute` returns success, will the state -be committed. It returns the data and a list of Events on successful execution or an `Err(String)` -on error. There are some helper methods tied to the `Executor` trait that create the `CosmosMsg` for -you to provide a less verbose API. `instantiate_contract`,`execute_contract`, and `send_tokens` are exposed -for your convenience in writing tests. Each execute one `CosmosMsg` atomically as if it was submitted by a user. -(You can also use `execute_multi` if you wish to run multiple message together that revert all state if any fail). - -The other key entry point to `App` is the `Querier` interface that it implements. In particular, you -can use `App.wrap()` to get a `QuerierWrapper`, which provides all kinds of nice APIs to query the -blockchain, like `all_balances` and `query_wasm_smart`. Putting this together, you have one `Storage` wrapped -into an application, where you can execute contracts and bank, query them easily, and update the current -`BlockInfo`, in an API that is not very verbose or cumbersome. Under the hood it will process all messages -returned from contracts, move "bank" tokens and call into other contracts. - -You can create an App for use in your testcode like: - -```rust -fn mock_app() -> App { - let env = mock_env(); - let api = Box::new(MockApi::default()); - let bank = BankKeeper::new(); - - App::new(api, env.block, bank, Box::new(MockStorage::new())) -} -``` - -Inside App, it maintains the root `Storage`, and the `BlockInfo` for the current block. -It also contains a `Router` (discussed below), which can process any `CosmosMsg` variant -by passing it to the proper "Keeper". - -Note: This properly handles submessages and reply blocks. - -Note: While the API currently supports custom messages, we don't currently have a way to handle/process them. - -### Contracts - -Before you can call contracts, you must `instantiate` them. And to instantiate them, you need a `code_id`. -In `wasmd`, this `code_id` points to some stored Wasm code that is then run. In multitest, we use it to -point to a `Box` that should be run. That is, you need to implement the `Contract` trait -and then add the contract to the app via `app.store_code(my_contract)`. - -The `Contract` trait defines the major entry points to any CosmWasm contract: `execute`, `instantiate`, `query`, -`sudo`, and `reply` (for submessages). Migration and IBC are currently not supported. - -In order to easily implement `Contract` from some existing contract code, we use the `ContractWrapper` struct, -which takes some function pointers and combines them. You can look in `test_helpers.rs` for some examples -or how to do so (and useful mocks for some test cases). Here is an example of wrapping a CosmWasm contract into -a `Contract` trait to add to an `App`: - -```rust -use cw20_escrow::contract::{ execute, instantiate, query }; - -pub fn contract_escrow() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query); - Box::new(contract) -} -``` - -If you are not using custom messages in your contract, you can just use `dyn Contract`. - -### Examples - -The best intro is most likely `integration.rs` in `cw20-escrow`, which shows sending and releasing native tokens in -an escrow, as well as sending and releasing cw20 tokens. The first one updates the global bank ledger, the second -actually shows how we can test orchestrating multiple contracts. - -## Implementation - -Besides the `App` and `Contract` interfaces which are the primary means with interacting with this module, -there are a number of components that need to be understood if you wish to extend the module (say, adding -a MockStaking module to handle `CosmosMsg::Staking` and `QueryRequest::Staking` calls). - -### StorageTransaction - -Since much of the logic, both on the app side, as well as in submessages, relies on rolling back any changes -if there is an error, we make heavy use of `StorageTransaction` under the hood. It takes a `&Storage` -reference and produces `&mut Storage` that can be written too. Notably, we can still query the original -(snapshot) storage while writing (which is very useful for the `Querier` interface for contracts). - -You can drop the `StorageTransaction` causing the changes to be rolled back (well, never committed), -or on success, you can commit it to the underlying storage. Note that there may be multiple levels -or `StorageTransaction` wrappers above the root (App) storage. Here is an example of using it, -that should make the concepts clear: - -```rust -// execute in cache -let mut cache = StorageTransaction::new(storage); -// Note that we *could* query the original `storage` while `cache` is live -let res = router.execute(&mut cache, block, contract.clone(), msg.msg); -if res.is_ok() { - cache.prepare().commit(storage); -} -``` - -### Modules - -There is only one root Storage, stored inside `App`. This is wrapped into a transaction, and then passed down -to other functions to work with. The code that modifies the Storage is divided into "Modules" much like the -CosmosSDK. Here, we plan to divide logic into one "module" for every `CosmosMsg` variant. `Bank` handles `BankMsg` -and `BankQuery`, `Wasm` handles `WasmMsg` and `WasmQuery`, etc. - -Each module produces a soon-to-be standardized interface to interact with. It exposes `execute` and `query` support -as well as some "admin" methods that cannot be called by users but are needed for testcase setup. I am working on a -design to make these "admin" methods more extensible as well. If you look at the two existing modules, you can -see the great similarity in `query` and `execute`, such that we could consider making a `Module` trait. - -```rust -pub trait Wasm -where - C: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - /// Handles all WasmQuery requests - fn query( - &self, - storage: &dyn Storage, - querier: &dyn Querier, - block: &BlockInfo, - request: WasmQuery, - ) -> Result; - - /// Handles all WasmMsg messages - fn execute( - &self, - storage: &mut dyn Storage, - router: &Router, - block: &BlockInfo, - sender: Addr, - msg: WasmMsg, - ) -> Result; - - // Add a new contract. Must be done on the base object, when no contracts running - fn store_code(&mut self, code: Box>) -> usize; - - /// Admin interface, cannot be called via CosmosMsg - fn sudo( - &self, - contract_addr: Addr, - storage: &mut dyn Storage, - router: &Router, - block: &BlockInfo, - msg: Vec, - ) -> Result; -} -``` - -```rust -/// Bank is a minimal contract-like interface that implements a bank module -/// It is initialized outside of the trait -pub trait Bank { - fn execute( - &self, - storage: &mut dyn Storage, - sender: Addr, - msg: BankMsg, - ) -> Result; - - fn query(&self, storage: &dyn Storage, request: BankQuery) -> Result; - - // Admin interface - fn init_balance( - &self, - storage: &mut dyn Storage, - account: &Addr, - amount: Vec, - ) -> Result<(), String>; -} -``` - -These traits should capture all public interactions with the module ("Keeper interface" if you come from -Cosmos SDK terminology). All other methods on the implementations should be private (or at least not exposed -outside of the multitest crate). - -### Router - -The `Router` groups all Modules in the system into one "macro-module" that can handle any `CosmosMsg`. -While `Bank` handles `BankMsg`, and `Wasm` handles `WasmMsg`, we need to combine them into a larger whole -to process them messages from `App`. This is the concept of the `Router`. If you look at the -`execute` method, you see it is quite simple: - -```rust -impl Router { - pub fn execute( - &self, - storage: &mut dyn Storage, - block: &BlockInfo, - sender: Addr, - msg: CosmosMsg, - ) -> Result { - match msg { - CosmosMsg::Wasm(msg) => self.wasm.execute(storage, &self, block, sender, msg), - // FIXME: we could pass in unused router and block for consistency - CosmosMsg::Bank(msg) => self.bank.execute(storage, sender, msg), - _ => unimplemented!(), - } - } -} -``` - -Note that the only way one module can call or query another module is by dispatching messages via the `Router`. -This allows us to implement an independent `Wasm` in a way that it can process `SubMsg` that call into `Bank`. -You can see an example of that in WasmKeeper.send, where it moves bank tokens from one account to another: - -```rust -impl WasmKeeper { - fn send>( - &self, - storage: &mut dyn Storage, - router: &Router, - block: &BlockInfo, - sender: T, - recipient: String, - amount: &[Coin], - ) -> Result { - if !amount.is_empty() { - let msg = BankMsg::Send { - to_address: recipient, - amount: amount.to_vec(), - }; - let res = router.execute(storage, block, sender.into(), msg.into())?; - Ok(res) - } else { - Ok(AppResponse::default()) - } - } -} -``` diff --git a/packages/multi-test/MIGRATING.md b/packages/multi-test/MIGRATING.md deleted file mode 100644 index 673e17dd9..000000000 --- a/packages/multi-test/MIGRATING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Migration Tips - -Note, that we currently do not support this fully for external use. -These are some partial tips to help in upgrades. - -## 0.7 -> 0.8 - -* `SimpleBank` was renamed to `BankKeeper` -* `App::new` takes `Box` rather than a closure as the last argument. -* Your test setup will look something like this: - - ```rust - pub fn mock_app() -> App { - let env = mock_env(); - let api = Box::new(MockApi::default()); - let bank = BankKeeper::new(); - - App::new(api, env.block, bank, Box::new(MockStorage::new())) - } - ``` -* `App.set_bank_balance` was renamed to `init_bank_balance`, with the same args. -* You will want to import `cw_multi_test::Executor` in order to get access to the execution helpers - like `App.execute_contract`, and `App.instantiate_contract` -* `App.instantiate_contract` takes one additional arg: `admin: Option`. You can set it to `None` - unless you want to test migrations. diff --git a/packages/multi-test/NOTICE b/packages/multi-test/NOTICE deleted file mode 100644 index 44944a1ec..000000000 --- a/packages/multi-test/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -CW Multi Test: Test helpers for multi-contract interactions -Copyright (C) 2020-2021 Confio OÜ - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/multi-test/README.md b/packages/multi-test/README.md deleted file mode 100644 index b0d659b69..000000000 --- a/packages/multi-test/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Multi Test: Test helpers for multi-contract interactions - -Warning: **Alpha Software** Designed for internal use only. - -This is used for testing cw-plus contracts, we have no API -stability currently. We are working on refactoring it and will -expose a more refined version for use in other contracts. (Ideally -in cw-plus 0.9 or 0.10). - -**Use at your own risk** - -Let us run unit tests with contracts calling contracts, and calling -in and out of bank. - -This only works with contracts and bank currently. We are working -on refactoring to make it more extensible for more handlers, -including custom messages/queries as well as IBC. - - diff --git a/packages/multi-test/clippy.toml b/packages/multi-test/clippy.toml deleted file mode 100644 index 15906305c..000000000 --- a/packages/multi-test/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -too-many-arguments-threshold = 10 diff --git a/packages/multi-test/src/app.rs b/packages/multi-test/src/app.rs deleted file mode 100644 index 9087cebf1..000000000 --- a/packages/multi-test/src/app.rs +++ /dev/null @@ -1,2804 +0,0 @@ -use std::fmt::{self, Debug}; -use std::marker::PhantomData; - -use anyhow::bail; -use anyhow::Result as AnyResult; -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; -use cosmwasm_std::{ - from_slice, to_binary, Addr, Api, Binary, BlockInfo, ContractResult, CosmosMsg, CustomQuery, - Empty, Querier, QuerierResult, QuerierWrapper, QueryRequest, Record, Storage, SystemError, - SystemResult, -}; -use schemars::JsonSchema; -use serde::de::DeserializeOwned; -use serde::Serialize; - -use crate::bank::{Bank, BankKeeper, BankSudo}; -use crate::contracts::Contract; -use crate::executor::{AppResponse, Executor}; -use crate::module::{FailingModule, Module}; -use crate::staking::{Distribution, DistributionKeeper, StakeKeeper, Staking, StakingSudo}; -use crate::transactions::transactional; -use crate::wasm::{ContractData, Wasm, WasmKeeper, WasmSudo}; - -pub fn next_block(block: &mut BlockInfo) { - block.time = block.time.plus_seconds(5); - block.height += 1; -} - -/// Type alias for default build `App` to make its storing simpler in typical scenario -pub type BasicApp = App< - BankKeeper, - MockApi, - MockStorage, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, ->; - -/// Router is a persisted state. You can query this. -/// Execution generally happens on the RouterCache, which then can be atomically committed or rolled back. -/// We offer .execute() as a wrapper around cache, execute, commit/rollback process. -pub struct App< - Bank = BankKeeper, - Api = MockApi, - Storage = MockStorage, - Custom = FailingModule, - Wasm = WasmKeeper, - Staking = StakeKeeper, - Distr = DistributionKeeper, -> { - router: Router, - api: Api, - storage: Storage, - block: BlockInfo, -} - -fn no_init( - _: &mut Router, - _: &dyn Api, - _: &mut dyn Storage, -) { -} - -impl Default for BasicApp { - fn default() -> Self { - Self::new(no_init) - } -} - -impl BasicApp { - /// Creates new default `App` implementation working with Empty custom messages. - pub fn new(init_fn: F) -> Self - where - F: FnOnce( - &mut Router< - BankKeeper, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, - >, - &dyn Api, - &mut dyn Storage, - ), - { - AppBuilder::new().build(init_fn) - } -} - -/// Creates new default `App` implementation working with customized exec and query messages. -/// Outside of `App` implementation to make type elision better. -pub fn custom_app(init_fn: F) -> BasicApp -where - ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: Debug + CustomQuery + DeserializeOwned + 'static, - F: FnOnce( - &mut Router< - BankKeeper, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, - >, - &dyn Api, - &mut dyn Storage, - ), -{ - AppBuilder::new_custom().build(init_fn) -} - -impl Querier - for App -where - CustomT::ExecT: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, - WasmT: Wasm, - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - StakingT: Staking, - DistrT: Distribution, -{ - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - self.router - .querier(&self.api, &self.storage, &self.block) - .raw_query(bin_request) - } -} - -impl Executor - for App -where - CustomT::ExecT: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, - WasmT: Wasm, - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - StakingT: Staking, - DistrT: Distribution, -{ - fn execute( - &mut self, - sender: Addr, - msg: cosmwasm_std::CosmosMsg, - ) -> AnyResult { - let mut all = self.execute_multi(sender, vec![msg])?; - let res = all.pop().unwrap(); - Ok(res) - } -} - -/// This is essential to create a custom app with custom handler. -/// let mut app = BasicAppBuilder::::new_custom().with_custom(handler).build(); -pub type BasicAppBuilder = AppBuilder< - BankKeeper, - MockApi, - MockStorage, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, ->; - -/// Utility to build App in stages. If particular items wont be set, defaults would be used -pub struct AppBuilder { - api: Api, - block: BlockInfo, - storage: Storage, - bank: Bank, - wasm: Wasm, - custom: Custom, - staking: Staking, - distribution: Distr, -} - -impl Default - for AppBuilder< - BankKeeper, - MockApi, - MockStorage, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, - > -{ - fn default() -> Self { - Self::new() - } -} - -impl - AppBuilder< - BankKeeper, - MockApi, - MockStorage, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, - > -{ - /// Creates builder with default components working with empty exec and query messages. - pub fn new() -> Self { - AppBuilder { - api: MockApi::default(), - block: mock_env().block, - storage: MockStorage::new(), - bank: BankKeeper::new(), - wasm: WasmKeeper::new(), - custom: FailingModule::new(), - staking: StakeKeeper::new(), - distribution: DistributionKeeper::new(), - } - } -} - -impl - AppBuilder< - BankKeeper, - MockApi, - MockStorage, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, - > -where - ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: Debug + CustomQuery + DeserializeOwned + 'static, -{ - /// Creates builder with default components designed to work with custom exec and query - /// messages. - pub fn new_custom() -> Self { - AppBuilder { - api: MockApi::default(), - block: mock_env().block, - storage: MockStorage::new(), - bank: BankKeeper::new(), - wasm: WasmKeeper::new(), - custom: FailingModule::new(), - staking: StakeKeeper::new(), - distribution: DistributionKeeper::new(), - } - } -} - -impl - AppBuilder -{ - /// Overwrites default wasm executor. - /// - /// At this point it is needed that new wasm implements some `Wasm` trait, but it doesn't need - /// to be bound to Bank or Custom yet - as those may change. The cross-components validation is - /// done on final building. - /// - /// Also it is possible to completely abandon trait bounding here which would not be bad idea, - /// however it might make the message on build creepy in many cases, so as for properly build - /// `App` we always want `Wasm` to be `Wasm`, some checks are done early. - pub fn with_wasm>( - self, - wasm: NewWasm, - ) -> AppBuilder { - let AppBuilder { - bank, - api, - storage, - custom, - block, - staking, - distribution, - .. - } = self; - - AppBuilder { - api, - block, - storage, - bank, - wasm, - custom, - staking, - distribution, - } - } - - /// Overwrites default bank interface - pub fn with_bank( - self, - bank: NewBank, - ) -> AppBuilder { - let AppBuilder { - wasm, - api, - storage, - custom, - block, - staking, - distribution, - .. - } = self; - - AppBuilder { - api, - block, - storage, - bank, - wasm, - custom, - staking, - distribution, - } - } - - /// Overwrites default api interface - pub fn with_api( - self, - api: NewApi, - ) -> AppBuilder { - let AppBuilder { - wasm, - bank, - storage, - custom, - block, - staking, - distribution, - .. - } = self; - - AppBuilder { - api, - block, - storage, - bank, - wasm, - custom, - staking, - distribution, - } - } - - /// Overwrites default storage interface - pub fn with_storage( - self, - storage: NewStorage, - ) -> AppBuilder { - let AppBuilder { - wasm, - api, - bank, - custom, - block, - staking, - distribution, - .. - } = self; - - AppBuilder { - api, - block, - storage, - bank, - wasm, - custom, - staking, - distribution, - } - } - - /// Overwrites default custom messages handler - /// - /// At this point it is needed that new custom implements some `Module` trait, but it doesn't need - /// to be bound to ExecC or QueryC yet - as those may change. The cross-components validation is - /// done on final building. - /// - /// Also it is possible to completely abandon trait bounding here which would not be bad idea, - /// however it might make the message on build creepy in many cases, so as for properly build - /// `App` we always want `Wasm` to be `Wasm`, some checks are done early. - pub fn with_custom( - self, - custom: NewCustom, - ) -> AppBuilder { - let AppBuilder { - wasm, - bank, - api, - storage, - block, - staking, - distribution, - .. - } = self; - - AppBuilder { - api, - block, - storage, - bank, - wasm, - custom, - staking, - distribution, - } - } - - /// Overwrites default bank interface - pub fn with_staking( - self, - staking: NewStaking, - ) -> AppBuilder { - let AppBuilder { - wasm, - api, - storage, - custom, - block, - bank, - distribution, - .. - } = self; - - AppBuilder { - api, - block, - storage, - bank, - wasm, - custom, - staking, - distribution, - } - } - - /// Overwrites default bank interface - pub fn with_distribution( - self, - distribution: NewDistribution, - ) -> AppBuilder { - let AppBuilder { - wasm, - api, - storage, - custom, - block, - staking, - bank, - .. - } = self; - - AppBuilder { - api, - block, - storage, - bank, - wasm, - custom, - staking, - distribution, - } - } - - /// Overwrites default initial block - pub fn with_block(mut self, block: BlockInfo) -> Self { - self.block = block; - self - } - - /// Builds final `App`. At this point all components type have to be properly related to each - /// other. If there are some generics related compilation error make sure, that all components - /// are properly relating to each other. - pub fn build( - self, - init_fn: F, - ) -> App - where - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - WasmT: Wasm, - StakingT: Staking, - DistrT: Distribution, - F: FnOnce(&mut Router, &dyn Api, &mut dyn Storage), - { - let router = Router { - wasm: self.wasm, - bank: self.bank, - custom: self.custom, - staking: self.staking, - distribution: self.distribution, - }; - - let mut app = App { - router, - api: self.api, - block: self.block, - storage: self.storage, - }; - app.init_modules(init_fn); - app - } -} - -impl - App -where - WasmT: Wasm, - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - StakingT: Staking, - DistrT: Distribution, -{ - pub fn init_modules(&mut self, init_fn: F) -> T - where - F: FnOnce( - &mut Router, - &dyn Api, - &mut dyn Storage, - ) -> T, - { - init_fn(&mut self.router, &self.api, &mut self.storage) - } - - pub fn read_module(&self, query_fn: F) -> T - where - F: FnOnce(&Router, &dyn Api, &dyn Storage) -> T, - { - query_fn(&self.router, &self.api, &self.storage) - } -} - -// Helper functions to call some custom WasmKeeper logic. -// They show how we can easily add such calls to other custom keepers (CustomT, StakingT, etc) -impl - App< - BankT, - ApiT, - StorageT, - CustomT, - WasmKeeper, - StakingT, - DistrT, - > -where - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - StakingT: Staking, - DistrT: Distribution, - CustomT::ExecT: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - /// This registers contract code (like uploading wasm bytecode on a chain), - /// so it can later be used to instantiate a contract. - pub fn store_code(&mut self, code: Box>) -> u64 { - self.init_modules(|router, _, _| router.wasm.store_code(code) as u64) - } - - /// This allows to get `ContractData` for specific contract - pub fn contract_data(&self, address: &Addr) -> AnyResult { - self.read_module(|router, _, storage| router.wasm.load_contract(storage, address)) - } - - /// This gets a raw state dump of all key-values held by a given contract - pub fn dump_wasm_raw(&self, address: &Addr) -> Vec { - self.read_module(|router, _, storage| router.wasm.dump_wasm_raw(storage, address)) - } -} - -impl - App -where - CustomT::ExecT: std::fmt::Debug + PartialEq + Clone + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, - WasmT: Wasm, - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - StakingT: Staking, - DistrT: Distribution, -{ - pub fn set_block(&mut self, block: BlockInfo) { - self.block = block; - } - - // this let's use use "next block" steps that add eg. one height and 5 seconds - pub fn update_block(&mut self, action: F) { - action(&mut self.block); - } - - /// Returns a copy of the current block_info - pub fn block_info(&self) -> BlockInfo { - self.block.clone() - } - - /// Simple helper so we get access to all the QuerierWrapper helpers, - /// eg. wrap().query_wasm_smart, query_all_balances, ... - pub fn wrap(&self) -> QuerierWrapper { - QuerierWrapper::new(self) - } - - /// Runs multiple CosmosMsg in one atomic operation. - /// This will create a cache before the execution, so no state changes are persisted if any of them - /// return an error. But all writes are persisted on success. - pub fn execute_multi( - &mut self, - sender: Addr, - msgs: Vec>, - ) -> AnyResult> { - // we need to do some caching of storage here, once in the entry point: - // meaning, wrap current state, all writes go to a cache, only when execute - // returns a success do we flush it (otherwise drop it) - - let Self { - block, - router, - api, - storage, - } = self; - - transactional(&mut *storage, |write_cache, _| { - msgs.into_iter() - .map(|msg| router.execute(&*api, write_cache, block, sender.clone(), msg)) - .collect() - }) - } - - /// Call a smart contract in "sudo" mode. - /// This will create a cache before the execution, so no state changes are persisted if this - /// returns an error, but all are persisted on success. - pub fn wasm_sudo>( - &mut self, - contract_addr: U, - msg: &T, - ) -> AnyResult { - let msg = to_binary(msg)?; - - let Self { - block, - router, - api, - storage, - } = self; - - transactional(&mut *storage, |write_cache, _| { - router - .wasm - .sudo(&*api, contract_addr.into(), write_cache, router, block, msg) - }) - } - - /// Runs arbitrary SudoMsg. - /// This will create a cache before the execution, so no state changes are persisted if this - /// returns an error, but all are persisted on success. - pub fn sudo(&mut self, msg: SudoMsg) -> AnyResult { - // we need to do some caching of storage here, once in the entry point: - // meaning, wrap current state, all writes go to a cache, only when execute - // returns a success do we flush it (otherwise drop it) - let Self { - block, - router, - api, - storage, - } = self; - - transactional(&mut *storage, |write_cache, _| { - router.sudo(&*api, write_cache, block, msg) - }) - } -} - -pub struct Router { - // this can remain crate-only as all special functions are wired up to app currently - // we need to figure out another format for wasm, as some like sudo need to be called after init - pub(crate) wasm: Wasm, - // these must be pub so we can initialize them (super user) on build - pub bank: Bank, - pub custom: Custom, - pub staking: Staking, - pub distribution: Distr, -} - -impl Router -where - CustomT::ExecT: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, - CustomT: Module, - WasmT: Wasm, - BankT: Bank, - StakingT: Staking, - DistrT: Distribution, -{ - pub fn querier<'a>( - &'a self, - api: &'a dyn Api, - storage: &'a dyn Storage, - block_info: &'a BlockInfo, - ) -> RouterQuerier<'a, CustomT::ExecT, CustomT::QueryT> { - RouterQuerier { - router: self, - api, - storage, - block_info, - } - } -} - -/// We use it to allow calling into modules from another module in sudo mode. -/// Things like gov proposals belong here. -pub enum SudoMsg { - Bank(BankSudo), - Custom(Empty), - Staking(StakingSudo), - Wasm(WasmSudo), -} - -impl From for SudoMsg { - fn from(wasm: WasmSudo) -> Self { - SudoMsg::Wasm(wasm) - } -} - -impl From for SudoMsg { - fn from(bank: BankSudo) -> Self { - SudoMsg::Bank(bank) - } -} - -impl From for SudoMsg { - fn from(staking: StakingSudo) -> Self { - SudoMsg::Staking(staking) - } -} - -pub trait CosmosRouter { - type ExecC; - type QueryC: CustomQuery; - - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - block: &BlockInfo, - sender: Addr, - msg: CosmosMsg, - ) -> AnyResult; - - fn query( - &self, - api: &dyn Api, - storage: &dyn Storage, - block: &BlockInfo, - request: QueryRequest, - ) -> AnyResult; - - fn sudo( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - block: &BlockInfo, - msg: SudoMsg, - ) -> AnyResult; -} - -impl CosmosRouter - for Router -where - CustomT::ExecT: std::fmt::Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, - CustomT: Module, - WasmT: Wasm, - BankT: Bank, - StakingT: Staking, - DistrT: Distribution, -{ - type ExecC = CustomT::ExecT; - type QueryC = CustomT::QueryT; - - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - block: &BlockInfo, - sender: Addr, - msg: CosmosMsg, - ) -> AnyResult { - match msg { - CosmosMsg::Wasm(msg) => self.wasm.execute(api, storage, self, block, sender, msg), - CosmosMsg::Bank(msg) => self.bank.execute(api, storage, self, block, sender, msg), - CosmosMsg::Custom(msg) => self.custom.execute(api, storage, self, block, sender, msg), - CosmosMsg::Staking(msg) => self.staking.execute(api, storage, self, block, sender, msg), - CosmosMsg::Distribution(msg) => self - .distribution - .execute(api, storage, self, block, sender, msg), - _ => bail!("Cannot execute {:?}", msg), - } - } - - /// this is used by `RouterQuerier` to actual implement the `Querier` interface. - /// you most likely want to use `router.querier(storage, block).wrap()` to get a - /// QuerierWrapper to interact with - fn query( - &self, - api: &dyn Api, - storage: &dyn Storage, - block: &BlockInfo, - request: QueryRequest, - ) -> AnyResult { - let querier = self.querier(api, storage, block); - match request { - QueryRequest::Wasm(req) => self.wasm.query(api, storage, &querier, block, req), - QueryRequest::Bank(req) => self.bank.query(api, storage, &querier, block, req), - QueryRequest::Custom(req) => self.custom.query(api, storage, &querier, block, req), - QueryRequest::Staking(req) => self.staking.query(api, storage, &querier, block, req), - _ => unimplemented!(), - } - } - - fn sudo( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - block: &BlockInfo, - msg: SudoMsg, - ) -> AnyResult { - match msg { - SudoMsg::Wasm(msg) => { - self.wasm - .sudo(api, msg.contract_addr, storage, self, block, msg.msg) - } - SudoMsg::Bank(msg) => self.bank.sudo(api, storage, self, block, msg), - SudoMsg::Staking(msg) => self.staking.sudo(api, storage, self, block, msg), - SudoMsg::Custom(_) => unimplemented!(), - } - } -} - -pub struct MockRouter(PhantomData<(ExecC, QueryC)>); - -impl Default for MockRouter { - fn default() -> Self { - Self::new() - } -} - -impl MockRouter { - pub fn new() -> Self - where - QueryC: CustomQuery, - { - MockRouter(PhantomData) - } -} - -impl CosmosRouter for MockRouter -where - QueryC: CustomQuery, -{ - type ExecC = ExecC; - type QueryC = QueryC; - - fn execute( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _block: &BlockInfo, - _sender: Addr, - _msg: CosmosMsg, - ) -> AnyResult { - panic!("Cannot execute MockRouters"); - } - - fn query( - &self, - _api: &dyn Api, - _storage: &dyn Storage, - _block: &BlockInfo, - _request: QueryRequest, - ) -> AnyResult { - panic!("Cannot query MockRouters"); - } - - fn sudo( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _block: &BlockInfo, - _msg: SudoMsg, - ) -> AnyResult { - panic!("Cannot sudo MockRouters"); - } -} - -pub struct RouterQuerier<'a, ExecC, QueryC> { - router: &'a dyn CosmosRouter, - api: &'a dyn Api, - storage: &'a dyn Storage, - block_info: &'a BlockInfo, -} - -impl<'a, ExecC, QueryC> RouterQuerier<'a, ExecC, QueryC> { - pub fn new( - router: &'a dyn CosmosRouter, - api: &'a dyn Api, - storage: &'a dyn Storage, - block_info: &'a BlockInfo, - ) -> Self { - Self { - router, - api, - storage, - block_info, - } - } -} - -impl<'a, ExecC, QueryC> Querier for RouterQuerier<'a, ExecC, QueryC> -where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static, -{ - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return SystemResult::Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - let contract_result: ContractResult = self - .router - .query(self.api, self.storage, self.block_info, request) - .into(); - SystemResult::Ok(contract_result) - } -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::testing::MockQuerier; - use cosmwasm_std::{ - coin, coins, to_binary, AllBalanceResponse, Attribute, BankMsg, BankQuery, Coin, Event, - OverflowError, OverflowOperation, Reply, StdError, StdResult, SubMsg, WasmMsg, - }; - - use crate::error::Error; - use crate::test_helpers::contracts::{caller, echo, error, hackatom, payout, reflect}; - use crate::test_helpers::{CustomMsg, EmptyMsg}; - use crate::transactions::StorageTransaction; - - fn get_balance( - app: &App, - addr: &Addr, - ) -> Vec - where - CustomT::ExecT: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, - WasmT: Wasm, - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - { - app.wrap().query_all_balances(addr).unwrap() - } - - #[test] - fn update_block() { - let mut app = App::default(); - - let BlockInfo { time, height, .. } = app.block; - app.update_block(next_block); - - assert_eq!(time.plus_seconds(5), app.block.time); - assert_eq!(height + 1, app.block.height); - } - - #[test] - fn send_tokens() { - let owner = Addr::unchecked("owner"); - let rcpt = Addr::unchecked("receiver"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - let rcpt_funds = vec![coin(5, "btc")]; - - let mut app = App::new(|router, _, storage| { - // initialization moved to App construction - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - router - .bank - .init_balance(storage, &rcpt, rcpt_funds) - .unwrap(); - }); - - // send both tokens - let to_send = vec![coin(30, "eth"), coin(5, "btc")]; - let msg: cosmwasm_std::CosmosMsg = BankMsg::Send { - to_address: rcpt.clone().into(), - amount: to_send, - } - .into(); - app.execute(owner.clone(), msg.clone()).unwrap(); - let rich = get_balance(&app, &owner); - assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich); - let poor = get_balance(&app, &rcpt); - assert_eq!(vec![coin(10, "btc"), coin(30, "eth")], poor); - - // can send from other account (but funds will be deducted from sender) - app.execute(rcpt.clone(), msg).unwrap(); - - // cannot send too much - let msg = BankMsg::Send { - to_address: rcpt.into(), - amount: coins(20, "btc"), - } - .into(); - app.execute(owner.clone(), msg).unwrap_err(); - - let rich = get_balance(&app, &owner); - assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich); - } - - #[test] - fn simple_contract() { - // set personal balance - let owner = Addr::unchecked("owner"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // set up contract - let code_id = app.store_code(payout::contract()); - let msg = payout::InstantiateMessage { - payout: coin(5, "eth"), - }; - let contract_addr = app - .instantiate_contract( - code_id, - owner.clone(), - &msg, - &coins(23, "eth"), - "Payout", - None, - ) - .unwrap(); - - let contract_data = app.contract_data(&contract_addr).unwrap(); - assert_eq!( - contract_data, - ContractData { - code_id: code_id as usize, - creator: owner.clone(), - admin: None, - label: "Payout".to_owned(), - created: app.block_info().height - } - ); - - // sender funds deducted - let sender = get_balance(&app, &owner); - assert_eq!(sender, vec![coin(20, "btc"), coin(77, "eth")]); - // get contract address, has funds - let funds = get_balance(&app, &contract_addr); - assert_eq!(funds, coins(23, "eth")); - - // create empty account - let random = Addr::unchecked("random"); - let funds = get_balance(&app, &random); - assert_eq!(funds, vec![]); - - // do one payout and see money coming in - let res = app - .execute_contract(random.clone(), contract_addr.clone(), &EmptyMsg {}, &[]) - .unwrap(); - assert_eq!(3, res.events.len()); - - // the call to payout does emit this as well as custom attributes - let payout_exec = &res.events[0]; - assert_eq!(payout_exec.ty.as_str(), "execute"); - assert_eq!(payout_exec.attributes, [("_contract_addr", &contract_addr)]); - - // next is a custom wasm event - let custom_attrs = res.custom_attrs(1); - assert_eq!(custom_attrs, [("action", "payout")]); - - // then the transfer event - let expected_transfer = Event::new("transfer") - .add_attribute("recipient", "random") - .add_attribute("sender", &contract_addr) - .add_attribute("amount", "5eth"); - assert_eq!(&expected_transfer, &res.events[2]); - - // random got cash - let funds = get_balance(&app, &random); - assert_eq!(funds, coins(5, "eth")); - // contract lost it - let funds = get_balance(&app, &contract_addr); - assert_eq!(funds, coins(18, "eth")); - } - - #[test] - fn reflect_success() { - // set personal balance - let owner = Addr::unchecked("owner"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - - let mut app = custom_app::(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // set up payout contract - let payout_id = app.store_code(payout::contract()); - let msg = payout::InstantiateMessage { - payout: coin(5, "eth"), - }; - let payout_addr = app - .instantiate_contract( - payout_id, - owner.clone(), - &msg, - &coins(23, "eth"), - "Payout", - None, - ) - .unwrap(); - - // set up reflect contract - let reflect_id = app.store_code(reflect::contract()); - let reflect_addr = app - .instantiate_contract(reflect_id, owner, &EmptyMsg {}, &[], "Reflect", None) - .unwrap(); - - // reflect account is empty - let funds = get_balance(&app, &reflect_addr); - assert_eq!(funds, vec![]); - // reflect count is 1 - let qres: payout::CountResponse = app - .wrap() - .query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {}) - .unwrap(); - assert_eq!(0, qres.count); - - // reflecting payout message pays reflect contract - let msg = SubMsg::new(WasmMsg::Execute { - contract_addr: payout_addr.clone().into(), - msg: b"{}".into(), - funds: vec![], - }); - let msgs = reflect::Message { - messages: vec![msg], - }; - let res = app - .execute_contract(Addr::unchecked("random"), reflect_addr.clone(), &msgs, &[]) - .unwrap(); - - // ensure the attributes were relayed from the sub-message - assert_eq!(4, res.events.len(), "{:?}", res.events); - - // reflect only returns standard wasm-execute event - let ref_exec = &res.events[0]; - assert_eq!(ref_exec.ty.as_str(), "execute"); - assert_eq!(ref_exec.attributes, [("_contract_addr", &reflect_addr)]); - - // the call to payout does emit this as well as custom attributes - let payout_exec = &res.events[1]; - assert_eq!(payout_exec.ty.as_str(), "execute"); - assert_eq!(payout_exec.attributes, [("_contract_addr", &payout_addr)]); - - let payout = &res.events[2]; - assert_eq!(payout.ty.as_str(), "wasm"); - assert_eq!( - payout.attributes, - [ - ("_contract_addr", payout_addr.as_str()), - ("action", "payout") - ] - ); - - // final event is the transfer from bank - let second = &res.events[3]; - assert_eq!(second.ty.as_str(), "transfer"); - assert_eq!(3, second.attributes.len()); - assert_eq!(second.attributes[0], ("recipient", &reflect_addr)); - assert_eq!(second.attributes[1], ("sender", &payout_addr)); - assert_eq!(second.attributes[2], ("amount", "5eth")); - - // ensure transfer was executed with reflect as sender - let funds = get_balance(&app, &reflect_addr); - assert_eq!(funds, coins(5, "eth")); - - // reflect count updated - let qres: payout::CountResponse = app - .wrap() - .query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {}) - .unwrap(); - assert_eq!(1, qres.count); - } - - #[test] - fn reflect_error() { - // set personal balance - let owner = Addr::unchecked("owner"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - - let mut app = custom_app::(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // set up reflect contract - let reflect_id = app.store_code(reflect::contract()); - let reflect_addr = app - .instantiate_contract( - reflect_id, - owner, - &EmptyMsg {}, - &coins(40, "eth"), - "Reflect", - None, - ) - .unwrap(); - - // reflect has 40 eth - let funds = get_balance(&app, &reflect_addr); - assert_eq!(funds, coins(40, "eth")); - let random = Addr::unchecked("random"); - - // sending 7 eth works - let msg = SubMsg::new(BankMsg::Send { - to_address: random.clone().into(), - amount: coins(7, "eth"), - }); - let msgs = reflect::Message { - messages: vec![msg], - }; - let res = app - .execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[]) - .unwrap(); - // no wasm events as no attributes - assert_eq!(2, res.events.len()); - // standard wasm-execute event - let exec = &res.events[0]; - assert_eq!(exec.ty.as_str(), "execute"); - assert_eq!(exec.attributes, [("_contract_addr", &reflect_addr)]); - // only transfer event from bank - let transfer = &res.events[1]; - assert_eq!(transfer.ty.as_str(), "transfer"); - - // ensure random got paid - let funds = get_balance(&app, &random); - assert_eq!(funds, coins(7, "eth")); - - // reflect count should be updated to 1 - let qres: payout::CountResponse = app - .wrap() - .query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {}) - .unwrap(); - assert_eq!(1, qres.count); - - // sending 8 eth, then 3 btc should fail both - let msg = SubMsg::new(BankMsg::Send { - to_address: random.clone().into(), - amount: coins(8, "eth"), - }); - let msg2 = SubMsg::new(BankMsg::Send { - to_address: random.clone().into(), - amount: coins(3, "btc"), - }); - let msgs = reflect::Message { - messages: vec![msg, msg2], - }; - let err = app - .execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[]) - .unwrap_err(); - assert_eq!( - StdError::overflow(OverflowError::new(OverflowOperation::Sub, 0, 3)), - err.downcast().unwrap() - ); - - // first one should have been rolled-back on error (no second payment) - let funds = get_balance(&app, &random); - assert_eq!(funds, coins(7, "eth")); - - // failure should not update reflect count - let qres: payout::CountResponse = app - .wrap() - .query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {}) - .unwrap(); - assert_eq!(1, qres.count); - } - - #[test] - fn sudo_works() { - let owner = Addr::unchecked("owner"); - let init_funds = vec![coin(100, "eth")]; - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - let payout_id = app.store_code(payout::contract()); - let msg = payout::InstantiateMessage { - payout: coin(5, "eth"), - }; - let payout_addr = app - .instantiate_contract(payout_id, owner, &msg, &coins(23, "eth"), "Payout", None) - .unwrap(); - - // count is 1 - let payout::CountResponse { count } = app - .wrap() - .query_wasm_smart(&payout_addr, &payout::QueryMsg::Count {}) - .unwrap(); - assert_eq!(1, count); - - // wasm_sudo call - let msg = payout::SudoMsg { set_count: 25 }; - app.wasm_sudo(payout_addr.clone(), &msg).unwrap(); - - // count is 25 - let payout::CountResponse { count } = app - .wrap() - .query_wasm_smart(&payout_addr, &payout::QueryMsg::Count {}) - .unwrap(); - assert_eq!(25, count); - - // we can do the same with sudo call - let msg = payout::SudoMsg { set_count: 49 }; - let sudo_msg = WasmSudo { - contract_addr: payout_addr.clone(), - msg: to_binary(&msg).unwrap(), - }; - app.sudo(sudo_msg.into()).unwrap(); - - let payout::CountResponse { count } = app - .wrap() - .query_wasm_smart(&payout_addr, &payout::QueryMsg::Count {}) - .unwrap(); - assert_eq!(49, count); - } - - // this demonstrates that we can mint tokens and send from other accounts via a custom module, - // as an example of ability to do privileged actions - mod custom_handler { - use super::*; - - use anyhow::{bail, Result as AnyResult}; - use cw_storage_plus::Item; - use serde::{Deserialize, Serialize}; - - use crate::Executor; - - const LOTTERY: Item = Item::new("lottery"); - const PITY: Item = Item::new("pity"); - - #[derive(Clone, std::fmt::Debug, PartialEq, JsonSchema, Serialize, Deserialize)] - struct CustomMsg { - // we mint LOTTERY tokens to this one - lucky_winner: String, - // we transfer PITY from lucky_winner to runner_up - runner_up: String, - } - - struct CustomHandler {} - - impl Module for CustomHandler { - type ExecT = CustomMsg; - type QueryT = Empty; - type SudoT = Empty; - - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - _sender: Addr, - msg: Self::ExecT, - ) -> AnyResult - where - ExecC: - std::fmt::Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static, - { - let lottery = LOTTERY.load(storage)?; - let pity = PITY.load(storage)?; - - // mint new tokens - let mint = BankSudo::Mint { - to_address: msg.lucky_winner.clone(), - amount: vec![lottery], - }; - router.sudo(api, storage, block, mint.into())?; - - // send from an arbitrary account (not the module) - let transfer = BankMsg::Send { - to_address: msg.runner_up, - amount: vec![pity], - }; - let rcpt = api.addr_validate(&msg.lucky_winner)?; - router.execute(api, storage, block, rcpt, transfer.into())?; - - Ok(AppResponse::default()) - } - - fn sudo( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - _msg: Self::SudoT, - ) -> AnyResult - where - ExecC: - std::fmt::Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static, - { - bail!("sudo not implemented for CustomHandler") - } - - fn query( - &self, - _api: &dyn Api, - _storage: &dyn Storage, - _querier: &dyn Querier, - _block: &BlockInfo, - _request: Self::QueryT, - ) -> AnyResult { - bail!("query not implemented for CustomHandler") - } - } - - impl CustomHandler { - // this is a custom initialization method - pub fn set_payout( - &self, - storage: &mut dyn Storage, - lottery: Coin, - pity: Coin, - ) -> AnyResult<()> { - LOTTERY.save(storage, &lottery)?; - PITY.save(storage, &pity)?; - Ok(()) - } - } - - // let's call this custom handler - #[test] - fn dispatches_messages() { - let winner = "winner".to_string(); - let second = "second".to_string(); - - // payments. note 54321 - 12321 = 42000 - let denom = "tix"; - let lottery = coin(54321, denom); - let bonus = coin(12321, denom); - - let mut app = BasicAppBuilder::::new_custom() - .with_custom(CustomHandler {}) - .build(|router, _, storage| { - router - .custom - .set_payout(storage, lottery.clone(), bonus.clone()) - .unwrap(); - }); - - // query that balances are empty - let start = app.wrap().query_balance(&winner, denom).unwrap(); - assert_eq!(start, coin(0, denom)); - - // trigger the custom module - let msg = CosmosMsg::Custom(CustomMsg { - lucky_winner: winner.clone(), - runner_up: second.clone(), - }); - app.execute(Addr::unchecked("anyone"), msg).unwrap(); - - // see if coins were properly added - let big_win = app.wrap().query_balance(&winner, denom).unwrap(); - assert_eq!(big_win, coin(42000, denom)); - let little_win = app.wrap().query_balance(&second, denom).unwrap(); - assert_eq!(little_win, bonus); - } - } - - #[test] - fn reflect_submessage_reply_works() { - // set personal balance - let owner = Addr::unchecked("owner"); - let random = Addr::unchecked("random"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - - let mut app = custom_app::(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // set up reflect contract - let reflect_id = app.store_code(reflect::contract()); - let reflect_addr = app - .instantiate_contract( - reflect_id, - owner, - &EmptyMsg {}, - &coins(40, "eth"), - "Reflect", - None, - ) - .unwrap(); - - // no reply writen beforehand - let query = reflect::QueryMsg::Reply { id: 123 }; - let res: StdResult = app.wrap().query_wasm_smart(&reflect_addr, &query); - res.unwrap_err(); - - // reflect sends 7 eth, success - let msg = SubMsg::reply_always( - BankMsg::Send { - to_address: random.clone().into(), - amount: coins(7, "eth"), - }, - 123, - ); - let msgs = reflect::Message { - messages: vec![msg], - }; - let res = app - .execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[]) - .unwrap(); - - // expected events: execute, transfer, reply, custom wasm (set in reply) - assert_eq!(4, res.events.len(), "{:?}", res.events); - res.assert_event(&Event::new("execute").add_attribute("_contract_addr", &reflect_addr)); - res.assert_event(&Event::new("transfer").add_attribute("amount", "7eth")); - res.assert_event( - &Event::new("reply") - .add_attribute("_contract_addr", reflect_addr.as_str()) - .add_attribute("mode", "handle_success"), - ); - res.assert_event(&Event::new("wasm-custom").add_attribute("from", "reply")); - - // ensure success was written - let res: Reply = app.wrap().query_wasm_smart(&reflect_addr, &query).unwrap(); - assert_eq!(res.id, 123); - // validate the events written in the reply blob...should just be bank transfer - let reply = res.result.unwrap(); - assert_eq!(1, reply.events.len()); - AppResponse::from(reply) - .assert_event(&Event::new("transfer").add_attribute("amount", "7eth")); - - // reflect sends 300 btc, failure, but error caught by submessage (so shows success) - let msg = SubMsg::reply_always( - BankMsg::Send { - to_address: random.clone().into(), - amount: coins(300, "btc"), - }, - 456, - ); - let msgs = reflect::Message { - messages: vec![msg], - }; - let _res = app - .execute_contract(random, reflect_addr.clone(), &msgs, &[]) - .unwrap(); - - // ensure error was written - let query = reflect::QueryMsg::Reply { id: 456 }; - let res: Reply = app.wrap().query_wasm_smart(&reflect_addr, &query).unwrap(); - assert_eq!(res.id, 456); - assert!(res.result.is_err()); - // TODO: check error? - } - - fn query_router( - router: &Router, - api: &dyn Api, - storage: &dyn Storage, - rcpt: &Addr, - ) -> Vec - where - CustomT::ExecT: Clone + fmt::Debug + PartialEq + JsonSchema, - CustomT::QueryT: CustomQuery + DeserializeOwned, - WasmT: Wasm, - BankT: Bank, - CustomT: Module, - StakingT: Staking, - DistrT: Distribution, - { - let query = BankQuery::AllBalances { - address: rcpt.into(), - }; - let block = mock_env().block; - let querier: MockQuerier = MockQuerier::new(&[]); - let res = router - .bank - .query(api, storage, &querier, &block, query) - .unwrap(); - let val: AllBalanceResponse = from_slice(&res).unwrap(); - val.amount - } - - fn query_app( - app: &App, - rcpt: &Addr, - ) -> Vec - where - CustomT::ExecT: - std::fmt::Debug + PartialEq + Clone + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryT: CustomQuery + DeserializeOwned + 'static, - WasmT: Wasm, - BankT: Bank, - ApiT: Api, - StorageT: Storage, - CustomT: Module, - StakingT: Staking, - DistrT: Distribution, - { - let query = BankQuery::AllBalances { - address: rcpt.into(), - } - .into(); - let val: AllBalanceResponse = app.wrap().query(&query).unwrap(); - val.amount - } - - #[test] - fn multi_level_bank_cache() { - // set personal balance - let owner = Addr::unchecked("owner"); - let rcpt = Addr::unchecked("recipient"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // cache 1 - send some tokens - let mut cache = StorageTransaction::new(&app.storage); - let msg = BankMsg::Send { - to_address: rcpt.clone().into(), - amount: coins(25, "eth"), - }; - app.router - .execute(&app.api, &mut cache, &app.block, owner.clone(), msg.into()) - .unwrap(); - - // shows up in cache - let cached_rcpt = query_router(&app.router, &app.api, &cache, &rcpt); - assert_eq!(coins(25, "eth"), cached_rcpt); - let router_rcpt = query_app(&app, &rcpt); - assert_eq!(router_rcpt, vec![]); - - // now, second level cache - transactional(&mut cache, |cache2, read| { - let msg = BankMsg::Send { - to_address: rcpt.clone().into(), - amount: coins(12, "eth"), - }; - app.router - .execute(&app.api, cache2, &app.block, owner, msg.into()) - .unwrap(); - - // shows up in 2nd cache - let cached_rcpt = query_router(&app.router, &app.api, read, &rcpt); - assert_eq!(coins(25, "eth"), cached_rcpt); - let cached2_rcpt = query_router(&app.router, &app.api, cache2, &rcpt); - assert_eq!(coins(37, "eth"), cached2_rcpt); - Ok(()) - }) - .unwrap(); - - // apply first to router - cache.prepare().commit(&mut app.storage); - - let committed = query_app(&app, &rcpt); - assert_eq!(coins(37, "eth"), committed); - } - - #[test] - fn sent_funds_properly_visible_on_execution() { - // Testing if funds on contract are properly visible on contract. - // Hackatom contract is initialized with 10btc. Then, the contract is executed, with - // additional 20btc. Then beneficiary balance is checked - expeced value is 30btc. 10btc - // would mean that sending tokens with message is not visible for this very message, and - // 20btc means, that only such just send funds are visible. - let owner = Addr::unchecked("owner"); - let beneficiary = Addr::unchecked("beneficiary"); - let init_funds = coins(30, "btc"); - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - let contract_id = app.store_code(hackatom::contract()); - let contract = app - .instantiate_contract( - contract_id, - owner.clone(), - &hackatom::InstantiateMsg { - beneficiary: beneficiary.as_str().to_owned(), - }, - &coins(10, "btc"), - "Hackatom", - None, - ) - .unwrap(); - - app.execute_contract( - owner.clone(), - contract.clone(), - &EmptyMsg {}, - &coins(20, "btc"), - ) - .unwrap(); - - // Check balance of all accounts to ensure no tokens where burned or created, and they are - // in correct places - assert_eq!(get_balance(&app, &owner), &[]); - assert_eq!(get_balance(&app, &contract), &[]); - assert_eq!(get_balance(&app, &beneficiary), coins(30, "btc")); - } - - #[test] - fn sent_wasm_migration_works() { - // The plan: - // create a hackatom contract with some funds - // check admin set properly - // check beneficiary set properly - // migrate fails if not admin - // migrate succeeds if admin - // check beneficiary updated - let owner = Addr::unchecked("owner"); - let beneficiary = Addr::unchecked("beneficiary"); - let init_funds = coins(30, "btc"); - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // create a hackatom contract with some funds - let contract_id = app.store_code(hackatom::contract()); - let contract = app - .instantiate_contract( - contract_id, - owner.clone(), - &hackatom::InstantiateMsg { - beneficiary: beneficiary.as_str().to_owned(), - }, - &coins(20, "btc"), - "Hackatom", - Some(owner.to_string()), - ) - .unwrap(); - - // check admin set properly - let info = app.contract_data(&contract).unwrap(); - assert_eq!(info.admin, Some(owner.clone())); - // check beneficiary set properly - let state: hackatom::InstantiateMsg = app - .wrap() - .query_wasm_smart(&contract, &hackatom::QueryMsg::Beneficiary {}) - .unwrap(); - assert_eq!(state.beneficiary, beneficiary); - - // migrate fails if not admin - let random = Addr::unchecked("random"); - let migrate_msg = hackatom::MigrateMsg { - new_guy: random.to_string(), - }; - app.migrate_contract(beneficiary, contract.clone(), &migrate_msg, contract_id) - .unwrap_err(); - - // migrate fails if unregistred code id - app.migrate_contract( - owner.clone(), - contract.clone(), - &migrate_msg, - contract_id + 7, - ) - .unwrap_err(); - - // migrate succeeds when the stars align - app.migrate_contract(owner, contract.clone(), &migrate_msg, contract_id) - .unwrap(); - - // check beneficiary updated - let state: hackatom::InstantiateMsg = app - .wrap() - .query_wasm_smart(&contract, &hackatom::QueryMsg::Beneficiary {}) - .unwrap(); - assert_eq!(state.beneficiary, random); - } - - #[test] - fn send_update_admin_works() { - // The plan: - // create a hackatom contract - // check admin set properly - // update admin succeeds if admin - // update admin fails if not (new) admin - // check admin set properly - let owner = Addr::unchecked("owner"); - let owner2 = Addr::unchecked("owner2"); - let beneficiary = Addr::unchecked("beneficiary"); - - let mut app = App::default(); - - // create a hackatom contract with some funds - let contract_id = app.store_code(hackatom::contract()); - let contract = app - .instantiate_contract( - contract_id, - owner.clone(), - &hackatom::InstantiateMsg { - beneficiary: beneficiary.as_str().to_owned(), - }, - &[], - "Hackatom", - Some(owner.to_string()), - ) - .unwrap(); - - // check admin set properly - let info = app.contract_data(&contract).unwrap(); - assert_eq!(info.admin, Some(owner.clone())); - - // transfer adminship to owner2 - app.execute( - owner.clone(), - CosmosMsg::Wasm(WasmMsg::UpdateAdmin { - contract_addr: contract.to_string(), - admin: owner2.to_string(), - }), - ) - .unwrap(); - - // check admin set properly - let info = app.contract_data(&contract).unwrap(); - assert_eq!(info.admin, Some(owner2.clone())); - - // update admin fails if not owner2 - app.execute( - owner.clone(), - CosmosMsg::Wasm(WasmMsg::UpdateAdmin { - contract_addr: contract.to_string(), - admin: owner.to_string(), - }), - ) - .unwrap_err(); - - // check admin still the same - let info = app.contract_data(&contract).unwrap(); - assert_eq!(info.admin, Some(owner2)); - } - - mod reply_data_overwrite { - use super::*; - - use echo::EXECUTE_REPLY_BASE_ID; - - fn make_echo_submsg( - contract: Addr, - data: impl Into>, - sub_msg: Vec, - id: u64, - ) -> SubMsg { - let data = data.into().map(|s| s.to_owned()); - SubMsg::reply_always( - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract.into(), - msg: to_binary(&echo::Message { - data, - sub_msg, - ..echo::Message::default() - }) - .unwrap(), - funds: vec![], - }), - id, - ) - } - - fn make_echo_submsg_no_reply( - contract: Addr, - data: impl Into>, - sub_msg: Vec, - ) -> SubMsg { - let data = data.into().map(|s| s.to_owned()); - SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract.into(), - msg: to_binary(&echo::Message { - data, - sub_msg, - ..echo::Message::default() - }) - .unwrap(), - funds: vec![], - })) - } - - #[test] - fn no_submsg() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract, - &echo::Message:: { - data: Some("Data".to_owned()), - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"Data".into())); - } - - #[test] - fn single_submsg() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - data: Some("First".to_owned()), - sub_msg: vec![make_echo_submsg( - contract, - "Second", - vec![], - EXECUTE_REPLY_BASE_ID, - )], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"Second".into())); - } - - #[test] - fn single_submsg_no_reply() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - data: Some("First".to_owned()), - sub_msg: vec![make_echo_submsg_no_reply(contract, "Second", vec![])], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"First".into())); - } - - #[test] - fn single_no_submsg_data() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - data: Some("First".to_owned()), - sub_msg: vec![make_echo_submsg(contract, None, vec![], 1)], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"First".into())); - } - - #[test] - fn single_no_top_level_data() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - sub_msg: vec![make_echo_submsg( - contract, - "Second", - vec![], - EXECUTE_REPLY_BASE_ID, - )], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"Second".into())); - } - - #[test] - fn single_submsg_reply_returns_none() { - // set personal balance - let owner = Addr::unchecked("owner"); - let init_funds = coins(100, "tgd"); - - let mut app = custom_app::(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // set up reflect contract - let reflect_id = app.store_code(reflect::contract()); - let reflect_addr = app - .instantiate_contract( - reflect_id, - owner.clone(), - &EmptyMsg {}, - &[], - "Reflect", - None, - ) - .unwrap(); - - // set up echo contract - let echo_id = app.store_code(echo::custom_contract()); - let echo_addr = app - .instantiate_contract(echo_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - // reflect will call echo - // echo will set the data - // top-level app will not display the data - let echo_msg = echo::Message:: { - data: Some("my echo".into()), - events: vec![Event::new("echo").add_attribute("called", "true")], - ..echo::Message::default() - }; - let reflect_msg = reflect::Message { - messages: vec![SubMsg::new(WasmMsg::Execute { - contract_addr: echo_addr.to_string(), - msg: to_binary(&echo_msg).unwrap(), - funds: vec![], - })], - }; - - let res = app - .execute_contract(owner, reflect_addr.clone(), &reflect_msg, &[]) - .unwrap(); - - // ensure data is empty - assert_eq!(res.data, None); - // ensure expected events - assert_eq!(res.events.len(), 3, "{:?}", res.events); - res.assert_event(&Event::new("execute").add_attribute("_contract_addr", &reflect_addr)); - res.assert_event(&Event::new("execute").add_attribute("_contract_addr", &echo_addr)); - res.assert_event(&Event::new("wasm-echo")); - } - - #[test] - fn multiple_submsg() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - data: Some("Orig".to_owned()), - sub_msg: vec![ - make_echo_submsg( - contract.clone(), - None, - vec![], - EXECUTE_REPLY_BASE_ID + 1, - ), - make_echo_submsg( - contract.clone(), - "First", - vec![], - EXECUTE_REPLY_BASE_ID + 2, - ), - make_echo_submsg( - contract.clone(), - "Second", - vec![], - EXECUTE_REPLY_BASE_ID + 3, - ), - make_echo_submsg(contract, None, vec![], EXECUTE_REPLY_BASE_ID + 4), - ], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"Second".into())); - } - - #[test] - fn multiple_submsg_no_reply() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - data: Some("Orig".to_owned()), - sub_msg: vec![ - make_echo_submsg_no_reply(contract.clone(), None, vec![]), - make_echo_submsg_no_reply(contract.clone(), "First", vec![]), - make_echo_submsg_no_reply(contract.clone(), "Second", vec![]), - make_echo_submsg_no_reply(contract, None, vec![]), - ], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"Orig".into())); - } - - #[test] - fn multiple_submsg_mixed() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - sub_msg: vec![ - make_echo_submsg( - contract.clone(), - None, - vec![], - EXECUTE_REPLY_BASE_ID + 1, - ), - make_echo_submsg_no_reply(contract.clone(), "Hidden", vec![]), - make_echo_submsg( - contract.clone(), - "Shown", - vec![], - EXECUTE_REPLY_BASE_ID + 2, - ), - make_echo_submsg( - contract.clone(), - None, - vec![], - EXECUTE_REPLY_BASE_ID + 3, - ), - make_echo_submsg_no_reply(contract, "Lost", vec![]), - ], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"Shown".into())); - } - - #[test] - fn nested_submsg() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let response = app - .execute_contract( - owner, - contract.clone(), - &echo::Message { - data: Some("Orig".to_owned()), - sub_msg: vec![make_echo_submsg( - contract.clone(), - None, - vec![make_echo_submsg( - contract.clone(), - "First", - vec![make_echo_submsg( - contract.clone(), - "Second", - vec![make_echo_submsg( - contract, - None, - vec![], - EXECUTE_REPLY_BASE_ID + 4, - )], - EXECUTE_REPLY_BASE_ID + 3, - )], - EXECUTE_REPLY_BASE_ID + 2, - )], - EXECUTE_REPLY_BASE_ID + 1, - )], - ..echo::Message::default() - }, - &[], - ) - .unwrap(); - - assert_eq!(response.data, Some(b"Second".into())); - } - } - - mod response_validation { - use super::*; - - #[test] - fn empty_attribute_key() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let err = app - .execute_contract( - owner, - contract, - &echo::Message:: { - data: None, - attributes: vec![ - Attribute::new(" ", "value"), - Attribute::new("proper", "proper_val"), - ], - ..echo::Message::default() - }, - &[], - ) - .unwrap_err(); - - assert_eq!(Error::empty_attribute_key("value"), err.downcast().unwrap(),); - } - - #[test] - fn empty_attribute_value() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let err = app - .execute_contract( - owner, - contract, - &echo::Message:: { - data: None, - attributes: vec![ - Attribute::new("key", " "), - Attribute::new("proper", "proper_val"), - ], - ..echo::Message::default() - }, - &[], - ) - .unwrap_err(); - - assert_eq!(Error::empty_attribute_value("key"), err.downcast().unwrap()); - } - - #[test] - fn empty_event_attribute_key() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let err = app - .execute_contract( - owner, - contract, - &echo::Message:: { - data: None, - events: vec![Event::new("event") - .add_attribute(" ", "value") - .add_attribute("proper", "proper_val")], - ..echo::Message::default() - }, - &[], - ) - .unwrap_err(); - - assert_eq!(Error::empty_attribute_key("value"), err.downcast().unwrap()); - } - - #[test] - fn empty_event_attribute_value() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let err = app - .execute_contract( - owner, - contract, - &echo::Message:: { - data: None, - events: vec![Event::new("event") - .add_attribute("key", " ") - .add_attribute("proper", "proper_val")], - ..echo::Message::default() - }, - &[], - ) - .unwrap_err(); - - assert_eq!(Error::empty_attribute_value("key"), err.downcast().unwrap()); - } - - #[test] - fn too_short_event_type() { - let mut app = App::default(); - - let owner = Addr::unchecked("owner"); - - let contract_id = app.store_code(echo::contract()); - let contract = app - .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - let err = app - .execute_contract( - owner, - contract, - &echo::Message:: { - data: None, - events: vec![Event::new(" e "), Event::new("event")], - ..echo::Message::default() - }, - &[], - ) - .unwrap_err(); - - assert_eq!(Error::event_type_too_short("e"), err.downcast().unwrap()); - } - } - - mod custom_messages { - use super::*; - use crate::custom_handler::CachingCustomHandler; - - #[test] - fn triggering_custom_msg() { - let api = MockApi::default(); - let sender = api.addr_validate("sender").unwrap(); - let owner = api.addr_validate("owner").unwrap(); - - let custom_handler = CachingCustomHandler::::new(); - let custom_handler_state = custom_handler.state(); - - let mut app = AppBuilder::new_custom() - .with_api(api) - .with_custom(custom_handler) - .build(no_init); - - let contract_id = app.store_code(echo::custom_contract()); - let contract = app - .instantiate_contract(contract_id, owner, &EmptyMsg {}, &[], "Echo", None) - .unwrap(); - - app.execute_contract( - sender, - contract, - &echo::Message { - sub_msg: vec![SubMsg::new(CosmosMsg::Custom(CustomMsg::SetAge { - age: 20, - }))], - ..Default::default() - }, - &[], - ) - .unwrap(); - - assert_eq!( - custom_handler_state.execs().to_owned(), - vec![CustomMsg::SetAge { age: 20 }] - ); - - assert!(custom_handler_state.queries().is_empty()); - } - } - - mod protobuf_wrapped_data { - use super::*; - use crate::test_helpers::contracts::echo::EXECUTE_REPLY_BASE_ID; - use cw_utils::parse_instantiate_response_data; - - #[test] - fn instantiate_wrapped_properly() { - // set personal balance - let owner = Addr::unchecked("owner"); - let init_funds = vec![coin(20, "btc")]; - - let mut app = custom_app::(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_funds) - .unwrap(); - }); - - // set up reflect contract - let code_id = app.store_code(reflect::contract()); - let init_msg = to_binary(&EmptyMsg {}).unwrap(); - let msg = WasmMsg::Instantiate { - admin: None, - code_id, - msg: init_msg, - funds: vec![], - label: "label".into(), - }; - let res = app.execute(owner, msg.into()).unwrap(); - - // assert we have a proper instantiate result - let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap(); - assert!(parsed.data.is_none()); - // check the address is right - - let count: payout::CountResponse = app - .wrap() - .query_wasm_smart(&parsed.contract_address, &reflect::QueryMsg::Count {}) - .unwrap(); - assert_eq!(count.count, 0); - } - - #[test] - fn instantiate_with_data_works() { - let owner = Addr::unchecked("owner"); - let mut app = BasicApp::new(|_, _, _| {}); - - // set up echo contract - let code_id = app.store_code(echo::contract()); - let msg = echo::InitMessage:: { - data: Some("food".into()), - sub_msg: None, - }; - let init_msg = to_binary(&msg).unwrap(); - let msg = WasmMsg::Instantiate { - admin: None, - code_id, - msg: init_msg, - funds: vec![], - label: "label".into(), - }; - let res = app.execute(owner, msg.into()).unwrap(); - - // assert we have a proper instantiate result - let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap(); - assert!(parsed.data.is_some()); - assert_eq!(parsed.data.unwrap(), Binary::from(b"food")); - assert!(!parsed.contract_address.is_empty()); - } - - #[test] - fn instantiate_with_reply_works() { - let owner = Addr::unchecked("owner"); - let mut app = BasicApp::new(|_, _, _| {}); - - // set up echo contract - let code_id = app.store_code(echo::contract()); - let msg = echo::InitMessage:: { - data: Some("food".into()), - ..Default::default() - }; - let addr1 = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "first", None) - .unwrap(); - - // another echo contract - let msg = echo::Message:: { - data: Some("Passed to contract instantiation, returned as reply, and then returned as response".into()), - ..Default::default() - }; - let sub_msg = SubMsg::reply_on_success( - WasmMsg::Execute { - contract_addr: addr1.to_string(), - msg: to_binary(&msg).unwrap(), - funds: vec![], - }, - EXECUTE_REPLY_BASE_ID, - ); - let init_msg = echo::InitMessage:: { - data: Some("Overwrite me".into()), - sub_msg: Some(vec![sub_msg]), - }; - let init_msg = to_binary(&init_msg).unwrap(); - let msg = WasmMsg::Instantiate { - admin: None, - code_id, - msg: init_msg, - funds: vec![], - label: "label".into(), - }; - let res = app.execute(owner, msg.into()).unwrap(); - - // assert we have a proper instantiate result - let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap(); - assert!(parsed.data.is_some()); - // Result is from the reply, not the original one - assert_eq!(parsed.data.unwrap(), Binary::from(b"Passed to contract instantiation, returned as reply, and then returned as response")); - assert!(!parsed.contract_address.is_empty()); - assert_ne!(parsed.contract_address, addr1.to_string()); - } - - #[test] - fn execute_wrapped_properly() { - let owner = Addr::unchecked("owner"); - let mut app = BasicApp::new(|_, _, _| {}); - - // set up reflect contract - let code_id = app.store_code(echo::contract()); - let echo_addr = app - .instantiate_contract(code_id, owner.clone(), &EmptyMsg {}, &[], "label", None) - .unwrap(); - - // ensure the execute has the same wrapper as it should - let msg = echo::Message:: { - data: Some("hello".into()), - ..echo::Message::default() - }; - // execute_contract now decodes a protobuf wrapper, so we get the top-level response - let exec_res = app.execute_contract(owner, echo_addr, &msg, &[]).unwrap(); - assert_eq!(exec_res.data, Some(Binary::from(b"hello"))); - } - } - - mod errors { - use super::*; - - #[test] - fn simple_instantiation() { - let owner = Addr::unchecked("owner"); - let mut app = App::default(); - - // set up contract - let code_id = app.store_code(error::contract(false)); - let msg = EmptyMsg {}; - let err = app - .instantiate_contract(code_id, owner, &msg, &[], "error", None) - .unwrap_err(); - - // we should be able to retrieve the original error by downcasting - let source: &StdError = err.downcast_ref().unwrap(); - if let StdError::GenericErr { msg } = source { - assert_eq!(msg, "Init failed"); - } else { - panic!("wrong StdError variant"); - } - - // We're expecting exactly 2 nested error types - // (the original error, WasmMsg context) - assert_eq!(err.chain().count(), 2); - } - - #[test] - fn simple_call() { - let owner = Addr::unchecked("owner"); - let mut app = App::default(); - - // set up contract - let code_id = app.store_code(error::contract(true)); - let msg = EmptyMsg {}; - let contract_addr = app - .instantiate_contract(code_id, owner, &msg, &[], "error", None) - .unwrap(); - - // execute should error - let err = app - .execute_contract(Addr::unchecked("random"), contract_addr, &msg, &[]) - .unwrap_err(); - - // we should be able to retrieve the original error by downcasting - let source: &StdError = err.downcast_ref().unwrap(); - if let StdError::GenericErr { msg } = source { - assert_eq!(msg, "Handle failed"); - } else { - panic!("wrong StdError variant"); - } - - // We're expecting exactly 2 nested error types - // (the original error, WasmMsg context) - assert_eq!(err.chain().count(), 2); - } - - #[test] - fn nested_call() { - let owner = Addr::unchecked("owner"); - let mut app = App::default(); - - let error_code_id = app.store_code(error::contract(true)); - let caller_code_id = app.store_code(caller::contract()); - - // set up contracts - let msg = EmptyMsg {}; - let caller_addr = app - .instantiate_contract(caller_code_id, owner.clone(), &msg, &[], "caller", None) - .unwrap(); - let error_addr = app - .instantiate_contract(error_code_id, owner, &msg, &[], "error", None) - .unwrap(); - - // execute should error - let msg = WasmMsg::Execute { - contract_addr: error_addr.into(), - msg: to_binary(&EmptyMsg {}).unwrap(), - funds: vec![], - }; - let err = app - .execute_contract(Addr::unchecked("random"), caller_addr, &msg, &[]) - .unwrap_err(); - - // we can downcast to get the original error - let source: &StdError = err.downcast_ref().unwrap(); - if let StdError::GenericErr { msg } = source { - assert_eq!(msg, "Handle failed"); - } else { - panic!("wrong StdError variant"); - } - - // We're expecting exactly 3 nested error types - // (the original error, 2 WasmMsg contexts) - assert_eq!(err.chain().count(), 3); - } - - #[test] - fn double_nested_call() { - let owner = Addr::unchecked("owner"); - let mut app = App::default(); - - let error_code_id = app.store_code(error::contract(true)); - let caller_code_id = app.store_code(caller::contract()); - - // set up contracts - let msg = EmptyMsg {}; - let caller_addr1 = app - .instantiate_contract(caller_code_id, owner.clone(), &msg, &[], "caller", None) - .unwrap(); - let caller_addr2 = app - .instantiate_contract(caller_code_id, owner.clone(), &msg, &[], "caller", None) - .unwrap(); - let error_addr = app - .instantiate_contract(error_code_id, owner, &msg, &[], "error", None) - .unwrap(); - - // caller1 calls caller2, caller2 calls error - let msg = WasmMsg::Execute { - contract_addr: caller_addr2.into(), - msg: to_binary(&WasmMsg::Execute { - contract_addr: error_addr.into(), - msg: to_binary(&EmptyMsg {}).unwrap(), - funds: vec![], - }) - .unwrap(), - funds: vec![], - }; - let err = app - .execute_contract(Addr::unchecked("random"), caller_addr1, &msg, &[]) - .unwrap_err(); - - // uncomment to have the test fail and see how the error stringifies - // panic!("{:?}", err); - - // we can downcast to get the original error - let source: &StdError = err.downcast_ref().unwrap(); - if let StdError::GenericErr { msg } = source { - assert_eq!(msg, "Handle failed"); - } else { - panic!("wrong StdError variant"); - } - - // We're expecting exactly 4 nested error types - // (the original error, 3 WasmMsg contexts) - assert_eq!(err.chain().count(), 4); - } - } -} diff --git a/packages/multi-test/src/bank.rs b/packages/multi-test/src/bank.rs deleted file mode 100644 index 15cc66172..000000000 --- a/packages/multi-test/src/bank.rs +++ /dev/null @@ -1,473 +0,0 @@ -use anyhow::{bail, Result as AnyResult}; -use itertools::Itertools; -use schemars::JsonSchema; - -use cosmwasm_std::{ - coin, to_binary, Addr, AllBalanceResponse, Api, BalanceResponse, BankMsg, BankQuery, Binary, - BlockInfo, Coin, Event, Querier, Storage, -}; -use cw_storage_plus::Map; -use cw_utils::NativeBalance; - -use crate::app::CosmosRouter; -use crate::executor::AppResponse; -use crate::module::Module; -use crate::prefixed_storage::{prefixed, prefixed_read}; - -const BALANCES: Map<&Addr, NativeBalance> = Map::new("balances"); - -pub const NAMESPACE_BANK: &[u8] = b"bank"; - -// WIP -#[derive(Clone, std::fmt::Debug, PartialEq, Eq, JsonSchema)] -pub enum BankSudo { - Mint { - to_address: String, - amount: Vec, - }, -} - -pub trait Bank: Module {} - -#[derive(Default)] -pub struct BankKeeper {} - -impl BankKeeper { - pub fn new() -> Self { - BankKeeper {} - } - - // this is an "admin" function to let us adjust bank accounts in genesis - pub fn init_balance( - &self, - storage: &mut dyn Storage, - account: &Addr, - amount: Vec, - ) -> AnyResult<()> { - let mut bank_storage = prefixed(storage, NAMESPACE_BANK); - self.set_balance(&mut bank_storage, account, amount) - } - - fn set_balance( - &self, - bank_storage: &mut dyn Storage, - account: &Addr, - amount: Vec, - ) -> AnyResult<()> { - let mut balance = NativeBalance(amount); - balance.normalize(); - BALANCES - .save(bank_storage, account, &balance) - .map_err(Into::into) - } - - // this is an "admin" function to let us adjust bank accounts - fn get_balance(&self, bank_storage: &dyn Storage, account: &Addr) -> AnyResult> { - let val = BALANCES.may_load(bank_storage, account)?; - Ok(val.unwrap_or_default().into_vec()) - } - - fn send( - &self, - bank_storage: &mut dyn Storage, - from_address: Addr, - to_address: Addr, - amount: Vec, - ) -> AnyResult<()> { - self.burn(bank_storage, from_address, amount.clone())?; - self.mint(bank_storage, to_address, amount) - } - - fn mint( - &self, - bank_storage: &mut dyn Storage, - to_address: Addr, - amount: Vec, - ) -> AnyResult<()> { - let amount = self.normalize_amount(amount)?; - let b = self.get_balance(bank_storage, &to_address)?; - let b = NativeBalance(b) + NativeBalance(amount); - self.set_balance(bank_storage, &to_address, b.into_vec()) - } - - fn burn( - &self, - bank_storage: &mut dyn Storage, - from_address: Addr, - amount: Vec, - ) -> AnyResult<()> { - let amount = self.normalize_amount(amount)?; - let a = self.get_balance(bank_storage, &from_address)?; - let a = (NativeBalance(a) - amount)?; - self.set_balance(bank_storage, &from_address, a.into_vec()) - } - - /// Filters out all 0 value coins and returns an error if the resulting Vec is empty - fn normalize_amount(&self, amount: Vec) -> AnyResult> { - let res: Vec<_> = amount.into_iter().filter(|x| !x.amount.is_zero()).collect(); - if res.is_empty() { - bail!("Cannot transfer empty coins amount") - } else { - Ok(res) - } - } -} - -fn coins_to_string(coins: &[Coin]) -> String { - coins - .iter() - .map(|c| format!("{}{}", c.amount, c.denom)) - .join(",") -} - -impl Bank for BankKeeper {} - -impl Module for BankKeeper { - type ExecT = BankMsg; - type QueryT = BankQuery; - type SudoT = BankSudo; - - fn execute( - &self, - _api: &dyn Api, - storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - sender: Addr, - msg: BankMsg, - ) -> AnyResult { - let mut bank_storage = prefixed(storage, NAMESPACE_BANK); - match msg { - BankMsg::Send { to_address, amount } => { - // see https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/bank/keeper/send.go#L142-L147 - let events = vec![Event::new("transfer") - .add_attribute("recipient", &to_address) - .add_attribute("sender", &sender) - .add_attribute("amount", coins_to_string(&amount))]; - self.send( - &mut bank_storage, - sender, - Addr::unchecked(to_address), - amount, - )?; - Ok(AppResponse { events, data: None }) - } - BankMsg::Burn { amount } => { - // burn doesn't seem to emit any events - self.burn(&mut bank_storage, sender, amount)?; - Ok(AppResponse::default()) - } - m => bail!("Unsupported bank message: {:?}", m), - } - } - - fn sudo( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - msg: BankSudo, - ) -> AnyResult { - let mut bank_storage = prefixed(storage, NAMESPACE_BANK); - match msg { - BankSudo::Mint { to_address, amount } => { - let to_address = api.addr_validate(&to_address)?; - self.mint(&mut bank_storage, to_address, amount)?; - Ok(AppResponse::default()) - } - } - } - - fn query( - &self, - api: &dyn Api, - storage: &dyn Storage, - _querier: &dyn Querier, - _block: &BlockInfo, - request: BankQuery, - ) -> AnyResult { - let bank_storage = prefixed_read(storage, NAMESPACE_BANK); - match request { - BankQuery::AllBalances { address } => { - let address = api.addr_validate(&address)?; - let amount = self.get_balance(&bank_storage, &address)?; - let res = AllBalanceResponse { amount }; - Ok(to_binary(&res)?) - } - BankQuery::Balance { address, denom } => { - let address = api.addr_validate(&address)?; - let all_amounts = self.get_balance(&bank_storage, &address)?; - let amount = all_amounts - .into_iter() - .find(|c| c.denom == denom) - .unwrap_or_else(|| coin(0, denom)); - let res = BalanceResponse { amount }; - Ok(to_binary(&res)?) - } - q => bail!("Unsupported bank query: {:?}", q), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - use crate::app::MockRouter; - use cosmwasm_std::testing::{mock_env, MockApi, MockQuerier, MockStorage}; - use cosmwasm_std::{coins, from_slice, Empty, StdError}; - - fn query_balance( - bank: &BankKeeper, - api: &dyn Api, - store: &dyn Storage, - rcpt: &Addr, - ) -> Vec { - let req = BankQuery::AllBalances { - address: rcpt.clone().into(), - }; - let block = mock_env().block; - let querier: MockQuerier = MockQuerier::new(&[]); - - let raw = bank.query(api, store, &querier, &block, req).unwrap(); - let res: AllBalanceResponse = from_slice(&raw).unwrap(); - res.amount - } - - #[test] - fn get_set_balance() { - let api = MockApi::default(); - let mut store = MockStorage::new(); - let block = mock_env().block; - let querier: MockQuerier = MockQuerier::new(&[]); - - let owner = Addr::unchecked("owner"); - let rcpt = Addr::unchecked("receiver"); - let init_funds = vec![coin(100, "eth"), coin(20, "btc")]; - let norm = vec![coin(20, "btc"), coin(100, "eth")]; - - // set money - let bank = BankKeeper::new(); - bank.init_balance(&mut store, &owner, init_funds).unwrap(); - let bank_storage = prefixed_read(&store, NAMESPACE_BANK); - - // get balance work - let rich = bank.get_balance(&bank_storage, &owner).unwrap(); - assert_eq!(rich, norm); - let poor = bank.get_balance(&bank_storage, &rcpt).unwrap(); - assert_eq!(poor, vec![]); - - // proper queries work - let req = BankQuery::AllBalances { - address: owner.clone().into(), - }; - let raw = bank.query(&api, &store, &querier, &block, req).unwrap(); - let res: AllBalanceResponse = from_slice(&raw).unwrap(); - assert_eq!(res.amount, norm); - - let req = BankQuery::AllBalances { - address: rcpt.clone().into(), - }; - let raw = bank.query(&api, &store, &querier, &block, req).unwrap(); - let res: AllBalanceResponse = from_slice(&raw).unwrap(); - assert_eq!(res.amount, vec![]); - - let req = BankQuery::Balance { - address: owner.clone().into(), - denom: "eth".into(), - }; - let raw = bank.query(&api, &store, &querier, &block, req).unwrap(); - let res: BalanceResponse = from_slice(&raw).unwrap(); - assert_eq!(res.amount, coin(100, "eth")); - - let req = BankQuery::Balance { - address: owner.into(), - denom: "foobar".into(), - }; - let raw = bank.query(&api, &store, &querier, &block, req).unwrap(); - let res: BalanceResponse = from_slice(&raw).unwrap(); - assert_eq!(res.amount, coin(0, "foobar")); - - let req = BankQuery::Balance { - address: rcpt.into(), - denom: "eth".into(), - }; - let raw = bank.query(&api, &store, &querier, &block, req).unwrap(); - let res: BalanceResponse = from_slice(&raw).unwrap(); - assert_eq!(res.amount, coin(0, "eth")); - } - - #[test] - fn send_coins() { - let api = MockApi::default(); - let mut store = MockStorage::new(); - let block = mock_env().block; - let router = MockRouter::default(); - - let owner = Addr::unchecked("owner"); - let rcpt = Addr::unchecked("receiver"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - let rcpt_funds = vec![coin(5, "btc")]; - - // set money - let bank = BankKeeper::new(); - bank.init_balance(&mut store, &owner, init_funds).unwrap(); - bank.init_balance(&mut store, &rcpt, rcpt_funds).unwrap(); - - // send both tokens - let to_send = vec![coin(30, "eth"), coin(5, "btc")]; - let msg = BankMsg::Send { - to_address: rcpt.clone().into(), - amount: to_send, - }; - bank.execute( - &api, - &mut store, - &router, - &block, - owner.clone(), - msg.clone(), - ) - .unwrap(); - let rich = query_balance(&bank, &api, &store, &owner); - assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich); - let poor = query_balance(&bank, &api, &store, &rcpt); - assert_eq!(vec![coin(10, "btc"), coin(30, "eth")], poor); - - // can send from any account with funds - bank.execute(&api, &mut store, &router, &block, rcpt.clone(), msg) - .unwrap(); - - // cannot send too much - let msg = BankMsg::Send { - to_address: rcpt.into(), - amount: coins(20, "btc"), - }; - bank.execute(&api, &mut store, &router, &block, owner.clone(), msg) - .unwrap_err(); - - let rich = query_balance(&bank, &api, &store, &owner); - assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich); - } - - #[test] - fn burn_coins() { - let api = MockApi::default(); - let mut store = MockStorage::new(); - let block = mock_env().block; - let router = MockRouter::default(); - - let owner = Addr::unchecked("owner"); - let rcpt = Addr::unchecked("recipient"); - let init_funds = vec![coin(20, "btc"), coin(100, "eth")]; - - // set money - let bank = BankKeeper::new(); - bank.init_balance(&mut store, &owner, init_funds).unwrap(); - - // burn both tokens - let to_burn = vec![coin(30, "eth"), coin(5, "btc")]; - let msg = BankMsg::Burn { amount: to_burn }; - bank.execute(&api, &mut store, &router, &block, owner.clone(), msg) - .unwrap(); - let rich = query_balance(&bank, &api, &store, &owner); - assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich); - - // cannot burn too much - let msg = BankMsg::Burn { - amount: coins(20, "btc"), - }; - let err = bank - .execute(&api, &mut store, &router, &block, owner.clone(), msg) - .unwrap_err(); - assert!(matches!(err.downcast().unwrap(), StdError::Overflow { .. })); - - let rich = query_balance(&bank, &api, &store, &owner); - assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich); - - // cannot burn from empty account - let msg = BankMsg::Burn { - amount: coins(1, "btc"), - }; - let err = bank - .execute(&api, &mut store, &router, &block, rcpt, msg) - .unwrap_err(); - assert!(matches!(err.downcast().unwrap(), StdError::Overflow { .. })); - } - - #[test] - fn fail_on_zero_values() { - let api = MockApi::default(); - let mut store = MockStorage::new(); - let block = mock_env().block; - let router = MockRouter::default(); - - let owner = Addr::unchecked("owner"); - let rcpt = Addr::unchecked("recipient"); - let init_funds = vec![coin(5000, "atom"), coin(100, "eth")]; - - // set money - let bank = BankKeeper::new(); - bank.init_balance(&mut store, &owner, init_funds).unwrap(); - - // can send normal amounts - let msg = BankMsg::Send { - to_address: rcpt.to_string(), - amount: coins(100, "atom"), - }; - bank.execute(&api, &mut store, &router, &block, owner.clone(), msg) - .unwrap(); - - // fails send on no coins - let msg = BankMsg::Send { - to_address: rcpt.to_string(), - amount: vec![], - }; - bank.execute(&api, &mut store, &router, &block, owner.clone(), msg) - .unwrap_err(); - - // fails send on 0 coins - let msg = BankMsg::Send { - to_address: rcpt.to_string(), - amount: coins(0, "atom"), - }; - bank.execute(&api, &mut store, &router, &block, owner.clone(), msg) - .unwrap_err(); - - // fails burn on no coins - let msg = BankMsg::Burn { amount: vec![] }; - bank.execute(&api, &mut store, &router, &block, owner.clone(), msg) - .unwrap_err(); - - // fails burn on 0 coins - let msg = BankMsg::Burn { - amount: coins(0, "atom"), - }; - bank.execute(&api, &mut store, &router, &block, owner, msg) - .unwrap_err(); - - // can mint via sudo - let msg = BankSudo::Mint { - to_address: rcpt.to_string(), - amount: coins(4321, "atom"), - }; - bank.sudo(&api, &mut store, &router, &block, msg).unwrap(); - - // mint fails with 0 tokens - let msg = BankSudo::Mint { - to_address: rcpt.to_string(), - amount: coins(0, "atom"), - }; - bank.sudo(&api, &mut store, &router, &block, msg) - .unwrap_err(); - - // mint fails with no tokens - let msg = BankSudo::Mint { - to_address: rcpt.to_string(), - amount: vec![], - }; - bank.sudo(&api, &mut store, &router, &block, msg) - .unwrap_err(); - } -} diff --git a/packages/multi-test/src/contracts.rs b/packages/multi-test/src/contracts.rs deleted file mode 100644 index b38cdc114..000000000 --- a/packages/multi-test/src/contracts.rs +++ /dev/null @@ -1,437 +0,0 @@ -use schemars::JsonSchema; -use serde::de::DeserializeOwned; -use std::error::Error; -use std::fmt::{self, Debug, Display}; -use std::ops::Deref; - -use cosmwasm_std::{ - from_slice, Binary, CosmosMsg, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, - QuerierWrapper, Reply, Response, SubMsg, -}; - -use anyhow::{anyhow, bail, Result as AnyResult}; - -/// Interface to call into a Contract -pub trait Contract -where - T: Clone + fmt::Debug + PartialEq + JsonSchema, - Q: CustomQuery, -{ - fn execute( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Vec, - ) -> AnyResult>; - - fn instantiate( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Vec, - ) -> AnyResult>; - - fn query(&self, deps: Deps, env: Env, msg: Vec) -> AnyResult; - - fn sudo(&self, deps: DepsMut, env: Env, msg: Vec) -> AnyResult>; - - fn reply(&self, deps: DepsMut, env: Env, msg: Reply) -> AnyResult>; - - fn migrate(&self, deps: DepsMut, env: Env, msg: Vec) -> AnyResult>; -} - -type ContractFn = - fn(deps: DepsMut, env: Env, info: MessageInfo, msg: T) -> Result, E>; -type PermissionedFn = fn(deps: DepsMut, env: Env, msg: T) -> Result, E>; -type ReplyFn = fn(deps: DepsMut, env: Env, msg: Reply) -> Result, E>; -type QueryFn = fn(deps: Deps, env: Env, msg: T) -> Result; - -type ContractClosure = - Box, Env, MessageInfo, T) -> Result, E>>; -type PermissionedClosure = Box, Env, T) -> Result, E>>; -type ReplyClosure = Box, Env, Reply) -> Result, E>>; -type QueryClosure = Box, Env, T) -> Result>; - -/// Wraps the exported functions from a contract and provides the normalized format -/// Place T4 and E4 at the end, as we just want default placeholders for most contracts that don't have sudo -pub struct ContractWrapper< - T1, - T2, - T3, - E1, - E2, - E3, - C = Empty, - Q = Empty, - T4 = Empty, - E4 = anyhow::Error, - E5 = anyhow::Error, - T6 = Empty, - E6 = anyhow::Error, -> where - T1: DeserializeOwned + Debug, - T2: DeserializeOwned, - T3: DeserializeOwned, - T4: DeserializeOwned, - T6: DeserializeOwned, - E1: Display + Debug + Send + Sync + 'static, - E2: Display + Debug + Send + Sync + 'static, - E3: Display + Debug + Send + Sync + 'static, - E4: Display + Debug + Send + Sync + 'static, - E5: Display + Debug + Send + Sync + 'static, - E6: Display + Debug + Send + Sync + 'static, - C: Clone + fmt::Debug + PartialEq + JsonSchema, - Q: CustomQuery + DeserializeOwned + 'static, -{ - execute_fn: ContractClosure, - instantiate_fn: ContractClosure, - query_fn: QueryClosure, - sudo_fn: Option>, - reply_fn: Option>, - migrate_fn: Option>, -} - -impl ContractWrapper -where - T1: DeserializeOwned + Debug + 'static, - T2: DeserializeOwned + 'static, - T3: DeserializeOwned + 'static, - E1: Display + Debug + Send + Sync + 'static, - E2: Display + Debug + Send + Sync + 'static, - E3: Display + Debug + Send + Sync + 'static, - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - Q: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new( - execute_fn: ContractFn, - instantiate_fn: ContractFn, - query_fn: QueryFn, - ) -> Self { - Self { - execute_fn: Box::new(execute_fn), - instantiate_fn: Box::new(instantiate_fn), - query_fn: Box::new(query_fn), - sudo_fn: None, - reply_fn: None, - migrate_fn: None, - } - } - - /// this will take a contract that returns Response and will "upgrade" it - /// to Response if needed to be compatible with a chain-specific extension - pub fn new_with_empty( - execute_fn: ContractFn, - instantiate_fn: ContractFn, - query_fn: QueryFn, - ) -> Self { - Self { - execute_fn: customize_fn(execute_fn), - instantiate_fn: customize_fn(instantiate_fn), - query_fn: customize_query(query_fn), - sudo_fn: None, - reply_fn: None, - migrate_fn: None, - } - } -} - -impl - ContractWrapper -where - T1: DeserializeOwned + Debug + 'static, - T2: DeserializeOwned + 'static, - T3: DeserializeOwned + 'static, - T4: DeserializeOwned + 'static, - T6: DeserializeOwned + 'static, - E1: Display + Debug + Send + Sync + 'static, - E2: Display + Debug + Send + Sync + 'static, - E3: Display + Debug + Send + Sync + 'static, - E4: Display + Debug + Send + Sync + 'static, - E5: Display + Debug + Send + Sync + 'static, - E6: Display + Debug + Send + Sync + 'static, - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - Q: CustomQuery + DeserializeOwned + 'static, -{ - pub fn with_sudo( - self, - sudo_fn: PermissionedFn, - ) -> ContractWrapper - where - T4A: DeserializeOwned + 'static, - E4A: Display + Debug + Send + Sync + 'static, - { - ContractWrapper { - execute_fn: self.execute_fn, - instantiate_fn: self.instantiate_fn, - query_fn: self.query_fn, - sudo_fn: Some(Box::new(sudo_fn)), - reply_fn: self.reply_fn, - migrate_fn: self.migrate_fn, - } - } - - pub fn with_sudo_empty( - self, - sudo_fn: PermissionedFn, - ) -> ContractWrapper - where - T4A: DeserializeOwned + 'static, - E4A: Display + Debug + Send + Sync + 'static, - { - ContractWrapper { - execute_fn: self.execute_fn, - instantiate_fn: self.instantiate_fn, - query_fn: self.query_fn, - sudo_fn: Some(customize_permissioned_fn(sudo_fn)), - reply_fn: self.reply_fn, - migrate_fn: self.migrate_fn, - } - } - - pub fn with_reply( - self, - reply_fn: ReplyFn, - ) -> ContractWrapper - where - E5A: Display + Debug + Send + Sync + 'static, - { - ContractWrapper { - execute_fn: self.execute_fn, - instantiate_fn: self.instantiate_fn, - query_fn: self.query_fn, - sudo_fn: self.sudo_fn, - reply_fn: Some(Box::new(reply_fn)), - migrate_fn: self.migrate_fn, - } - } - - /// A correlate of new_with_empty - pub fn with_reply_empty( - self, - reply_fn: ReplyFn, - ) -> ContractWrapper - where - E5A: Display + Debug + Send + Sync + 'static, - { - ContractWrapper { - execute_fn: self.execute_fn, - instantiate_fn: self.instantiate_fn, - query_fn: self.query_fn, - sudo_fn: self.sudo_fn, - reply_fn: Some(customize_permissioned_fn(reply_fn)), - migrate_fn: self.migrate_fn, - } - } - - pub fn with_migrate( - self, - migrate_fn: PermissionedFn, - ) -> ContractWrapper - where - T6A: DeserializeOwned + 'static, - E6A: Display + Debug + Send + Sync + 'static, - { - ContractWrapper { - execute_fn: self.execute_fn, - instantiate_fn: self.instantiate_fn, - query_fn: self.query_fn, - sudo_fn: self.sudo_fn, - reply_fn: self.reply_fn, - migrate_fn: Some(Box::new(migrate_fn)), - } - } - - pub fn with_migrate_empty( - self, - migrate_fn: PermissionedFn, - ) -> ContractWrapper - where - T6A: DeserializeOwned + 'static, - E6A: Display + Debug + Send + Sync + 'static, - { - ContractWrapper { - execute_fn: self.execute_fn, - instantiate_fn: self.instantiate_fn, - query_fn: self.query_fn, - sudo_fn: self.sudo_fn, - reply_fn: self.reply_fn, - migrate_fn: Some(customize_permissioned_fn(migrate_fn)), - } - } -} - -fn customize_fn(raw_fn: ContractFn) -> ContractClosure -where - T: DeserializeOwned + 'static, - E: Display + Debug + Send + Sync + 'static, - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - Q: CustomQuery + DeserializeOwned + 'static, -{ - let customized = move |mut deps: DepsMut, - env: Env, - info: MessageInfo, - msg: T| - -> Result, E> { - let deps = decustomize_deps_mut(&mut deps); - raw_fn(deps, env, info, msg).map(customize_response::) - }; - Box::new(customized) -} - -fn customize_query(raw_fn: QueryFn) -> QueryClosure -where - T: DeserializeOwned + 'static, - E: Display + Debug + Send + Sync + 'static, - Q: CustomQuery + DeserializeOwned + 'static, -{ - let customized = move |deps: Deps, env: Env, msg: T| -> Result { - let deps = decustomize_deps(&deps); - raw_fn(deps, env, msg) - }; - Box::new(customized) -} - -fn decustomize_deps_mut<'a, Q>(deps: &'a mut DepsMut) -> DepsMut<'a, Empty> -where - Q: CustomQuery + DeserializeOwned + 'static, -{ - DepsMut { - storage: deps.storage, - api: deps.api, - querier: QuerierWrapper::new(deps.querier.deref()), - } -} - -fn decustomize_deps<'a, Q>(deps: &'a Deps<'a, Q>) -> Deps<'a, Empty> -where - Q: CustomQuery + DeserializeOwned + 'static, -{ - Deps { - storage: deps.storage, - api: deps.api, - querier: QuerierWrapper::new(deps.querier.deref()), - } -} - -fn customize_permissioned_fn( - raw_fn: PermissionedFn, -) -> PermissionedClosure -where - T: DeserializeOwned + 'static, - E: Display + Debug + Send + Sync + 'static, - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - Q: CustomQuery + DeserializeOwned + 'static, -{ - let customized = move |deps: DepsMut, env: Env, msg: T| -> Result, E> { - raw_fn(deps, env, msg).map(customize_response::) - }; - Box::new(customized) -} - -fn customize_response(resp: Response) -> Response -where - C: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - let mut customized_resp = Response::::new() - .add_submessages(resp.messages.into_iter().map(customize_msg::)) - .add_events(resp.events) - .add_attributes(resp.attributes); - customized_resp.data = resp.data; - customized_resp -} - -fn customize_msg(msg: SubMsg) -> SubMsg -where - C: Clone + fmt::Debug + PartialEq + JsonSchema, -{ - SubMsg { - msg: match msg.msg { - CosmosMsg::Wasm(wasm) => CosmosMsg::Wasm(wasm), - CosmosMsg::Bank(bank) => CosmosMsg::Bank(bank), - CosmosMsg::Staking(staking) => CosmosMsg::Staking(staking), - CosmosMsg::Distribution(distribution) => CosmosMsg::Distribution(distribution), - CosmosMsg::Custom(_) => unreachable!(), - #[cfg(feature = "stargate")] - CosmosMsg::Ibc(ibc) => CosmosMsg::Ibc(ibc), - #[cfg(feature = "stargate")] - CosmosMsg::Stargate { type_url, value } => CosmosMsg::Stargate { type_url, value }, - _ => panic!("unknown message variant {:?}", msg), - }, - id: msg.id, - gas_limit: msg.gas_limit, - reply_on: msg.reply_on, - } -} - -impl Contract - for ContractWrapper -where - T1: DeserializeOwned + Debug + Clone, - T2: DeserializeOwned + Debug + Clone, - T3: DeserializeOwned + Debug + Clone, - T4: DeserializeOwned, - T6: DeserializeOwned, - E1: Display + Debug + Send + Sync + Error + 'static, - E2: Display + Debug + Send + Sync + Error + 'static, - E3: Display + Debug + Send + Sync + Error + 'static, - E4: Display + Debug + Send + Sync + 'static, - E5: Display + Debug + Send + Sync + 'static, - E6: Display + Debug + Send + Sync + 'static, - C: Clone + fmt::Debug + PartialEq + JsonSchema, - Q: CustomQuery + DeserializeOwned, -{ - fn execute( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Vec, - ) -> AnyResult> { - let msg: T1 = from_slice(&msg)?; - (self.execute_fn)(deps, env, info, msg).map_err(|err| anyhow!(err)) - } - - fn instantiate( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Vec, - ) -> AnyResult> { - let msg: T2 = from_slice(&msg)?; - (self.instantiate_fn)(deps, env, info, msg).map_err(|err| anyhow!(err)) - } - - fn query(&self, deps: Deps, env: Env, msg: Vec) -> AnyResult { - let msg: T3 = from_slice(&msg)?; - (self.query_fn)(deps, env, msg).map_err(|err| anyhow!(err)) - } - - // this returns an error if the contract doesn't implement sudo - fn sudo(&self, deps: DepsMut, env: Env, msg: Vec) -> AnyResult> { - let msg = from_slice(&msg)?; - match &self.sudo_fn { - Some(sudo) => sudo(deps, env, msg).map_err(|err| anyhow!(err)), - None => bail!("sudo not implemented for contract"), - } - } - - // this returns an error if the contract doesn't implement reply - fn reply(&self, deps: DepsMut, env: Env, reply_data: Reply) -> AnyResult> { - match &self.reply_fn { - Some(reply) => reply(deps, env, reply_data).map_err(|err| anyhow!(err)), - None => bail!("reply not implemented for contract"), - } - } - - // this returns an error if the contract doesn't implement migrate - fn migrate(&self, deps: DepsMut, env: Env, msg: Vec) -> AnyResult> { - let msg = from_slice(&msg)?; - match &self.migrate_fn { - Some(migrate) => migrate(deps, env, msg).map_err(|err| anyhow!(err)), - None => bail!("migrate not implemented for contract"), - } - } -} diff --git a/packages/multi-test/src/custom_handler.rs b/packages/multi-test/src/custom_handler.rs deleted file mode 100644 index 298f58cce..000000000 --- a/packages/multi-test/src/custom_handler.rs +++ /dev/null @@ -1,93 +0,0 @@ -use anyhow::{bail, Result as AnyResult}; -use derivative::Derivative; -use std::cell::{Ref, RefCell}; -use std::ops::Deref; -use std::rc::Rc; - -use cosmwasm_std::{Addr, Api, Binary, BlockInfo, Empty, Querier, Storage}; - -use crate::app::CosmosRouter; -use crate::{AppResponse, Module}; - -/// Internal state of `CachingCustomHandler` wrapping internal mutability so it is not exposed to -/// user. Those have to be shared internal state, as after mock is passed to app it is not -/// possible to access mock internals which are not exposed by API. -#[derive(Derivative)] -#[derivative(Default(bound = "", new = "true"), Clone(bound = ""))] -pub struct CachingCustomHandlerState { - execs: Rc>>, - queries: Rc>>, -} - -impl CachingCustomHandlerState { - pub fn execs(&self) -> impl Deref + '_ { - Ref::map(self.execs.borrow(), Vec::as_slice) - } - - pub fn queries(&self) -> impl Deref + '_ { - Ref::map(self.queries.borrow(), Vec::as_slice) - } - - pub fn reset(&self) { - self.execs.borrow_mut().clear(); - self.queries.borrow_mut().clear(); - } -} - -/// Custom handler storing all the messages it received, so they can be later verified. State is -/// thin shared state, so it can be hold after mock is passed to App to read state. -#[derive(Clone, Derivative)] -#[derivative(Default(bound = "", new = "true"))] -pub struct CachingCustomHandler { - state: CachingCustomHandlerState, -} - -impl CachingCustomHandler { - pub fn state(&self) -> CachingCustomHandlerState { - self.state.clone() - } -} - -impl Module for CachingCustomHandler { - type ExecT = Exec; - type QueryT = Query; - type SudoT = Empty; - - // TODO: how to assert - // where ExecC: Exec, QueryC: Query - fn execute( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - _sender: Addr, - msg: Self::ExecT, - ) -> AnyResult { - self.state.execs.borrow_mut().push(msg); - Ok(AppResponse::default()) - } - - fn sudo( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - msg: Self::SudoT, - ) -> AnyResult { - bail!("Unexpected sudo msg {:?}", msg) - } - - fn query( - &self, - _api: &dyn Api, - _storage: &dyn Storage, - _querier: &dyn Querier, - _block: &BlockInfo, - request: Self::QueryT, - ) -> AnyResult { - self.state.queries.borrow_mut().push(request); - Ok(Binary::default()) - } -} diff --git a/packages/multi-test/src/error.rs b/packages/multi-test/src/error.rs deleted file mode 100644 index f0bd0920d..000000000 --- a/packages/multi-test/src/error.rs +++ /dev/null @@ -1,46 +0,0 @@ -use cosmwasm_std::{WasmMsg, WasmQuery}; -use thiserror::Error; - -#[derive(Debug, Error, PartialEq, Eq)] -pub enum Error { - #[error("Empty attribute key. Value: {value}")] - EmptyAttributeKey { value: String }, - - #[error("Empty attribute value. Key: {key}")] - EmptyAttributeValue { key: String }, - - #[error("Attribute key strats with reserved prefix _: {0}")] - ReservedAttributeKey(String), - - #[error("Event type too short: {0}")] - EventTypeTooShort(String), - - #[error("Unsupported wasm query: {0:?}")] - UnsupportedWasmQuery(WasmQuery), - - #[error("Unsupported wasm message: {0:?}")] - UnsupportedWasmMsg(WasmMsg), - - #[error("Unregistered code id")] - UnregisteredCodeId(usize), -} - -impl Error { - pub fn empty_attribute_key(value: impl Into) -> Self { - Self::EmptyAttributeKey { - value: value.into(), - } - } - - pub fn empty_attribute_value(key: impl Into) -> Self { - Self::EmptyAttributeValue { key: key.into() } - } - - pub fn reserved_attribute_key(key: impl Into) -> Self { - Self::ReservedAttributeKey(key.into()) - } - - pub fn event_type_too_short(ty: impl Into) -> Self { - Self::EventTypeTooShort(ty.into()) - } -} diff --git a/packages/multi-test/src/executor.rs b/packages/multi-test/src/executor.rs deleted file mode 100644 index 75be02d60..000000000 --- a/packages/multi-test/src/executor.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::fmt; - -use cosmwasm_std::{ - to_binary, Addr, Attribute, BankMsg, Binary, Coin, CosmosMsg, Event, SubMsgResponse, WasmMsg, -}; -use cw_utils::{parse_execute_response_data, parse_instantiate_response_data}; -use schemars::JsonSchema; -use serde::Serialize; - -use anyhow::Result as AnyResult; - -#[derive(Default, Clone, Debug)] -pub struct AppResponse { - pub events: Vec, - pub data: Option, -} - -impl AppResponse { - // Return all custom attributes returned by the contract in the `idx` event. - // We assert the type is wasm, and skip the contract_address attribute. - #[track_caller] - pub fn custom_attrs(&self, idx: usize) -> &[Attribute] { - assert_eq!(self.events[idx].ty.as_str(), "wasm"); - &self.events[idx].attributes[1..] - } - - /// Check if there is an Event that is a super-set of this. - /// It has the same type, and all compare.attributes are included in it as well. - /// You don't need to specify them all. - pub fn has_event(&self, expected: &Event) -> bool { - self.events.iter().any(|ev| { - expected.ty == ev.ty - && expected - .attributes - .iter() - .all(|at| ev.attributes.contains(at)) - }) - } - - /// Like has_event but panics if no match - #[track_caller] - pub fn assert_event(&self, expected: &Event) { - assert!( - self.has_event(expected), - "Expected to find an event {:?}, but received: {:?}", - expected, - self.events - ); - } -} - -/// They have the same shape, SubMsgExecutionResponse is what is returned in reply. -/// This is just to make some test cases easier. -impl From for AppResponse { - fn from(reply: SubMsgResponse) -> Self { - AppResponse { - data: reply.data, - events: reply.events, - } - } -} - -pub trait Executor -where - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, -{ - /// Runs arbitrary CosmosMsg. - /// This will create a cache before the execution, so no state changes are persisted if this - /// returns an error, but all are persisted on success. - fn execute(&mut self, sender: Addr, msg: CosmosMsg) -> AnyResult; - - /// Create a contract and get the new address. - /// This is just a helper around execute() - fn instantiate_contract>( - &mut self, - code_id: u64, - sender: Addr, - init_msg: &T, - send_funds: &[Coin], - label: U, - admin: Option, - ) -> AnyResult { - // instantiate contract - let init_msg = to_binary(init_msg)?; - let msg = WasmMsg::Instantiate { - admin, - code_id, - msg: init_msg, - funds: send_funds.to_vec(), - label: label.into(), - }; - let res = self.execute(sender, msg.into())?; - let data = parse_instantiate_response_data(res.data.unwrap_or_default().as_slice())?; - Ok(Addr::unchecked(data.contract_address)) - } - - /// Execute a contract and process all returned messages. - /// This is just a helper around execute(), - /// but we parse out the data field to that what is returned by the contract (not the protobuf wrapper) - fn execute_contract( - &mut self, - sender: Addr, - contract_addr: Addr, - msg: &T, - send_funds: &[Coin], - ) -> AnyResult { - let binary_msg = to_binary(msg)?; - let wrapped_msg = WasmMsg::Execute { - contract_addr: contract_addr.into_string(), - msg: binary_msg, - funds: send_funds.to_vec(), - }; - let mut res = self.execute(sender, wrapped_msg.into())?; - res.data = res - .data - .and_then(|d| parse_execute_response_data(d.as_slice()).unwrap().data); - Ok(res) - } - - /// Migrate a contract. Sender must be registered admin. - /// This is just a helper around execute() - fn migrate_contract( - &mut self, - sender: Addr, - contract_addr: Addr, - msg: &T, - new_code_id: u64, - ) -> AnyResult { - let msg = to_binary(msg)?; - let msg = WasmMsg::Migrate { - contract_addr: contract_addr.into(), - msg, - new_code_id, - }; - self.execute(sender, msg.into()) - } - - fn send_tokens( - &mut self, - sender: Addr, - recipient: Addr, - amount: &[Coin], - ) -> AnyResult { - let msg = BankMsg::Send { - to_address: recipient.to_string(), - amount: amount.to_vec(), - }; - self.execute(sender, msg.into()) - } -} diff --git a/packages/multi-test/src/lib.rs b/packages/multi-test/src/lib.rs deleted file mode 100644 index 7d9ae4ff9..000000000 --- a/packages/multi-test/src/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Multitest is a design to simulate a blockchain environment in pure Rust. -//! This allows us to run unit tests that involve contract -> contract, -//! and contract -> bank interactions. This is not intended to be a full blockchain app -//! but to simulate the Cosmos SDK x/wasm module close enough to gain confidence in -//! multi-contract deployements before testing them on a live blockchain. -//! -//! To understand the design of this module, please refer to `../DESIGN.md` - -mod app; -mod bank; -#[allow(clippy::type_complexity)] -mod contracts; -pub mod custom_handler; -pub mod error; -mod executor; -mod module; -mod prefixed_storage; -mod staking; -mod test_helpers; -mod transactions; -mod wasm; - -pub use crate::app::{ - custom_app, next_block, App, AppBuilder, BasicApp, BasicAppBuilder, CosmosRouter, Router, - SudoMsg, -}; -pub use crate::bank::{Bank, BankKeeper, BankSudo}; -pub use crate::contracts::{Contract, ContractWrapper}; -pub use crate::executor::{AppResponse, Executor}; -pub use crate::module::{FailingModule, Module}; -pub use crate::staking::{DistributionKeeper, StakeKeeper, Staking, StakingInfo, StakingSudo}; -pub use crate::wasm::{Wasm, WasmKeeper, WasmSudo}; diff --git a/packages/multi-test/src/module.rs b/packages/multi-test/src/module.rs deleted file mode 100644 index 7125a3e4d..000000000 --- a/packages/multi-test/src/module.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::marker::PhantomData; - -use anyhow::{bail, Result as AnyResult}; -use cosmwasm_std::{Addr, Api, Binary, BlockInfo, CustomQuery, Querier, Storage}; - -use crate::app::CosmosRouter; -use crate::AppResponse; -use schemars::JsonSchema; -use serde::de::DeserializeOwned; - -pub trait Module { - type ExecT; - type QueryT; - type SudoT; - - /// execute runs any ExecT message, which can be called by any external actor - /// or smart contract - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - sender: Addr, - msg: Self::ExecT, - ) -> AnyResult - where - ExecC: std::fmt::Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static; - - /// sudo runs privileged actions, like minting tokens, or governance proposals. - /// This allows modules to have full access to these privileged actions, - /// that cannot be triggered by smart contracts. - /// - /// There is no sender, as this must be previously authorized before the call - fn sudo( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - msg: Self::SudoT, - ) -> AnyResult - where - ExecC: std::fmt::Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static; - - fn query( - &self, - api: &dyn Api, - storage: &dyn Storage, - querier: &dyn Querier, - block: &BlockInfo, - request: Self::QueryT, - ) -> AnyResult; -} - -pub struct FailingModule(PhantomData<(ExecT, QueryT, SudoT)>); - -impl FailingModule { - pub fn new() -> Self { - FailingModule(PhantomData) - } -} - -impl Default for FailingModule { - fn default() -> Self { - Self::new() - } -} - -impl Module for FailingModule -where - Exec: std::fmt::Debug, - Query: std::fmt::Debug, - Sudo: std::fmt::Debug, -{ - type ExecT = Exec; - type QueryT = Query; - type SudoT = Sudo; - - fn execute( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - sender: Addr, - msg: Self::ExecT, - ) -> AnyResult { - bail!("Unexpected exec msg {:?} from {:?}", msg, sender) - } - - fn sudo( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - msg: Self::SudoT, - ) -> AnyResult { - bail!("Unexpected sudo msg {:?}", msg) - } - - fn query( - &self, - _api: &dyn Api, - _storage: &dyn Storage, - _querier: &dyn Querier, - _block: &BlockInfo, - request: Self::QueryT, - ) -> AnyResult { - bail!("Unexpected custom query {:?}", request) - } -} diff --git a/packages/multi-test/src/prefixed_storage.rs b/packages/multi-test/src/prefixed_storage.rs deleted file mode 100644 index b74e4d764..000000000 --- a/packages/multi-test/src/prefixed_storage.rs +++ /dev/null @@ -1,185 +0,0 @@ -mod length_prefixed; -mod namespace_helpers; - -use cosmwasm_std::Storage; -#[cfg(feature = "iterator")] -use cosmwasm_std::{Order, Record}; - -use length_prefixed::{to_length_prefixed, to_length_prefixed_nested}; -#[cfg(feature = "iterator")] -use namespace_helpers::range_with_prefix; -use namespace_helpers::{get_with_prefix, remove_with_prefix, set_with_prefix}; - -/// 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) -} - -/// 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 PrefixedStorage<'a> { - storage: &'a mut dyn Storage, - prefix: Vec, -} - -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(storage: &'a mut dyn Storage, namespaces: &[&[u8]]) -> Self { - PrefixedStorage { - storage, - prefix: to_length_prefixed_nested(namespaces), - } - } -} - -impl<'a> Storage for PrefixedStorage<'a> { - fn get(&self, key: &[u8]) -> Option> { - get_with_prefix(self.storage, &self.prefix, key) - } - - 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(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> { - range_with_prefix(self.storage, &self.prefix, start, end, order) - } -} - -pub struct ReadonlyPrefixedStorage<'a> { - storage: &'a dyn Storage, - prefix: Vec, -} - -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(storage: &'a dyn Storage, namespaces: &[&[u8]]) -> Self { - ReadonlyPrefixedStorage { - storage, - prefix: to_length_prefixed_nested(namespaces), - } - } -} - -impl<'a> Storage for ReadonlyPrefixedStorage<'a> { - fn get(&self, key: &[u8]) -> Option> { - get_with_prefix(self.storage, &self.prefix, key) - } - - fn set(&mut self, _key: &[u8], _value: &[u8]) { - unimplemented!(); - } - - fn remove(&mut self, _key: &[u8]) { - unimplemented!(); - } - - #[cfg(feature = "iterator")] - /// range allows iteration over a set of keys, either forwards or backwards - fn range<'b>( - &'b self, - start: Option<&[u8]>, - end: Option<&[u8]>, - order: Order, - ) -> Box + 'b> { - range_with_prefix(self.storage, &self.prefix, start, end, order) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::MockStorage; - - #[test] - fn prefixed_storage_set_and_get() { - let mut storage = MockStorage::new(); - - // set - let mut s1 = PrefixedStorage::new(&mut storage, b"foo"); - s1.set(b"bar", b"gotcha"); - assert_eq!(storage.get(b"\x00\x03foobar").unwrap(), b"gotcha".to_vec()); - - // get - let s2 = PrefixedStorage::new(&mut storage, b"foo"); - assert_eq!(s2.get(b"bar"), Some(b"gotcha".to_vec())); - assert_eq!(s2.get(b"elsewhere"), None); - } - - #[test] - fn prefixed_storage_multilevel_set_and_get() { - let mut storage = MockStorage::new(); - - // 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 readonly_prefixed_storage_get() { - let mut storage = MockStorage::new(); - storage.set(b"\x00\x03foobar", b"gotcha"); - - // try readonly correctly - let s1 = ReadonlyPrefixedStorage::new(&storage, b"foo"); - assert_eq!(s1.get(b"bar"), Some(b"gotcha".to_vec())); - assert_eq!(s1.get(b"elsewhere"), None); - - // no collisions with other prefixes - let s2 = ReadonlyPrefixedStorage::new(&storage, b"fo"); - assert_eq!(s2.get(b"obar"), None); - } - - #[test] - fn readonly_prefixed_storage_multilevel_get() { - let mut storage = MockStorage::new(); - storage.set(b"\x00\x03foo\x00\x03barbaz", b"winner"); - - 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/multi-test/src/prefixed_storage/length_prefixed.rs b/packages/multi-test/src/prefixed_storage/length_prefixed.rs deleted file mode 100644 index 5d97b3b41..000000000 --- a/packages/multi-test/src/prefixed_storage/length_prefixed.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! This module is an implemention of a namespacing scheme described -//! in https://github.com/webmaster128/key-namespacing#length-prefixed-keys -//! -//! Everything in this file is only responsible for building such keys -//! and is in no way specific to any kind of storage. - -/// Calculates the raw key prefix for a given namespace as documented -/// in https://github.com/webmaster128/key-namespacing#length-prefixed-keys -pub fn to_length_prefixed(namespace: &[u8]) -> Vec { - let mut out = Vec::with_capacity(namespace.len() + 2); - out.extend_from_slice(&encode_length(namespace)); - out.extend_from_slice(namespace); - out -} - -/// Calculates the raw key prefix for a given nested namespace -/// as documented in https://github.com/webmaster128/key-namespacing#nesting -pub fn to_length_prefixed_nested(namespaces: &[&[u8]]) -> Vec { - let mut size = 0; - for &namespace in namespaces { - size += namespace.len() + 2; - } - - let mut out = Vec::with_capacity(size); - for &namespace in namespaces { - out.extend_from_slice(&encode_length(namespace)); - out.extend_from_slice(namespace); - } - out -} - -/// Encodes the length of a given namespace as a 2 byte big endian encoded integer -fn encode_length(namespace: &[u8]) -> [u8; 2] { - if namespace.len() > 0xFFFF { - panic!("only supports namespaces up to length 0xFFFF") - } - let length_bytes = (namespace.len() as u32).to_be_bytes(); - [length_bytes[2], length_bytes[3]] -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn to_length_prefixed_works() { - assert_eq!(to_length_prefixed(b""), b"\x00\x00"); - assert_eq!(to_length_prefixed(b"a"), b"\x00\x01a"); - assert_eq!(to_length_prefixed(b"ab"), b"\x00\x02ab"); - assert_eq!(to_length_prefixed(b"abc"), b"\x00\x03abc"); - } - - #[test] - fn to_length_prefixed_works_for_long_prefix() { - let long_namespace1 = vec![0; 256]; - let prefix1 = to_length_prefixed(&long_namespace1); - assert_eq!(prefix1.len(), 256 + 2); - assert_eq!(&prefix1[0..2], b"\x01\x00"); - - let long_namespace2 = vec![0; 30000]; - let prefix2 = to_length_prefixed(&long_namespace2); - assert_eq!(prefix2.len(), 30000 + 2); - assert_eq!(&prefix2[0..2], b"\x75\x30"); - - let long_namespace3 = vec![0; 0xFFFF]; - let prefix3 = to_length_prefixed(&long_namespace3); - assert_eq!(prefix3.len(), 0xFFFF + 2); - assert_eq!(&prefix3[0..2], b"\xFF\xFF"); - } - - #[test] - #[should_panic(expected = "only supports namespaces up to length 0xFFFF")] - fn to_length_prefixed_panics_for_too_long_prefix() { - let limit = 0xFFFF; - let long_namespace = vec![0; limit + 1]; - to_length_prefixed(&long_namespace); - } - - #[test] - fn to_length_prefixed_calculates_capacity_correctly() { - // Those tests cannot guarantee the required capacity was calculated correctly before - // the vector allocation but increase the likelyhood of a proper implementation. - - let key = to_length_prefixed(b""); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed(b"h"); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed(b"hij"); - assert_eq!(key.capacity(), key.len()); - } - - #[test] - fn to_length_prefixed_nested_works() { - assert_eq!(to_length_prefixed_nested(&[]), b""); - assert_eq!(to_length_prefixed_nested(&[b""]), b"\x00\x00"); - assert_eq!(to_length_prefixed_nested(&[b"", b""]), b"\x00\x00\x00\x00"); - - assert_eq!(to_length_prefixed_nested(&[b"a"]), b"\x00\x01a"); - assert_eq!( - to_length_prefixed_nested(&[b"a", b"ab"]), - b"\x00\x01a\x00\x02ab" - ); - assert_eq!( - to_length_prefixed_nested(&[b"a", b"ab", b"abc"]), - b"\x00\x01a\x00\x02ab\x00\x03abc" - ); - } - - #[test] - fn to_length_prefixed_nested_allows_many_long_namespaces() { - // The 0xFFFF limit is for each namespace, not for the combination of them - - let long_namespace1 = vec![0xaa; 0xFFFD]; - let long_namespace2 = vec![0xbb; 0xFFFE]; - let long_namespace3 = vec![0xcc; 0xFFFF]; - - let prefix = - to_length_prefixed_nested(&[&long_namespace1, &long_namespace2, &long_namespace3]); - assert_eq!(&prefix[0..2], b"\xFF\xFD"); - assert_eq!(&prefix[2..(2 + 0xFFFD)], long_namespace1.as_slice()); - assert_eq!(&prefix[(2 + 0xFFFD)..(2 + 0xFFFD + 2)], b"\xFF\xFe"); - assert_eq!( - &prefix[(2 + 0xFFFD + 2)..(2 + 0xFFFD + 2 + 0xFFFE)], - long_namespace2.as_slice() - ); - assert_eq!( - &prefix[(2 + 0xFFFD + 2 + 0xFFFE)..(2 + 0xFFFD + 2 + 0xFFFE + 2)], - b"\xFF\xFf" - ); - assert_eq!( - &prefix[(2 + 0xFFFD + 2 + 0xFFFE + 2)..(2 + 0xFFFD + 2 + 0xFFFE + 2 + 0xFFFF)], - long_namespace3.as_slice() - ); - } - - #[test] - fn to_length_prefixed_nested_calculates_capacity_correctly() { - // Those tests cannot guarantee the required capacity was calculated correctly before - // the vector allocation but increase the likelyhood of a proper implementation. - - let key = to_length_prefixed_nested(&[]); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed_nested(&[b""]); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed_nested(&[b"a"]); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed_nested(&[b"a", b"bc"]); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed_nested(&[b"a", b"bc", b"def"]); - assert_eq!(key.capacity(), key.len()); - } - - #[test] - fn encode_length_works() { - assert_eq!(encode_length(b""), *b"\x00\x00"); - assert_eq!(encode_length(b"a"), *b"\x00\x01"); - assert_eq!(encode_length(b"aa"), *b"\x00\x02"); - assert_eq!(encode_length(b"aaa"), *b"\x00\x03"); - assert_eq!(encode_length(&vec![1; 255]), *b"\x00\xff"); - assert_eq!(encode_length(&vec![1; 256]), *b"\x01\x00"); - assert_eq!(encode_length(&vec![1; 12345]), *b"\x30\x39"); - assert_eq!(encode_length(&vec![1; 65535]), *b"\xff\xff"); - } - - #[test] - #[should_panic(expected = "only supports namespaces up to length 0xFFFF")] - fn encode_length_panics_for_large_values() { - encode_length(&vec![1; 65536]); - } -} diff --git a/packages/multi-test/src/prefixed_storage/namespace_helpers.rs b/packages/multi-test/src/prefixed_storage/namespace_helpers.rs deleted file mode 100644 index 2e69605de..000000000 --- a/packages/multi-test/src/prefixed_storage/namespace_helpers.rs +++ /dev/null @@ -1,220 +0,0 @@ -use cosmwasm_std::Storage; -#[cfg(feature = "iterator")] -use cosmwasm_std::{Order, Record}; - -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 dyn Storage, - namespace: &[u8], - key: &[u8], - value: &[u8], -) { - storage.set(&concat(namespace, key), value); -} - -pub(crate) fn remove_with_prefix(storage: &mut dyn Storage, namespace: &[u8], key: &[u8]) { - storage.remove(&concat(namespace, key)); -} - -#[inline] -fn concat(namespace: &[u8], key: &[u8]) -> Vec { - let mut k = namespace.to_vec(); - k.extend_from_slice(key); - k -} - -#[cfg(feature = "iterator")] -pub(crate) fn range_with_prefix<'a>( - storage: &'a dyn Storage, - namespace: &[u8], - start: Option<&[u8]>, - end: Option<&[u8]>, - order: Order, -) -> Box + 'a> { - // prepare start, end with prefix - let start = match start { - Some(s) => concat(namespace, s), - None => namespace.to_vec(), - }; - let end = match end { - Some(e) => concat(namespace, e), - // end is updating last byte by one - None => namespace_upper_bound(namespace), - }; - - // get iterator from storage - let base_iterator = storage.range(Some(&start), Some(&end), order); - - // make a copy for the closure to handle lifetimes safely - let prefix = namespace.to_vec(); - let mapped = base_iterator.map(move |(k, v)| (trim(&prefix, &k), v)); - Box::new(mapped) -} - -#[cfg(feature = "iterator")] -#[inline] -fn trim(namespace: &[u8], key: &[u8]) -> Vec { - key[namespace.len()..].to_vec() -} - -/// Returns a new vec of same length and last byte incremented by one -/// If last bytes are 255, we handle overflow up the chain. -/// If all bytes are 255, this returns wrong data - but that is never possible as a namespace -#[cfg(feature = "iterator")] -fn namespace_upper_bound(input: &[u8]) -> Vec { - let mut copy = input.to_vec(); - // zero out all trailing 255, increment first that is not such - for i in (0..input.len()).rev() { - if copy[i] == 255 { - copy[i] = 0; - } else { - copy[i] += 1; - break; - } - } - copy -} - -#[cfg(test)] -mod tests { - use super::super::length_prefixed::to_length_prefixed; - use super::*; - use cosmwasm_std::testing::MockStorage; - - #[test] - fn prefix_get_set() { - let mut storage = MockStorage::new(); - let prefix = to_length_prefixed(b"foo"); - - set_with_prefix(&mut storage, &prefix, b"bar", b"gotcha"); - let rfoo = get_with_prefix(&storage, &prefix, b"bar"); - assert_eq!(rfoo, Some(b"gotcha".to_vec())); - - // no collisions with other prefixes - let other_prefix = to_length_prefixed(b"fo"); - let collision = get_with_prefix(&storage, &other_prefix, b"obar"); - assert_eq!(collision, None); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_works() { - let mut storage = MockStorage::new(); - let prefix = to_length_prefixed(b"foo"); - let other_prefix = to_length_prefixed(b"food"); - - // set some values in this range - set_with_prefix(&mut storage, &prefix, b"bar", b"none"); - set_with_prefix(&mut storage, &prefix, b"snowy", b"day"); - - // set some values outside this range - set_with_prefix(&mut storage, &other_prefix, b"moon", b"buggy"); - - // ensure we get proper result from prefixed_range iterator - let mut iter = range_with_prefix(&storage, &prefix, None, None, Order::Descending); - let first = iter.next().unwrap(); - assert_eq!(first, (b"snowy".to_vec(), b"day".to_vec())); - let second = iter.next().unwrap(); - assert_eq!(second, (b"bar".to_vec(), b"none".to_vec())); - assert!(iter.next().is_none()); - - // ensure we get raw result from base range - let iter = storage.range(None, None, Order::Ascending); - assert_eq!(3, iter.count()); - - // foo comes first - let mut iter = storage.range(None, None, Order::Ascending); - let first = iter.next().unwrap(); - let expected_key = concat(&prefix, b"bar"); - assert_eq!(first, (expected_key, b"none".to_vec())); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_with_prefix_wrapover() { - let mut storage = MockStorage::new(); - // if we don't properly wrap over there will be issues here (note 255+1 is used to calculate end) - let prefix = to_length_prefixed(b"f\xff\xff"); - let other_prefix = to_length_prefixed(b"f\xff\x44"); - - // set some values in this range - set_with_prefix(&mut storage, &prefix, b"bar", b"none"); - set_with_prefix(&mut storage, &prefix, b"snowy", b"day"); - - // set some values outside this range - set_with_prefix(&mut storage, &other_prefix, b"moon", b"buggy"); - - // ensure we get proper result from prefixed_range iterator - let iter = range_with_prefix(&storage, &prefix, None, None, Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"snowy".to_vec(), b"day".to_vec()), - (b"bar".to_vec(), b"none".to_vec()), - ] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_with_start_end_set() { - let mut storage = MockStorage::new(); - // if we don't properly wrap over there will be issues here (note 255+1 is used to calculate end) - let prefix = to_length_prefixed(b"f\xff\xff"); - let other_prefix = to_length_prefixed(b"f\xff\x44"); - - // set some values in this range - set_with_prefix(&mut storage, &prefix, b"bar", b"none"); - set_with_prefix(&mut storage, &prefix, b"snowy", b"day"); - - // set some values outside this range - set_with_prefix(&mut storage, &other_prefix, b"moon", b"buggy"); - - // make sure start and end are applied properly - let res: Vec = - range_with_prefix(&storage, &prefix, Some(b"b"), Some(b"c"), Order::Ascending) - .collect(); - assert_eq!(res.len(), 1); - assert_eq!(res[0], (b"bar".to_vec(), b"none".to_vec())); - - // make sure start and end are applied properly - let res_count = range_with_prefix( - &storage, - &prefix, - Some(b"bas"), - Some(b"sno"), - Order::Ascending, - ) - .count(); - assert_eq!(res_count, 0); - - let res: Vec = - range_with_prefix(&storage, &prefix, Some(b"ant"), None, Order::Ascending).collect(); - assert_eq!(res.len(), 2); - assert_eq!(res[0], (b"bar".to_vec(), b"none".to_vec())); - assert_eq!(res[1], (b"snowy".to_vec(), b"day".to_vec())); - } - - #[test] - #[cfg(feature = "iterator")] - fn namespace_upper_bound_works() { - assert_eq!(namespace_upper_bound(b"bob"), b"boc".to_vec()); - assert_eq!(namespace_upper_bound(b"fo\xfe"), b"fo\xff".to_vec()); - assert_eq!(namespace_upper_bound(b"fo\xff"), b"fp\x00".to_vec()); - // multiple \xff roll over - assert_eq!( - namespace_upper_bound(b"fo\xff\xff\xff"), - b"fp\x00\x00\x00".to_vec() - ); - // \xff not at the end are ignored - assert_eq!(namespace_upper_bound(b"\xffabc"), b"\xffabd".to_vec()); - } -} diff --git a/packages/multi-test/src/staking.rs b/packages/multi-test/src/staking.rs deleted file mode 100644 index 043838f32..000000000 --- a/packages/multi-test/src/staking.rs +++ /dev/null @@ -1,1809 +0,0 @@ -use std::collections::BTreeSet; - -use anyhow::{anyhow, bail, Result as AnyResult}; -use schemars::JsonSchema; - -use cosmwasm_std::{ - coin, ensure, ensure_eq, to_binary, Addr, AllDelegationsResponse, AllValidatorsResponse, Api, - BankMsg, Binary, BlockInfo, BondedDenomResponse, Coin, CustomQuery, Decimal, Delegation, - DelegationResponse, DistributionMsg, Empty, Event, FullDelegation, Querier, StakingMsg, - StakingQuery, Storage, Timestamp, Uint128, Validator, ValidatorResponse, -}; -use cw_storage_plus::{Deque, Item, Map}; -use serde::{Deserialize, Serialize}; - -use crate::app::CosmosRouter; -use crate::executor::AppResponse; -use crate::prefixed_storage::{prefixed, prefixed_read}; -use crate::{BankSudo, Module}; - -// Contains some general staking parameters -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct StakingInfo { - /// The denominator of the staking token - pub bonded_denom: String, - /// Time between unbonding and receiving tokens in seconds - pub unbonding_time: u64, - /// Interest rate per year (60 * 60 * 24 * 365 seconds) - pub apr: Decimal, -} - -impl Default for StakingInfo { - fn default() -> Self { - StakingInfo { - bonded_denom: "TOKEN".to_string(), - unbonding_time: 60, - apr: Decimal::percent(10), - } - } -} - -/// The number of stake and rewards of this validator the staker has. These can be fractional in case of slashing. -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)] -struct Shares { - stake: Decimal, - rewards: Decimal, -} - -impl Shares { - /// Calculates the share of validator rewards that should be given to this staker. - pub fn share_of_rewards(&self, validator: &ValidatorInfo, rewards: Decimal) -> Decimal { - rewards * self.stake / validator.stake - } -} - -/// Holds some operational data about a validator -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -struct ValidatorInfo { - /// The stakers that have staked with this validator. - /// We need to track them for updating their rewards. - stakers: BTreeSet, - /// The whole stake of all stakers - stake: Uint128, - /// The block time when this validator's rewards were last update. This is needed for rewards calculation. - last_rewards_calculation: Timestamp, -} - -impl ValidatorInfo { - pub fn new(block_time: Timestamp) -> Self { - Self { - stakers: BTreeSet::new(), - stake: Uint128::zero(), - last_rewards_calculation: block_time, - } - } -} - -const STAKING_INFO: Item = Item::new("staking_info"); -/// (staker_addr, validator_addr) -> shares -const STAKES: Map<(&Addr, &Addr), Shares> = Map::new("stakes"); -const VALIDATOR_MAP: Map<&Addr, Validator> = Map::new("validator_map"); -/// Additional vec of validators, in case the `iterator` feature is disabled -const VALIDATORS: Deque = Deque::new("validators"); -/// Contains additional info for each validator -const VALIDATOR_INFO: Map<&Addr, ValidatorInfo> = Map::new("validator_info"); -/// The queue of unbonding operations. This is needed because unbonding has a waiting time. See [`StakeKeeper`] -const UNBONDING_QUEUE: Deque<(Addr, Timestamp, u128)> = Deque::new("unbonding_queue"); - -pub const NAMESPACE_STAKING: &[u8] = b"staking"; - -// We need to expand on this, but we will need this to properly test out staking -#[derive(Clone, std::fmt::Debug, PartialEq, Eq, JsonSchema)] -pub enum StakingSudo { - /// Slashes the given percentage of the validator's stake. - /// For now, you cannot slash retrospectively in tests. - Slash { - validator: String, - percentage: Decimal, - }, - /// Causes the unbonding queue to be processed. - /// This needs to be triggered manually, since there is no good place to do this right now. - /// In cosmos-sdk, this is done in `EndBlock`, but we don't have that here. - ProcessQueue {}, -} - -pub trait Staking: Module {} - -pub trait Distribution: Module {} - -pub struct StakeKeeper { - module_addr: Addr, -} - -impl Default for StakeKeeper { - fn default() -> Self { - Self::new() - } -} - -impl StakeKeeper { - pub fn new() -> Self { - StakeKeeper { - // The address of the staking module. This holds all staked tokens. - module_addr: Addr::unchecked("staking_module"), - } - } - - /// Provides some general parameters to the stake keeper - pub fn setup(&self, storage: &mut dyn Storage, staking_info: StakingInfo) -> AnyResult<()> { - let mut storage = prefixed(storage, NAMESPACE_STAKING); - - STAKING_INFO.save(&mut storage, &staking_info)?; - Ok(()) - } - - /// Add a new validator available for staking - pub fn add_validator( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - block: &BlockInfo, - validator: Validator, - ) -> AnyResult<()> { - let mut storage = prefixed(storage, NAMESPACE_STAKING); - - let val_addr = api.addr_validate(&validator.address)?; - if VALIDATOR_MAP.may_load(&storage, &val_addr)?.is_some() { - bail!( - "Cannot add validator {}, since a validator with that address already exists", - val_addr - ); - } - - VALIDATOR_MAP.save(&mut storage, &val_addr, &validator)?; - VALIDATORS.push_back(&mut storage, &validator)?; - VALIDATOR_INFO.save(&mut storage, &val_addr, &ValidatorInfo::new(block.time))?; - Ok(()) - } - - fn get_staking_info(staking_storage: &dyn Storage) -> AnyResult { - Ok(STAKING_INFO.may_load(staking_storage)?.unwrap_or_default()) - } - - /// Returns the rewards of the given delegator at the given validator - pub fn get_rewards( - &self, - storage: &dyn Storage, - block: &BlockInfo, - delegator: &Addr, - validator: &Addr, - ) -> AnyResult> { - let staking_storage = prefixed_read(storage, NAMESPACE_STAKING); - - let validator_obj = match self.get_validator(&staking_storage, validator)? { - Some(validator) => validator, - None => bail!("validator {} not found", validator), - }; - // calculate rewards using fixed ratio - let shares = match STAKES.load(&staking_storage, (delegator, validator)) { - Ok(stakes) => stakes, - Err(_) => { - return Ok(None); - } - }; - let validator_info = VALIDATOR_INFO.load(&staking_storage, validator)?; - - Self::get_rewards_internal( - &staking_storage, - block, - &shares, - &validator_obj, - &validator_info, - ) - .map(Some) - } - - fn get_rewards_internal( - staking_storage: &dyn Storage, - block: &BlockInfo, - shares: &Shares, - validator: &Validator, - validator_info: &ValidatorInfo, - ) -> AnyResult { - let staking_info = Self::get_staking_info(staking_storage)?; - - // calculate missing rewards without updating the validator to reduce rounding errors - let new_validator_rewards = Self::calculate_rewards( - block.time, - validator_info.last_rewards_calculation, - staking_info.apr, - validator.commission, - validator_info.stake, - ); - - // calculate the delegator's share of those - let delegator_rewards = - shares.rewards + shares.share_of_rewards(validator_info, new_validator_rewards); - - Ok(Coin { - denom: staking_info.bonded_denom, - amount: Uint128::new(1) * delegator_rewards, // multiplying by 1 to convert Decimal to Uint128 - }) - } - - /// Calculates the rewards that are due since the last calculation. - fn calculate_rewards( - current_time: Timestamp, - since: Timestamp, - interest_rate: Decimal, - validator_commission: Decimal, - stake: Uint128, - ) -> Decimal { - // calculate time since last update (in seconds) - let time_diff = current_time.minus_seconds(since.seconds()).seconds(); - - // using decimal here to reduce rounding error when calling this function a lot - let reward = Decimal::from_ratio(stake, 1u128) - * interest_rate - * Decimal::from_ratio(time_diff, 1u128) - / Decimal::from_ratio(60u128 * 60 * 24 * 365, 1u128); - let commission = reward * validator_commission; - - reward - commission - } - - /// Updates the staking reward for the given validator and their stakers - /// It saves the validator info and it's stakers, so make sure not to overwrite that. - /// Always call this to update rewards before changing anything that influences future rewards. - fn update_rewards( - api: &dyn Api, - staking_storage: &mut dyn Storage, - block: &BlockInfo, - validator: &Addr, - ) -> AnyResult<()> { - let staking_info = Self::get_staking_info(staking_storage)?; - - let mut validator_info = VALIDATOR_INFO - .may_load(staking_storage, validator)? - .ok_or_else(|| anyhow!("validator {} not found", validator))?; - - let validator_obj = VALIDATOR_MAP.load(staking_storage, validator)?; - - if validator_info.last_rewards_calculation >= block.time { - return Ok(()); - } - - let new_rewards = Self::calculate_rewards( - block.time, - validator_info.last_rewards_calculation, - staking_info.apr, - validator_obj.commission, - validator_info.stake, - ); - - // update validator info and delegators - if !new_rewards.is_zero() { - validator_info.last_rewards_calculation = block.time; - - // save updated validator - VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?; - - let validator_addr = api.addr_validate(&validator_obj.address)?; - // update all delegators - for staker in validator_info.stakers.iter() { - STAKES.update( - staking_storage, - (staker, &validator_addr), - |shares| -> AnyResult<_> { - let mut shares = - shares.expect("all stakers in validator_info should exist"); - shares.rewards += shares.share_of_rewards(&validator_info, new_rewards); - Ok(shares) - }, - )?; - } - } - Ok(()) - } - - /// Returns the single validator with the given address (or `None` if there is no such validator) - fn get_validator( - &self, - staking_storage: &dyn Storage, - address: &Addr, - ) -> AnyResult> { - Ok(VALIDATOR_MAP.may_load(staking_storage, address)?) - } - - /// Returns all available validators - fn get_validators(&self, staking_storage: &dyn Storage) -> AnyResult> { - let res: Result<_, _> = VALIDATORS.iter(staking_storage)?.collect(); - Ok(res?) - } - - fn get_stake( - &self, - staking_storage: &dyn Storage, - account: &Addr, - validator: &Addr, - ) -> AnyResult> { - let shares = STAKES.may_load(staking_storage, (account, validator))?; - let staking_info = Self::get_staking_info(staking_storage)?; - - Ok(shares.map(|shares| { - Coin { - denom: staking_info.bonded_denom, - amount: Uint128::new(1) * shares.stake, // multiplying by 1 to convert Decimal to Uint128 - } - })) - } - - fn add_stake( - &self, - api: &dyn Api, - staking_storage: &mut dyn Storage, - block: &BlockInfo, - to_address: &Addr, - validator: &Addr, - amount: Coin, - ) -> AnyResult<()> { - self.validate_denom(staking_storage, &amount)?; - self.update_stake( - api, - staking_storage, - block, - to_address, - validator, - amount.amount, - false, - ) - } - - fn remove_stake( - &self, - api: &dyn Api, - staking_storage: &mut dyn Storage, - block: &BlockInfo, - from_address: &Addr, - validator: &Addr, - amount: Coin, - ) -> AnyResult<()> { - self.validate_denom(staking_storage, &amount)?; - self.update_stake( - api, - staking_storage, - block, - from_address, - validator, - amount.amount, - true, - ) - } - - fn update_stake( - &self, - api: &dyn Api, - staking_storage: &mut dyn Storage, - block: &BlockInfo, - delegator: &Addr, - validator: &Addr, - amount: impl Into, - sub: bool, - ) -> AnyResult<()> { - let amount = amount.into(); - - // update rewards for this validator - Self::update_rewards(api, staking_storage, block, validator)?; - - // now, we can update the stake of the delegator and validator - let mut validator_info = VALIDATOR_INFO - .may_load(staking_storage, validator)? - .unwrap_or_else(|| ValidatorInfo::new(block.time)); - let mut shares = STAKES - .may_load(staking_storage, (delegator, validator))? - .unwrap_or_default(); - let amount_dec = Decimal::from_ratio(amount, 1u128); - if sub { - if amount_dec > shares.stake { - bail!("insufficient stake"); - } - shares.stake -= amount_dec; - validator_info.stake = validator_info.stake.checked_sub(amount)?; - } else { - shares.stake += amount_dec; - validator_info.stake = validator_info.stake.checked_add(amount)?; - } - - // save updated values - if shares.stake.is_zero() { - // no more stake, so remove - STAKES.remove(staking_storage, (delegator, validator)); - validator_info.stakers.remove(delegator); - } else { - STAKES.save(staking_storage, (delegator, validator), &shares)?; - validator_info.stakers.insert(delegator.clone()); - } - // save updated validator info - VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?; - - Ok(()) - } - - fn slash( - &self, - api: &dyn Api, - staking_storage: &mut dyn Storage, - block: &BlockInfo, - validator: &Addr, - percentage: Decimal, - ) -> AnyResult<()> { - // calculate rewards before slashing - Self::update_rewards(api, staking_storage, block, validator)?; - - // update stake of validator and stakers - let mut validator_info = VALIDATOR_INFO - .may_load(staking_storage, validator)? - .ok_or_else(|| anyhow!("validator {} not found", validator))?; - - let remaining_percentage = Decimal::one() - percentage; - validator_info.stake = validator_info.stake * remaining_percentage; - - // if the stake is completely gone, we clear all stakers and reinitialize the validator - if validator_info.stake.is_zero() { - // need to remove all stakes - for delegator in validator_info.stakers.iter() { - STAKES.remove(staking_storage, (delegator, validator)); - } - validator_info.stakers.clear(); - } else { - // otherwise we update all stakers - for delegator in validator_info.stakers.iter() { - STAKES.update( - staking_storage, - (delegator, validator), - |stake| -> AnyResult<_> { - let mut stake = stake.expect("all stakers in validator_info should exist"); - stake.stake *= remaining_percentage; - - Ok(stake) - }, - )?; - } - } - VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?; - Ok(()) - } - - // Asserts that the given coin has the proper denominator - fn validate_denom(&self, staking_storage: &dyn Storage, amount: &Coin) -> AnyResult<()> { - let staking_info = Self::get_staking_info(staking_storage)?; - ensure_eq!( - amount.denom, - staking_info.bonded_denom, - anyhow!( - "cannot delegate coins of denominator {}, only of {}", - amount.denom, - staking_info.bonded_denom - ) - ); - Ok(()) - } - - // Asserts that the given coin has the proper denominator - fn validate_percentage(&self, percentage: Decimal) -> AnyResult<()> { - ensure!(percentage <= Decimal::one(), anyhow!("expected percentage")); - Ok(()) - } -} - -impl Staking for StakeKeeper {} - -impl Module for StakeKeeper { - type ExecT = StakingMsg; - type QueryT = StakingQuery; - type SudoT = StakingSudo; - - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - sender: Addr, - msg: StakingMsg, - ) -> AnyResult { - let mut staking_storage = prefixed(storage, NAMESPACE_STAKING); - match msg { - StakingMsg::Delegate { validator, amount } => { - let validator = api.addr_validate(&validator)?; - - // see https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/staking/keeper/msg_server.go#L251-L256 - let events = vec![Event::new("delegate") - .add_attribute("validator", &validator) - .add_attribute("amount", format!("{}{}", amount.amount, amount.denom)) - .add_attribute("new_shares", amount.amount.to_string())]; // TODO: calculate shares? - self.add_stake( - api, - &mut staking_storage, - block, - &sender, - &validator, - amount.clone(), - )?; - // move money from sender account to this module (note we can control sender here) - if !amount.amount.is_zero() { - router.execute( - api, - storage, - block, - sender, - BankMsg::Send { - to_address: self.module_addr.to_string(), - amount: vec![amount], - } - .into(), - )?; - } - Ok(AppResponse { events, data: None }) - } - StakingMsg::Undelegate { validator, amount } => { - let validator = api.addr_validate(&validator)?; - self.validate_denom(&staking_storage, &amount)?; - - // see https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/staking/keeper/msg_server.go#L378-L383 - let events = vec![Event::new("unbond") - .add_attribute("validator", &validator) - .add_attribute("amount", format!("{}{}", amount.amount, amount.denom)) - .add_attribute("completion_time", "2022-09-27T14:00:00+00:00")]; // TODO: actual date? - self.remove_stake( - api, - &mut staking_storage, - block, - &sender, - &validator, - amount.clone(), - )?; - // add tokens to unbonding queue - let staking_info = Self::get_staking_info(&staking_storage)?; - UNBONDING_QUEUE.push_back( - &mut staking_storage, - &( - sender.clone(), - block.time.plus_seconds(staking_info.unbonding_time), - amount.amount.u128(), - ), - )?; - Ok(AppResponse { events, data: None }) - } - StakingMsg::Redelegate { - src_validator, - dst_validator, - amount, - } => { - let src_validator = api.addr_validate(&src_validator)?; - let dst_validator = api.addr_validate(&dst_validator)?; - // see https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/staking/keeper/msg_server.go#L316-L322 - let events = vec![Event::new("redelegate") - .add_attribute("source_validator", &src_validator) - .add_attribute("destination_validator", &dst_validator) - .add_attribute("amount", format!("{}{}", amount.amount, amount.denom))]; - - self.remove_stake( - api, - &mut staking_storage, - block, - &sender, - &src_validator, - amount.clone(), - )?; - self.add_stake( - api, - &mut staking_storage, - block, - &sender, - &dst_validator, - amount, - )?; - - Ok(AppResponse { events, data: None }) - } - m => bail!("Unsupported staking message: {:?}", m), - } - } - - fn sudo( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - msg: StakingSudo, - ) -> AnyResult { - match msg { - StakingSudo::Slash { - validator, - percentage, - } => { - let mut staking_storage = prefixed(storage, NAMESPACE_STAKING); - let validator = api.addr_validate(&validator)?; - self.validate_percentage(percentage)?; - - self.slash(api, &mut staking_storage, block, &validator, percentage)?; - - Ok(AppResponse::default()) - } - StakingSudo::ProcessQueue {} => { - loop { - let mut staking_storage = prefixed(storage, NAMESPACE_STAKING); - let front = UNBONDING_QUEUE.front(&staking_storage)?; - match front { - // assuming the queue is sorted by payout_at - Some((_, payout_at, _)) if payout_at <= block.time => { - // remove from queue - let (delegator, _, amount) = - UNBONDING_QUEUE.pop_front(&mut staking_storage)?.unwrap(); - - let staking_info = Self::get_staking_info(&staking_storage)?; - if amount > 0 { - router.execute( - api, - storage, - block, - self.module_addr.clone(), - BankMsg::Send { - to_address: delegator.into_string(), - amount: vec![coin(amount, &staking_info.bonded_denom)], - } - .into(), - )?; - } - } - _ => break, - } - } - Ok(AppResponse::default()) - } - } - } - - fn query( - &self, - api: &dyn Api, - storage: &dyn Storage, - _querier: &dyn Querier, - block: &BlockInfo, - request: StakingQuery, - ) -> AnyResult { - let staking_storage = prefixed_read(storage, NAMESPACE_STAKING); - match request { - StakingQuery::BondedDenom {} => Ok(to_binary(&BondedDenomResponse { - denom: Self::get_staking_info(&staking_storage)?.bonded_denom, - })?), - StakingQuery::AllDelegations { delegator } => { - let delegator = api.addr_validate(&delegator)?; - let validators = self.get_validators(&staking_storage)?; - - let res: AnyResult> = validators - .into_iter() - .filter_map(|validator| { - let delegator = delegator.clone(); - let amount = self - .get_stake( - &staking_storage, - &delegator, - &Addr::unchecked(&validator.address), - ) - .transpose()?; - - Some(amount.map(|amount| Delegation { - delegator, - validator: validator.address, - amount, - })) - }) - .collect(); - - Ok(to_binary(&AllDelegationsResponse { delegations: res? })?) - } - StakingQuery::Delegation { - delegator, - validator, - } => { - let validator_addr = Addr::unchecked(&validator); - let validator_obj = match self.get_validator(&staking_storage, &validator_addr)? { - Some(validator) => validator, - None => bail!("non-existent validator {}", validator), - }; - let delegator = api.addr_validate(&delegator)?; - - let shares = match STAKES.load(&staking_storage, (&delegator, &validator_addr)) { - Ok(stakes) => stakes, - Err(_) => { - let response = DelegationResponse { delegation: None }; - return Ok(to_binary(&response)?); - } - }; - let validator_info = VALIDATOR_INFO.load(&staking_storage, &validator_addr)?; - let reward = Self::get_rewards_internal( - &staking_storage, - block, - &shares, - &validator_obj, - &validator_info, - )?; - let staking_info = Self::get_staking_info(&staking_storage)?; - let amount = coin( - (shares.stake * Uint128::new(1)).u128(), - staking_info.bonded_denom, - ); - let full_delegation_response = DelegationResponse { - delegation: Some(FullDelegation { - delegator, - validator, - amount: amount.clone(), - can_redelegate: amount, // TODO: not implemented right now - accumulated_rewards: if reward.amount.is_zero() { - vec![] - } else { - vec![reward] - }, - }), - }; - - let res = to_binary(&full_delegation_response)?; - Ok(res) - } - StakingQuery::AllValidators {} => Ok(to_binary(&AllValidatorsResponse { - validators: self.get_validators(&staking_storage)?, - })?), - StakingQuery::Validator { address } => Ok(to_binary(&ValidatorResponse { - validator: self.get_validator(&staking_storage, &Addr::unchecked(address))?, - })?), - q => bail!("Unsupported staking sudo message: {:?}", q), - } - } -} - -#[derive(Default)] -pub struct DistributionKeeper {} - -impl DistributionKeeper { - pub fn new() -> Self { - DistributionKeeper {} - } - - /// Removes all rewards from the given (delegator, validator) pair and returns the amount - pub fn remove_rewards( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - block: &BlockInfo, - delegator: &Addr, - validator: &Addr, - ) -> AnyResult { - let mut staking_storage = prefixed(storage, NAMESPACE_STAKING); - // update the validator and staker rewards - StakeKeeper::update_rewards(api, &mut staking_storage, block, validator)?; - - // load updated rewards for delegator - let mut shares = STAKES.load(&staking_storage, (delegator, validator))?; - let rewards = Uint128::new(1) * shares.rewards; // convert to Uint128 - - // remove rewards from delegator - shares.rewards = Decimal::zero(); - STAKES.save(&mut staking_storage, (delegator, validator), &shares)?; - - Ok(rewards) - } -} - -impl Distribution for DistributionKeeper {} - -impl Module for DistributionKeeper { - type ExecT = DistributionMsg; - type QueryT = Empty; - type SudoT = Empty; - - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - sender: Addr, - msg: DistributionMsg, - ) -> AnyResult { - match msg { - DistributionMsg::WithdrawDelegatorReward { validator } => { - let validator_addr = api.addr_validate(&validator)?; - - let rewards = self.remove_rewards(api, storage, block, &sender, &validator_addr)?; - - let staking_storage = prefixed_read(storage, NAMESPACE_STAKING); - let staking_info = StakeKeeper::get_staking_info(&staking_storage)?; - // directly mint rewards to delegator - router.sudo( - api, - storage, - block, - BankSudo::Mint { - to_address: sender.to_string(), - amount: vec![Coin { - amount: rewards, - denom: staking_info.bonded_denom.clone(), - }], - } - .into(), - )?; - - let events = vec![Event::new("withdraw_delegator_reward") - .add_attribute("validator", &validator) - .add_attribute("sender", &sender) - .add_attribute( - "amount", - format!("{}{}", rewards, staking_info.bonded_denom), - )]; - Ok(AppResponse { events, data: None }) - } - m => bail!("Unsupported distribution message: {:?}", m), - } - } - - fn sudo( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - _msg: Empty, - ) -> AnyResult { - bail!("Something went wrong - Distribution doesn't have sudo messages") - } - - fn query( - &self, - _api: &dyn Api, - _storage: &dyn Storage, - _querier: &dyn Querier, - _block: &BlockInfo, - _request: Empty, - ) -> AnyResult { - bail!("Something went wrong - Distribution doesn't have query messages") - } -} - -#[cfg(test)] -mod test { - use crate::{app::MockRouter, BankKeeper, FailingModule, Router, WasmKeeper}; - - use super::*; - - use cosmwasm_std::{ - from_slice, - testing::{mock_env, MockApi, MockStorage}, - BalanceResponse, BankQuery, - }; - - /// Type alias for default build `Router` to make its reference in typical scenario - type BasicRouter = Router< - BankKeeper, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, - >; - - fn mock_router() -> BasicRouter { - Router { - wasm: WasmKeeper::new(), - bank: BankKeeper::new(), - custom: FailingModule::new(), - staking: StakeKeeper::new(), - distribution: DistributionKeeper::new(), - } - } - - fn setup_test_env( - apr: Decimal, - validator_commission: Decimal, - ) -> (MockApi, MockStorage, BasicRouter, BlockInfo, Addr) { - let api = MockApi::default(); - let router = mock_router(); - let mut store = MockStorage::new(); - let block = mock_env().block; - - let validator = api.addr_validate("testvaloper1").unwrap(); - - router - .staking - .setup( - &mut store, - StakingInfo { - bonded_denom: "TOKEN".to_string(), - unbonding_time: 60, - apr, - }, - ) - .unwrap(); - - // add validator - let valoper1 = Validator { - address: "testvaloper1".to_string(), - commission: validator_commission, - max_commission: Decimal::percent(100), - max_change_rate: Decimal::percent(1), - }; - router - .staking - .add_validator(&api, &mut store, &block, valoper1) - .unwrap(); - - (api, store, router, block, validator) - } - - #[test] - fn add_get_validators() { - let api = MockApi::default(); - let mut store = MockStorage::new(); - let stake = StakeKeeper::default(); - let block = mock_env().block; - - // add validator - let valoper1 = Validator { - address: "testvaloper1".to_string(), - commission: Decimal::percent(10), - max_commission: Decimal::percent(20), - max_change_rate: Decimal::percent(1), - }; - stake - .add_validator(&api, &mut store, &block, valoper1.clone()) - .unwrap(); - - // get it - let staking_storage = prefixed_read(&store, NAMESPACE_STAKING); - let val = stake - .get_validator( - &staking_storage, - &api.addr_validate("testvaloper1").unwrap(), - ) - .unwrap() - .unwrap(); - assert_eq!(val, valoper1); - - // try to add with same address - let valoper1_fake = Validator { - address: "testvaloper1".to_string(), - commission: Decimal::percent(1), - max_commission: Decimal::percent(10), - max_change_rate: Decimal::percent(100), - }; - stake - .add_validator(&api, &mut store, &block, valoper1_fake) - .unwrap_err(); - - // should still be original value - let staking_storage = prefixed_read(&store, NAMESPACE_STAKING); - let val = stake - .get_validator( - &staking_storage, - &api.addr_validate("testvaloper1").unwrap(), - ) - .unwrap() - .unwrap(); - assert_eq!(val, valoper1); - } - - #[test] - fn validator_slashing() { - let api = MockApi::default(); - let router = MockRouter::default(); - let mut store = MockStorage::new(); - let stake = StakeKeeper::new(); - let block = mock_env().block; - - let delegator = Addr::unchecked("delegator"); - let validator = api.addr_validate("testvaloper1").unwrap(); - - // add validator - let valoper1 = Validator { - address: "testvaloper1".to_string(), - commission: Decimal::percent(10), - max_commission: Decimal::percent(20), - max_change_rate: Decimal::percent(1), - }; - stake - .add_validator(&api, &mut store, &block, valoper1) - .unwrap(); - - // stake 100 tokens - let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING); - stake - .add_stake( - &api, - &mut staking_storage, - &block, - &delegator, - &validator, - coin(100, "TOKEN"), - ) - .unwrap(); - - // slash 50% - stake - .sudo( - &api, - &mut store, - &router, - &block, - StakingSudo::Slash { - validator: "testvaloper1".to_string(), - percentage: Decimal::percent(50), - }, - ) - .unwrap(); - - // check stake - let staking_storage = prefixed(&mut store, NAMESPACE_STAKING); - let stake_left = stake - .get_stake(&staking_storage, &delegator, &validator) - .unwrap(); - assert_eq!( - stake_left.unwrap().amount.u128(), - 50, - "should have slashed 50%" - ); - - // slash all - stake - .sudo( - &api, - &mut store, - &router, - &block, - StakingSudo::Slash { - validator: "testvaloper1".to_string(), - percentage: Decimal::percent(100), - }, - ) - .unwrap(); - - // check stake - let staking_storage = prefixed(&mut store, NAMESPACE_STAKING); - let stake_left = stake - .get_stake(&staking_storage, &delegator, &validator) - .unwrap(); - assert_eq!(stake_left, None, "should have slashed whole stake"); - } - - #[test] - fn rewards_work_for_single_delegator() { - let (api, mut store, router, mut block, validator) = - setup_test_env(Decimal::percent(10), Decimal::percent(10)); - let stake = &router.staking; - let distr = &router.distribution; - let delegator = Addr::unchecked("delegator"); - - let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING); - // stake 200 tokens - stake - .add_stake( - &api, - &mut staking_storage, - &block, - &delegator, - &validator, - coin(200, "TOKEN"), - ) - .unwrap(); - - // wait 1/2 year - block.time = block.time.plus_seconds(60 * 60 * 24 * 365 / 2); - - // should now have 200 * 10% / 2 - 10% commission = 9 tokens reward - let rewards = stake - .get_rewards(&store, &block, &delegator, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 9, "should have 9 tokens reward"); - - // withdraw rewards - distr - .execute( - &api, - &mut store, - &router, - &block, - delegator.clone(), - DistributionMsg::WithdrawDelegatorReward { - validator: validator.to_string(), - }, - ) - .unwrap(); - - // should have no rewards left - let rewards = stake - .get_rewards(&store, &block, &delegator, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 0); - - // wait another 1/2 year - block.time = block.time.plus_seconds(60 * 60 * 24 * 365 / 2); - // should now have 9 tokens again - let rewards = stake - .get_rewards(&store, &block, &delegator, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 9); - } - - #[test] - fn rewards_work_for_multiple_delegators() { - let (api, mut store, router, mut block, validator) = - setup_test_env(Decimal::percent(10), Decimal::percent(10)); - let stake = &router.staking; - let distr = &router.distribution; - let bank = &router.bank; - let delegator1 = Addr::unchecked("delegator1"); - let delegator2 = Addr::unchecked("delegator2"); - - let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING); - - // add 100 stake to delegator1 and 200 to delegator2 - stake - .add_stake( - &api, - &mut staking_storage, - &block, - &delegator1, - &validator, - coin(100, "TOKEN"), - ) - .unwrap(); - stake - .add_stake( - &api, - &mut staking_storage, - &block, - &delegator2, - &validator, - coin(200, "TOKEN"), - ) - .unwrap(); - - // wait 1 year - block.time = block.time.plus_seconds(60 * 60 * 24 * 365); - - // delegator1 should now have 100 * 10% - 10% commission = 9 tokens - let rewards = stake - .get_rewards(&store, &block, &delegator1, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 9); - - // delegator2 should now have 200 * 10% - 10% commission = 18 tokens - let rewards = stake - .get_rewards(&store, &block, &delegator2, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 18); - - // delegator1 stakes 100 more - let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING); - stake - .add_stake( - &api, - &mut staking_storage, - &block, - &delegator1, - &validator, - coin(100, "TOKEN"), - ) - .unwrap(); - - // wait another year - block.time = block.time.plus_seconds(60 * 60 * 24 * 365); - - // delegator1 should now have 9 + 200 * 10% - 10% commission = 27 tokens - let rewards = stake - .get_rewards(&store, &block, &delegator1, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 27); - - // delegator2 should now have 18 + 200 * 10% - 10% commission = 36 tokens - let rewards = stake - .get_rewards(&store, &block, &delegator2, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 36); - - // delegator2 unstakes 100 (has 100 left after that) - let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING); - stake - .remove_stake( - &api, - &mut staking_storage, - &block, - &delegator2, - &validator, - coin(100, "TOKEN"), - ) - .unwrap(); - - // and delegator1 withdraws rewards - distr - .execute( - &api, - &mut store, - &router, - &block, - delegator1.clone(), - DistributionMsg::WithdrawDelegatorReward { - validator: validator.to_string(), - }, - ) - .unwrap(); - - let balance: BalanceResponse = from_slice( - &bank - .query( - &api, - &store, - &router.querier(&api, &store, &block), - &block, - BankQuery::Balance { - address: delegator1.to_string(), - denom: "TOKEN".to_string(), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - balance.amount.amount.u128(), - 27, - "withdraw should change bank balance" - ); - let rewards = stake - .get_rewards(&store, &block, &delegator1, &validator) - .unwrap() - .unwrap(); - assert_eq!( - rewards.amount.u128(), - 0, - "withdraw should reduce rewards to 0" - ); - - // wait another year - block.time = block.time.plus_seconds(60 * 60 * 24 * 365); - - // delegator1 should now have 0 + 200 * 10% - 10% commission = 18 tokens - let rewards = stake - .get_rewards(&store, &block, &delegator1, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 18); - - // delegator2 should now have 36 + 100 * 10% - 10% commission = 45 tokens - let rewards = stake - .get_rewards(&store, &block, &delegator2, &validator) - .unwrap() - .unwrap(); - assert_eq!(rewards.amount.u128(), 45); - } - - mod msg { - use cosmwasm_std::{from_slice, Addr, BondedDenomResponse, Decimal, StakingQuery}; - use serde::de::DeserializeOwned; - - use super::*; - - // shortens tests a bit - struct TestEnv { - api: MockApi, - store: MockStorage, - router: BasicRouter, - block: BlockInfo, - } - - impl TestEnv { - fn wrap(tuple: (MockApi, MockStorage, BasicRouter, BlockInfo, Addr)) -> (Self, Addr) { - ( - Self { - api: tuple.0, - store: tuple.1, - router: tuple.2, - block: tuple.3, - }, - tuple.4, - ) - } - } - - fn execute_stake( - env: &mut TestEnv, - sender: Addr, - msg: StakingMsg, - ) -> AnyResult { - env.router.staking.execute( - &env.api, - &mut env.store, - &env.router, - &env.block, - sender, - msg, - ) - } - - fn query_stake(env: &TestEnv, msg: StakingQuery) -> AnyResult { - Ok(from_slice(&env.router.staking.query( - &env.api, - &env.store, - &env.router.querier(&env.api, &env.store, &env.block), - &env.block, - msg, - )?)?) - } - - fn execute_distr( - env: &mut TestEnv, - sender: Addr, - msg: DistributionMsg, - ) -> AnyResult { - env.router.distribution.execute( - &env.api, - &mut env.store, - &env.router, - &env.block, - sender, - msg, - ) - } - - fn query_bank(env: &TestEnv, msg: BankQuery) -> AnyResult { - Ok(from_slice(&env.router.bank.query( - &env.api, - &env.store, - &env.router.querier(&env.api, &env.store, &env.block), - &env.block, - msg, - )?)?) - } - - fn assert_balances(env: &TestEnv, balances: impl IntoIterator) { - for (addr, amount) in balances { - let balance: BalanceResponse = query_bank( - env, - BankQuery::Balance { - address: addr.to_string(), - denom: "TOKEN".to_string(), - }, - ) - .unwrap(); - assert_eq!(balance.amount.amount.u128(), amount); - } - } - - #[test] - fn execute() { - // test all execute msgs - let (mut test_env, validator1) = - TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10))); - - let delegator1 = Addr::unchecked("delegator1"); - - // fund delegator1 account - test_env - .router - .bank - .init_balance(&mut test_env.store, &delegator1, vec![coin(1000, "TOKEN")]) - .unwrap(); - - // add second validator - let validator2 = Addr::unchecked("validator2"); - test_env - .router - .staking - .add_validator( - &test_env.api, - &mut test_env.store, - &test_env.block, - Validator { - address: validator2.to_string(), - commission: Decimal::zero(), - max_commission: Decimal::percent(20), - max_change_rate: Decimal::percent(1), - }, - ) - .unwrap(); - - // delegate 100 tokens to validator1 - execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Delegate { - validator: validator1.to_string(), - amount: coin(100, "TOKEN"), - }, - ) - .unwrap(); - - // should now have 100 tokens less - assert_balances(&test_env, vec![(delegator1.clone(), 900)]); - - // wait a year - test_env.block.time = test_env.block.time.plus_seconds(60 * 60 * 24 * 365); - - // withdraw rewards - execute_distr( - &mut test_env, - delegator1.clone(), - DistributionMsg::WithdrawDelegatorReward { - validator: validator1.to_string(), - }, - ) - .unwrap(); - - // redelegate to validator2 - execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Redelegate { - src_validator: validator1.to_string(), - dst_validator: validator2.to_string(), - amount: coin(100, "TOKEN"), - }, - ) - .unwrap(); - - // should have same amount as before - assert_balances( - &test_env, - vec![(delegator1.clone(), 900 + 100 / 10 * 9 / 10)], - ); - - let delegations: AllDelegationsResponse = query_stake( - &test_env, - StakingQuery::AllDelegations { - delegator: delegator1.to_string(), - }, - ) - .unwrap(); - assert_eq!( - delegations.delegations, - [Delegation { - delegator: delegator1.clone(), - validator: validator2.to_string(), - amount: coin(100, "TOKEN"), - }] - ); - - // undelegate all tokens - execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Undelegate { - validator: validator2.to_string(), - amount: coin(100, "TOKEN"), - }, - ) - .unwrap(); - - // wait for unbonding period (60 seconds in default config) - test_env.block.time = test_env.block.time.plus_seconds(60); - - // need to manually cause queue to get processed - test_env - .router - .staking - .sudo( - &test_env.api, - &mut test_env.store, - &test_env.router, - &test_env.block, - StakingSudo::ProcessQueue {}, - ) - .unwrap(); - - // check bank balance - assert_balances( - &test_env, - vec![(delegator1.clone(), 1000 + 100 / 10 * 9 / 10)], - ); - } - - #[test] - fn cannot_steal() { - let (mut test_env, validator1) = - TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10))); - - let delegator1 = Addr::unchecked("delegator1"); - - // fund delegator1 account - test_env - .router - .bank - .init_balance(&mut test_env.store, &delegator1, vec![coin(100, "TOKEN")]) - .unwrap(); - - // delegate 100 tokens to validator1 - execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Delegate { - validator: validator1.to_string(), - amount: coin(100, "TOKEN"), - }, - ) - .unwrap(); - - // undelegate more tokens than we have - let e = execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Undelegate { - validator: validator1.to_string(), - amount: coin(200, "TOKEN"), - }, - ) - .unwrap_err(); - - assert_eq!(e.to_string(), "insufficient stake"); - - // add second validator - let validator2 = Addr::unchecked("validator2"); - test_env - .router - .staking - .add_validator( - &test_env.api, - &mut test_env.store, - &test_env.block, - Validator { - address: validator2.to_string(), - commission: Decimal::zero(), - max_commission: Decimal::percent(20), - max_change_rate: Decimal::percent(1), - }, - ) - .unwrap(); - - // redelegate more tokens than we have - let e = execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Redelegate { - src_validator: validator1.to_string(), - dst_validator: validator2.to_string(), - amount: coin(200, "TOKEN"), - }, - ) - .unwrap_err(); - assert_eq!(e.to_string(), "insufficient stake"); - } - - #[test] - fn denom_validation() { - let (mut test_env, validator) = - TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10))); - - let delegator1 = Addr::unchecked("delegator1"); - - // fund delegator1 account - test_env - .router - .bank - .init_balance(&mut test_env.store, &delegator1, vec![coin(100, "FAKE")]) - .unwrap(); - - // try to delegate 100 to validator1 - let e = execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Delegate { - validator: validator.to_string(), - amount: coin(100, "FAKE"), - }, - ) - .unwrap_err(); - - assert_eq!( - e.to_string(), - "cannot delegate coins of denominator FAKE, only of TOKEN", - ); - } - - #[test] - fn cannot_slash_nonexistent() { - let (mut test_env, _) = - TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10))); - - let delegator1 = Addr::unchecked("delegator1"); - - // fund delegator1 account - test_env - .router - .bank - .init_balance(&mut test_env.store, &delegator1, vec![coin(100, "FAKE")]) - .unwrap(); - - // try to delegate 100 to validator1 - let e = test_env - .router - .staking - .sudo( - &test_env.api, - &mut test_env.store, - &test_env.router, - &test_env.block, - StakingSudo::Slash { - validator: "nonexistingvaloper".to_string(), - percentage: Decimal::percent(50), - }, - ) - .unwrap_err(); - assert_eq!(e.to_string(), "validator nonexistingvaloper not found"); - } - - #[test] - fn zero_staking_allowed() { - let (mut test_env, validator) = - TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10))); - - let delegator = Addr::unchecked("delegator1"); - - // delegate 0 - execute_stake( - &mut test_env, - delegator.clone(), - StakingMsg::Delegate { - validator: validator.to_string(), - amount: coin(0, "TOKEN"), - }, - ) - .unwrap(); - - // undelegate 0 - execute_stake( - &mut test_env, - delegator, - StakingMsg::Undelegate { - validator: validator.to_string(), - amount: coin(0, "TOKEN"), - }, - ) - .unwrap(); - } - - #[test] - fn query_staking() { - // run all staking queries - let (mut test_env, validator1) = - TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10))); - let delegator1 = Addr::unchecked("delegator1"); - let delegator2 = Addr::unchecked("delegator2"); - - // init balances - test_env - .router - .bank - .init_balance(&mut test_env.store, &delegator1, vec![coin(260, "TOKEN")]) - .unwrap(); - test_env - .router - .bank - .init_balance(&mut test_env.store, &delegator2, vec![coin(150, "TOKEN")]) - .unwrap(); - - // add another validator - let validator2 = test_env.api.addr_validate("testvaloper2").unwrap(); - let valoper2 = Validator { - address: "testvaloper2".to_string(), - commission: Decimal::percent(0), - max_commission: Decimal::percent(1), - max_change_rate: Decimal::percent(1), - }; - test_env - .router - .staking - .add_validator( - &test_env.api, - &mut test_env.store, - &test_env.block, - valoper2.clone(), - ) - .unwrap(); - - // query validators - let valoper1: ValidatorResponse = query_stake( - &test_env, - StakingQuery::Validator { - address: validator1.to_string(), - }, - ) - .unwrap(); - let validators: AllValidatorsResponse = - query_stake(&test_env, StakingQuery::AllValidators {}).unwrap(); - assert_eq!( - validators.validators, - [valoper1.validator.unwrap(), valoper2] - ); - // query non-existent validator - let response = query_stake::( - &test_env, - StakingQuery::Validator { - address: "notvaloper".to_string(), - }, - ) - .unwrap(); - assert_eq!(response.validator, None); - - // query bonded denom - let response: BondedDenomResponse = - query_stake(&test_env, StakingQuery::BondedDenom {}).unwrap(); - assert_eq!(response.denom, "TOKEN"); - - // delegate some tokens with delegator1 and delegator2 - execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Delegate { - validator: validator1.to_string(), - amount: coin(100, "TOKEN"), - }, - ) - .unwrap(); - execute_stake( - &mut test_env, - delegator1.clone(), - StakingMsg::Delegate { - validator: validator2.to_string(), - amount: coin(160, "TOKEN"), - }, - ) - .unwrap(); - execute_stake( - &mut test_env, - delegator2.clone(), - StakingMsg::Delegate { - validator: validator1.to_string(), - amount: coin(150, "TOKEN"), - }, - ) - .unwrap(); - - // query all delegations - let response1: AllDelegationsResponse = query_stake( - &test_env, - StakingQuery::AllDelegations { - delegator: delegator1.to_string(), - }, - ) - .unwrap(); - assert_eq!( - response1.delegations, - vec![ - Delegation { - delegator: delegator1.clone(), - validator: validator1.to_string(), - amount: coin(100, "TOKEN"), - }, - Delegation { - delegator: delegator1.clone(), - validator: validator2.to_string(), - amount: coin(160, "TOKEN"), - }, - ] - ); - let response2: DelegationResponse = query_stake( - &test_env, - StakingQuery::Delegation { - delegator: delegator2.to_string(), - validator: validator1.to_string(), - }, - ) - .unwrap(); - assert_eq!( - response2.delegation.unwrap(), - FullDelegation { - delegator: delegator2.clone(), - validator: validator1.to_string(), - amount: coin(150, "TOKEN"), - accumulated_rewards: vec![], - can_redelegate: coin(150, "TOKEN"), - }, - ); - } - } -} diff --git a/packages/multi-test/src/test_helpers.rs b/packages/multi-test/src/test_helpers.rs deleted file mode 100644 index cb551197b..000000000 --- a/packages/multi-test/src/test_helpers.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![cfg(test)] -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cw_storage_plus::Item; - -pub mod contracts; - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct EmptyMsg {} - -/// This is just a demo place so we can test custom message handling -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -#[serde(rename = "snake_case")] -pub enum CustomMsg { - SetName { name: String }, - SetAge { age: u32 }, -} - -const COUNT: Item = Item::new("count"); diff --git a/packages/multi-test/src/test_helpers/contracts.rs b/packages/multi-test/src/test_helpers/contracts.rs deleted file mode 100644 index 68babbf84..000000000 --- a/packages/multi-test/src/test_helpers/contracts.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Module for simple contracts to be used in tests - -pub mod caller; -pub mod echo; -pub mod error; -pub mod hackatom; -pub mod payout; -pub mod reflect; diff --git a/packages/multi-test/src/test_helpers/contracts/caller.rs b/packages/multi-test/src/test_helpers/contracts/caller.rs deleted file mode 100644 index 92367a3de..000000000 --- a/packages/multi-test/src/test_helpers/contracts/caller.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::fmt; - -use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, SubMsg, WasmMsg}; -use schemars::JsonSchema; - -use crate::{test_helpers::EmptyMsg, Contract, ContractWrapper}; - -fn instantiate( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: EmptyMsg, -) -> Result { - Ok(Response::default()) -} - -fn execute( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: WasmMsg, -) -> Result { - let message = SubMsg::new(msg); - - Ok(Response::new().add_submessage(message)) -} - -fn query(_deps: Deps, _env: Env, _msg: EmptyMsg) -> Result { - Err(StdError::generic_err( - "query not implemented for the `caller` contract", - )) -} - -pub fn contract() -> Box> -where - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, -{ - let contract = ContractWrapper::new_with_empty(execute, instantiate, query); - Box::new(contract) -} diff --git a/packages/multi-test/src/test_helpers/contracts/echo.rs b/packages/multi-test/src/test_helpers/contracts/echo.rs deleted file mode 100644 index fb87602a6..000000000 --- a/packages/multi-test/src/test_helpers/contracts/echo.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Very simple echoing contract which just returns incomming string if any, but performming subcall of -//! given message to test response. -//! -//! Additionally it bypass all events and attributes send to it - -use cosmwasm_std::{ - to_binary, Attribute, Binary, Deps, DepsMut, Empty, Env, Event, MessageInfo, Reply, Response, - StdError, SubMsg, SubMsgResponse, SubMsgResult, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -use crate::{test_helpers::EmptyMsg, Contract, ContractWrapper}; -use schemars::JsonSchema; -use std::fmt::Debug; - -use cw_utils::{parse_execute_response_data, parse_instantiate_response_data}; -use derivative::Derivative; - -// Choosing a reply id less than ECHO_EXECUTE_BASE_ID indicates an Instantiate message reply by convention. -// An Execute message reply otherwise. -pub(crate) const EXECUTE_REPLY_BASE_ID: u64 = i64::MAX as u64; - -#[derive(Debug, Clone, Serialize, Deserialize, Derivative)] -#[derivative(Default(bound = "", new = "true"))] -pub struct Message -where - ExecC: Debug + PartialEq + Clone + JsonSchema + 'static, -{ - pub data: Option, - pub sub_msg: Vec>, - pub attributes: Vec, - pub events: Vec, -} - -// This can take some data... but happy to accept {} -#[derive(Debug, Clone, Serialize, Deserialize, Derivative)] -#[derivative(Default(bound = "", new = "true"))] -pub struct InitMessage -where - ExecC: Debug + PartialEq + Clone + JsonSchema + 'static, -{ - pub data: Option, - pub sub_msg: Option>>, -} - -#[allow(clippy::unnecessary_wraps)] -fn instantiate( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InitMessage, -) -> Result, StdError> -where - ExecC: Debug + PartialEq + Clone + JsonSchema + 'static, -{ - let mut res = Response::new(); - if let Some(data) = msg.data { - res = res.set_data(data.into_bytes()); - } - if let Some(msgs) = msg.sub_msg { - res = res.add_submessages(msgs); - } - Ok(res) -} - -#[allow(clippy::unnecessary_wraps)] -fn execute( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: Message, -) -> Result, StdError> -where - ExecC: Debug + PartialEq + Clone + JsonSchema + 'static, -{ - let mut resp = Response::new(); - - if let Some(data) = msg.data { - resp = resp.set_data(data.into_bytes()); - } - - Ok(resp - .add_submessages(msg.sub_msg) - .add_attributes(msg.attributes) - .add_events(msg.events)) -} - -fn query(_deps: Deps, _env: Env, msg: EmptyMsg) -> Result { - to_binary(&msg) -} - -#[allow(clippy::unnecessary_wraps)] -fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result, StdError> -where - ExecC: Debug + PartialEq + Clone + JsonSchema + 'static, -{ - let res = Response::new(); - if let Reply { - id, - result: SubMsgResult::Ok(SubMsgResponse { - data: Some(data), .. - }), - } = msg - { - // We parse out the WasmMsg::Execute wrapper... - // TODO: Handle all of Execute, Instantiate, and BankMsg replies differently. - let parsed_data = if id < EXECUTE_REPLY_BASE_ID { - parse_instantiate_response_data(data.as_slice()) - .map_err(|e| StdError::generic_err(e.to_string()))? - .data - } else { - parse_execute_response_data(data.as_slice()) - .map_err(|e| StdError::generic_err(e.to_string()))? - .data - }; - - if let Some(data) = parsed_data { - Ok(res.set_data(data)) - } else { - Ok(res) - } - } else { - Ok(res) - } -} - -pub fn contract() -> Box> { - let contract = ContractWrapper::new(execute::, instantiate::, query) - .with_reply(reply::); - Box::new(contract) -} - -pub fn custom_contract() -> Box> -where - C: Clone + Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, -{ - let contract = - ContractWrapper::new(execute::, instantiate::, query).with_reply(reply::); - Box::new(contract) -} diff --git a/packages/multi-test/src/test_helpers/contracts/error.rs b/packages/multi-test/src/test_helpers/contracts/error.rs deleted file mode 100644 index e707e80d6..000000000 --- a/packages/multi-test/src/test_helpers/contracts/error.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::fmt; - -use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError}; -use schemars::JsonSchema; - -use crate::{test_helpers::EmptyMsg, Contract, ContractWrapper}; - -fn instantiate_err( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: EmptyMsg, -) -> Result { - Err(StdError::generic_err("Init failed")) -} - -fn instantiate_ok( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: EmptyMsg, -) -> Result { - Ok(Response::default()) -} - -fn execute( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: EmptyMsg, -) -> Result { - Err(StdError::generic_err("Handle failed")) -} - -fn query(_deps: Deps, _env: Env, _msg: EmptyMsg) -> Result { - Err(StdError::generic_err("Query failed")) -} - -pub fn contract(instantiable: bool) -> Box> -where - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, -{ - let contract = if instantiable { - ContractWrapper::new_with_empty(execute, instantiate_ok, query) - } else { - ContractWrapper::new_with_empty(execute, instantiate_err, query) - }; - Box::new(contract) -} diff --git a/packages/multi-test/src/test_helpers/contracts/hackatom.rs b/packages/multi-test/src/test_helpers/contracts/hackatom.rs deleted file mode 100644 index 7ceabbd61..000000000 --- a/packages/multi-test/src/test_helpers/contracts/hackatom.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Simplified contract which when executed releases the funds to beneficiary - -use cosmwasm_std::{ - to_binary, BankMsg, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, -}; -use cw_storage_plus::Item; -use serde::{Deserialize, Serialize}; - -use crate::{test_helpers::EmptyMsg, Contract, ContractWrapper}; -use schemars::JsonSchema; -use std::fmt; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InstantiateMsg { - pub beneficiary: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MigrateMsg { - // just use some other string so we see there are other types - pub new_guy: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - // returns InstantiateMsg - Beneficiary {}, -} - -const HACKATOM: Item = Item::new("hackatom"); - -fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - HACKATOM.save(deps.storage, &msg)?; - Ok(Response::default()) -} - -fn execute( - deps: DepsMut, - env: Env, - _info: MessageInfo, - _msg: EmptyMsg, -) -> Result { - let init = HACKATOM.load(deps.storage)?; - let balance = deps.querier.query_all_balances(env.contract.address)?; - - let resp = Response::new().add_message(BankMsg::Send { - to_address: init.beneficiary, - amount: balance, - }); - - Ok(resp) -} - -fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { - match msg { - QueryMsg::Beneficiary {} => { - let res = HACKATOM.load(deps.storage)?; - to_binary(&res) - } - } -} - -fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { - HACKATOM.update::<_, StdError>(deps.storage, |mut state| { - state.beneficiary = msg.new_guy; - Ok(state) - })?; - let resp = Response::new().add_attribute("migrate", "successful"); - Ok(resp) -} - -pub fn contract() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query).with_migrate(migrate); - Box::new(contract) -} - -#[allow(dead_code)] -pub fn custom_contract() -> Box> -where - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, -{ - let contract = - ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate_empty(migrate); - Box::new(contract) -} diff --git a/packages/multi-test/src/test_helpers/contracts/payout.rs b/packages/multi-test/src/test_helpers/contracts/payout.rs deleted file mode 100644 index 4e9f1c1b9..000000000 --- a/packages/multi-test/src/test_helpers/contracts/payout.rs +++ /dev/null @@ -1,90 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::fmt; - -use cosmwasm_std::{ - to_binary, BankMsg, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Response, StdError, -}; -use cw_storage_plus::Item; - -use crate::contracts::{Contract, ContractWrapper}; -use crate::test_helpers::{EmptyMsg, COUNT}; - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct InstantiateMessage { - pub payout: Coin, -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct SudoMsg { - pub set_count: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum QueryMsg { - Count {}, - Payout {}, -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct CountResponse { - pub count: u32, -} - -const PAYOUT: Item = Item::new("payout"); - -fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMessage, -) -> Result { - PAYOUT.save(deps.storage, &msg)?; - COUNT.save(deps.storage, &1)?; - Ok(Response::default()) -} - -fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - _msg: EmptyMsg, -) -> Result { - // always try to payout what was set originally - let payout = PAYOUT.load(deps.storage)?; - let msg = BankMsg::Send { - to_address: info.sender.into(), - amount: vec![payout.payout], - }; - Ok(Response::new() - .add_message(msg) - .add_attribute("action", "payout")) -} - -fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result { - COUNT.save(deps.storage, &msg.set_count)?; - Ok(Response::default()) -} - -fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { - match msg { - QueryMsg::Count {} => { - let count = COUNT.load(deps.storage)?; - let res = CountResponse { count }; - to_binary(&res) - } - QueryMsg::Payout {} => { - let payout = PAYOUT.load(deps.storage)?; - to_binary(&payout) - } - } -} - -pub fn contract() -> Box> -where - C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, -{ - let contract = - ContractWrapper::new_with_empty(execute, instantiate, query).with_sudo_empty(sudo); - Box::new(contract) -} diff --git a/packages/multi-test/src/test_helpers/contracts/reflect.rs b/packages/multi-test/src/test_helpers/contracts/reflect.rs deleted file mode 100644 index 11a107e8b..000000000 --- a/packages/multi-test/src/test_helpers/contracts/reflect.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Reply, Response, StdError, SubMsg, -}; -use cw_storage_plus::Map; - -use crate::contracts::{Contract, ContractWrapper}; -use crate::test_helpers::contracts::payout; -use crate::test_helpers::{CustomMsg, EmptyMsg, COUNT}; - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct Message { - pub messages: Vec>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum QueryMsg { - Count {}, - Reply { id: u64 }, -} - -const REFLECT: Map = Map::new("reflect"); - -fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: EmptyMsg, -) -> Result, StdError> { - COUNT.save(deps.storage, &0)?; - Ok(Response::default()) -} - -fn execute( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: Message, -) -> Result, StdError> { - COUNT.update::<_, StdError>(deps.storage, |old| Ok(old + 1))?; - - Ok(Response::new().add_submessages(msg.messages)) -} - -fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { - match msg { - QueryMsg::Count {} => { - let count = COUNT.load(deps.storage)?; - let res = payout::CountResponse { count }; - to_binary(&res) - } - QueryMsg::Reply { id } => { - let reply = REFLECT.load(deps.storage, id)?; - to_binary(&reply) - } - } -} - -fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result, StdError> { - REFLECT.save(deps.storage, msg.id, &msg)?; - // add custom event here to test - let event = Event::new("custom") - .add_attribute("from", "reply") - .add_attribute("to", "test"); - Ok(Response::new().add_event(event)) -} - -pub fn contract() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query).with_reply(reply); - Box::new(contract) -} diff --git a/packages/multi-test/src/transactions.rs b/packages/multi-test/src/transactions.rs deleted file mode 100644 index 7b9c5b598..000000000 --- a/packages/multi-test/src/transactions.rs +++ /dev/null @@ -1,589 +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}; - -use cosmwasm_std::Storage; -#[cfg(feature = "iterator")] -use cosmwasm_std::{Order, Record}; - -use anyhow::Result as AnyResult; - -#[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 fn transactional(base: &mut dyn Storage, action: F) -> AnyResult -where - F: FnOnce(&mut dyn Storage, &dyn Storage) -> AnyResult, -{ - let mut cache = StorageTransaction::new(base); - let res = action(&mut cache, base)?; - cache.prepare().commit(base); - Ok(res) -} - -pub struct StorageTransaction<'a> { - /// read-only access to backing storage - storage: &'a dyn Storage, - /// 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> StorageTransaction<'a> { - pub fn new(storage: &'a dyn Storage) -> 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 - } -} - -impl<'a> Storage for StorageTransaction<'a> { - 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), - } - } - - 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); - } - - #[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) - } -} - -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 dyn Storage) { - 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 dyn Storage) { - 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 = Record; - - 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, - } - } -} - -#[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 std::cell::RefCell; - use std::ops::{Deref, DerefMut}; - - use cosmwasm_std::MemoryStorage; - - #[test] - fn wrap_storage() { - let mut store = MemoryStorage::new(); - let mut wrap = StorageTransaction::new(&store); - wrap.set(b"foo", b"bar"); - - assert_eq!(None, store.get(b"foo")); - wrap.prepare().commit(&mut store); - assert_eq!(Some(b"bar".to_vec()), store.get(b"foo")); - } - - #[test] - fn wrap_ref_cell() { - let store = RefCell::new(MemoryStorage::new()); - let ops = { - let refer = store.borrow(); - let mut wrap = StorageTransaction::new(refer.deref()); - wrap.set(b"foo", b"bar"); - assert_eq!(None, store.borrow().get(b"foo")); - wrap.prepare() - }; - ops.commit(store.borrow_mut().deref_mut()); - assert_eq!(Some(b"bar".to_vec()), store.borrow().get(b"foo")); - } - - #[test] - fn wrap_box_storage() { - let mut store: Box = Box::new(MemoryStorage::new()); - let mut wrap = StorageTransaction::new(store.as_ref()); - wrap.set(b"foo", b"bar"); - - assert_eq!(None, store.get(b"foo")); - wrap.prepare().commit(store.as_mut()); - assert_eq!(Some(b"bar".to_vec()), store.get(b"foo")); - } - - #[test] - fn wrap_box_dyn_storage() { - let mut store: Box = Box::new(MemoryStorage::new()); - let mut wrap = StorageTransaction::new(store.as_ref()); - wrap.set(b"foo", b"bar"); - - assert_eq!(None, store.get(b"foo")); - wrap.prepare().commit(store.as_mut()); - assert_eq!(Some(b"bar".to_vec()), store.get(b"foo")); - } - - #[test] - fn wrap_ref_cell_dyn_storage() { - let inner: Box = Box::new(MemoryStorage::new()); - let store = RefCell::new(inner); - // Tricky but working - // 1. we cannot inline StorageTransaction::new(store.borrow().as_ref()) as Ref must outlive StorageTransaction - // 2. we cannot call ops.commit() until refer is out of scope - borrow_mut() and borrow() on the same object - // This can work with some careful scoping, this provides a good reference - let ops = { - let refer = store.borrow(); - let mut wrap = StorageTransaction::new(refer.as_ref()); - wrap.set(b"foo", b"bar"); - - assert_eq!(None, store.borrow().get(b"foo")); - wrap.prepare() - }; - ops.commit(store.borrow_mut().as_mut()); - assert_eq!(Some(b"bar".to_vec()), store.borrow().get(b"foo")); - } - - #[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 = Box::new(MemoryStorage::new()); - let mut check = StorageTransaction::new(base.as_ref()); - 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(base.as_mut()); - 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 = Box::new(MemoryStorage::new()); - base.set(b"foo", b"bar"); - let mut check = StorageTransaction::new(base.as_ref()); - 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(base.as_mut()); - 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 = Box::new(MemoryStorage::new()); - base.set(b"foo", b"bar"); - base.set(b"food", b"bank"); - let mut check = StorageTransaction::new(base.as_ref()); - check.remove(b"food"); - iterator_test_suite(&mut check); - } - - #[test] - fn commit_writes_through() { - let mut base = Box::new(MemoryStorage::new()); - base.set(b"foo", b"bar"); - - let mut check = StorageTransaction::new(base.as_ref()); - assert_eq!(check.get(b"foo"), Some(b"bar".to_vec())); - check.set(b"subtx", b"works"); - check.prepare().commit(base.as_mut()); - - 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 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); - } -} diff --git a/packages/multi-test/src/wasm.rs b/packages/multi-test/src/wasm.rs deleted file mode 100644 index bb0490332..000000000 --- a/packages/multi-test/src/wasm.rs +++ /dev/null @@ -1,1522 +0,0 @@ -use std::collections::HashMap; -use std::fmt; -use std::ops::Deref; - -use cosmwasm_std::{ - to_binary, Addr, Api, Attribute, BankMsg, Binary, BlockInfo, Coin, ContractInfo, - ContractInfoResponse, CustomQuery, Deps, DepsMut, Env, Event, MessageInfo, Order, Querier, - QuerierWrapper, Record, Reply, ReplyOn, Response, StdResult, Storage, SubMsg, SubMsgResponse, - SubMsgResult, TransactionInfo, WasmMsg, WasmQuery, -}; -use prost::Message; -use schemars::JsonSchema; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; - -use cw_storage_plus::Map; - -use crate::app::{CosmosRouter, RouterQuerier}; -use crate::contracts::Contract; -use crate::error::Error; -use crate::executor::AppResponse; -use crate::prefixed_storage::{prefixed, prefixed_read, PrefixedStorage, ReadonlyPrefixedStorage}; -use crate::transactions::transactional; -use cosmwasm_std::testing::mock_wasmd_attr; - -use anyhow::{bail, Context, Result as AnyResult}; - -// Contract state is kept in Storage, separate from the contracts themselves -const CONTRACTS: Map<&Addr, ContractData> = Map::new("contracts"); - -pub const NAMESPACE_WASM: &[u8] = b"wasm"; -const CONTRACT_ATTR: &str = "_contract_addr"; - -#[derive(Clone, std::fmt::Debug, PartialEq, Eq, JsonSchema)] -pub struct WasmSudo { - pub contract_addr: Addr, - pub msg: Binary, -} - -impl WasmSudo { - pub fn new(contract_addr: &Addr, msg: &T) -> StdResult { - Ok(WasmSudo { - contract_addr: contract_addr.clone(), - msg: to_binary(msg)?, - }) - } -} - -/// Contract Data includes information about contract, equivalent of `ContractInfo` in wasmd -/// interface. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct ContractData { - /// Identifier of stored contract code - pub code_id: usize, - /// Address of account who initially instantiated the contract - pub creator: Addr, - /// Optional address of account who can execute migrations - pub admin: Option, - /// Metadata passed while contract instantiation - pub label: String, - /// Blockchain height in the moment of instantiating the contract - pub created: u64, -} - -pub trait Wasm { - /// Handles all WasmQuery requests - fn query( - &self, - api: &dyn Api, - storage: &dyn Storage, - querier: &dyn Querier, - block: &BlockInfo, - request: WasmQuery, - ) -> AnyResult; - - /// Handles all WasmMsg messages - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - sender: Addr, - msg: WasmMsg, - ) -> AnyResult; - - /// Admin interface, cannot be called via CosmosMsg - fn sudo( - &self, - api: &dyn Api, - contract_addr: Addr, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - msg: Binary, - ) -> AnyResult; -} - -pub struct WasmKeeper { - /// code is in-memory lookup that stands in for wasm code - /// this can only be edited on the WasmRouter, and just read in caches - codes: HashMap>>, - /// Just markers to make type elision fork when using it as `Wasm` trait - _p: std::marker::PhantomData, -} - -impl Default for WasmKeeper { - fn default() -> Self { - Self { - codes: HashMap::default(), - _p: std::marker::PhantomData, - } - } -} - -impl Wasm for WasmKeeper -where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static, -{ - fn query( - &self, - api: &dyn Api, - storage: &dyn Storage, - querier: &dyn Querier, - block: &BlockInfo, - request: WasmQuery, - ) -> AnyResult { - match request { - WasmQuery::Smart { contract_addr, msg } => { - let addr = api.addr_validate(&contract_addr)?; - self.query_smart(addr, api, storage, querier, block, msg.into()) - } - WasmQuery::Raw { contract_addr, key } => { - let addr = api.addr_validate(&contract_addr)?; - Ok(self.query_raw(addr, storage, &key)) - } - WasmQuery::ContractInfo { contract_addr } => { - let addr = api.addr_validate(&contract_addr)?; - let contract = self.load_contract(storage, &addr)?; - let mut res = ContractInfoResponse::new(contract.code_id as u64, contract.creator); - res.admin = contract.admin.map(|x| x.into()); - to_binary(&res).map_err(Into::into) - } - query => bail!(Error::UnsupportedWasmQuery(query)), - } - } - - fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - sender: Addr, - msg: WasmMsg, - ) -> AnyResult { - self.execute_wasm(api, storage, router, block, sender.clone(), msg.clone()) - .context(format!( - "error executing WasmMsg:\nsender: {}\n{:?}", - sender, msg - )) - } - - fn sudo( - &self, - api: &dyn Api, - contract: Addr, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - msg: Binary, - ) -> AnyResult { - let custom_event = Event::new("sudo").add_attribute(CONTRACT_ATTR, &contract); - - let res = self.call_sudo(contract.clone(), api, storage, router, block, msg.to_vec())?; - let (res, msgs) = self.build_app_response(&contract, custom_event, res); - self.process_response(api, router, storage, block, contract, res, msgs) - } -} - -impl WasmKeeper { - pub fn store_code(&mut self, code: Box>) -> usize { - let idx = self.codes.len() + 1; - self.codes.insert(idx, code); - idx - } - - pub fn load_contract(&self, storage: &dyn Storage, address: &Addr) -> AnyResult { - CONTRACTS - .load(&prefixed_read(storage, NAMESPACE_WASM), address) - .map_err(Into::into) - } - - pub fn dump_wasm_raw(&self, storage: &dyn Storage, address: &Addr) -> Vec { - let storage = self.contract_storage_readonly(storage, address); - storage.range(None, None, Order::Ascending).collect() - } - - fn contract_namespace(&self, contract: &Addr) -> Vec { - let mut name = b"contract_data/".to_vec(); - name.extend_from_slice(contract.as_bytes()); - name - } - - fn contract_storage<'a>( - &self, - storage: &'a mut dyn Storage, - address: &Addr, - ) -> Box { - // We double-namespace this, once from global storage -> wasm_storage - // then from wasm_storage -> the contracts subspace - let namespace = self.contract_namespace(address); - let storage = PrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]); - Box::new(storage) - } - - // fails RUNTIME if you try to write. please don't - fn contract_storage_readonly<'a>( - &self, - storage: &'a dyn Storage, - address: &Addr, - ) -> Box { - // We double-namespace this, once from global storage -> wasm_storage - // then from wasm_storage -> the contracts subspace - let namespace = self.contract_namespace(address); - let storage = ReadonlyPrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]); - Box::new(storage) - } - - fn verify_attributes(attributes: &[Attribute]) -> AnyResult<()> { - for attr in attributes { - let key = attr.key.trim(); - let val = attr.value.trim(); - - if key.is_empty() { - bail!(Error::empty_attribute_key(val)); - } - - if val.is_empty() { - bail!(Error::empty_attribute_value(key)); - } - - if key.starts_with('_') { - bail!(Error::reserved_attribute_key(key)); - } - } - - Ok(()) - } - - fn verify_response(response: Response) -> AnyResult> - where - T: Clone + fmt::Debug + PartialEq + JsonSchema, - { - Self::verify_attributes(&response.attributes)?; - - for event in &response.events { - Self::verify_attributes(&event.attributes)?; - let ty = event.ty.trim(); - if ty.len() < 2 { - bail!(Error::event_type_too_short(ty)); - } - } - - Ok(response) - } -} - -impl WasmKeeper -where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new() -> Self { - Self::default() - } - - pub fn query_smart( - &self, - address: Addr, - api: &dyn Api, - storage: &dyn Storage, - querier: &dyn Querier, - block: &BlockInfo, - msg: Vec, - ) -> AnyResult { - self.with_storage_readonly( - api, - storage, - querier, - block, - address, - |handler, deps, env| handler.query(deps, env, msg), - ) - } - - pub fn query_raw(&self, address: Addr, storage: &dyn Storage, key: &[u8]) -> Binary { - let storage = self.contract_storage_readonly(storage, &address); - let data = storage.get(key).unwrap_or_default(); - data.into() - } - - fn send( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - sender: T, - recipient: String, - amount: &[Coin], - ) -> AnyResult - where - T: Into, - { - if !amount.is_empty() { - let msg: cosmwasm_std::CosmosMsg = BankMsg::Send { - to_address: recipient, - amount: amount.to_vec(), - } - .into(); - let res = router.execute(api, storage, block, sender.into(), msg)?; - Ok(res) - } else { - Ok(AppResponse::default()) - } - } - - /// unified logic for UpdateAdmin and ClearAdmin messages - fn update_admin( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - sender: Addr, - contract_addr: &str, - new_admin: Option, - ) -> AnyResult { - let contract_addr = api.addr_validate(contract_addr)?; - let admin = new_admin.map(|a| api.addr_validate(&a)).transpose()?; - - // check admin status - let mut data = self.load_contract(storage, &contract_addr)?; - if data.admin != Some(sender) { - bail!("Only admin can update the contract admin: {:?}", data.admin); - } - // update admin field - data.admin = admin; - self.save_contract(storage, &contract_addr, &data)?; - - // no custom event here - Ok(AppResponse { - data: None, - events: vec![], - }) - } - - // this returns the contract address as well, so we can properly resend the data - fn execute_wasm( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - sender: Addr, - wasm_msg: WasmMsg, - ) -> AnyResult { - match wasm_msg { - WasmMsg::Execute { - contract_addr, - msg, - funds, - } => { - let contract_addr = api.addr_validate(&contract_addr)?; - // first move the cash - self.send( - api, - storage, - router, - block, - sender.clone(), - contract_addr.clone().into(), - &funds, - )?; - - // then call the contract - let info = MessageInfo { sender, funds }; - let res = self.call_execute( - api, - storage, - contract_addr.clone(), - router, - block, - info, - msg.to_vec(), - )?; - - let custom_event = - Event::new("execute").add_attribute(CONTRACT_ATTR, &contract_addr); - - let (res, msgs) = self.build_app_response(&contract_addr, custom_event, res); - let mut res = - self.process_response(api, router, storage, block, contract_addr, res, msgs)?; - res.data = execute_response(res.data); - Ok(res) - } - WasmMsg::Instantiate { - admin, - code_id, - msg, - funds, - label, - } => { - if label.is_empty() { - bail!("Label is required on all contracts"); - } - - let contract_addr = self.register_contract( - storage, - code_id as usize, - sender.clone(), - admin.map(Addr::unchecked), - label, - block.height, - )?; - - // move the cash - self.send( - api, - storage, - router, - block, - sender.clone(), - contract_addr.clone().into(), - &funds, - )?; - - // then call the contract - let info = MessageInfo { sender, funds }; - let res = self.call_instantiate( - contract_addr.clone(), - api, - storage, - router, - block, - info, - msg.to_vec(), - )?; - - let custom_event = Event::new("instantiate") - .add_attribute(CONTRACT_ATTR, &contract_addr) - .add_attribute("code_id", code_id.to_string()); - - let (res, msgs) = self.build_app_response(&contract_addr, custom_event, res); - let mut res = self.process_response( - api, - router, - storage, - block, - contract_addr.clone(), - res, - msgs, - )?; - res.data = Some(instantiate_response(res.data, &contract_addr)); - Ok(res) - } - WasmMsg::Migrate { - contract_addr, - new_code_id, - msg, - } => { - let contract_addr = api.addr_validate(&contract_addr)?; - - // check admin status and update the stored code_id - let new_code_id = new_code_id as usize; - if !self.codes.contains_key(&new_code_id) { - bail!("Cannot migrate contract to unregistered code id"); - } - let mut data = self.load_contract(storage, &contract_addr)?; - if data.admin != Some(sender) { - bail!("Only admin can migrate contract: {:?}", data.admin); - } - data.code_id = new_code_id; - self.save_contract(storage, &contract_addr, &data)?; - - // then call migrate - let res = self.call_migrate( - contract_addr.clone(), - api, - storage, - router, - block, - msg.to_vec(), - )?; - - let custom_event = Event::new("migrate") - .add_attribute(CONTRACT_ATTR, &contract_addr) - .add_attribute("code_id", new_code_id.to_string()); - let (res, msgs) = self.build_app_response(&contract_addr, custom_event, res); - let mut res = - self.process_response(api, router, storage, block, contract_addr, res, msgs)?; - res.data = execute_response(res.data); - Ok(res) - } - WasmMsg::UpdateAdmin { - contract_addr, - admin, - } => self.update_admin(api, storage, sender, &contract_addr, Some(admin)), - WasmMsg::ClearAdmin { contract_addr } => { - self.update_admin(api, storage, sender, &contract_addr, None) - } - msg => bail!(Error::UnsupportedWasmMsg(msg)), - } - } - - /// This will execute the given messages, making all changes to the local cache. - /// This *will* write some data to the cache if the message fails half-way through. - /// All sequential calls to RouterCache will be one atomic unit (all commit or all fail). - /// - /// For normal use cases, you can use Router::execute() or Router::execute_multi(). - /// This is designed to be handled internally as part of larger process flows. - /// - /// The `data` on `AppResponse` is data returned from `reply` call, not from execution of - /// submessage itself. In case if `reply` is not called, no `data` is set. - fn execute_submsg( - &self, - api: &dyn Api, - router: &dyn CosmosRouter, - storage: &mut dyn Storage, - block: &BlockInfo, - contract: Addr, - msg: SubMsg, - ) -> AnyResult { - let SubMsg { - msg, id, reply_on, .. - } = msg; - - // execute in cache - let res = transactional(storage, |write_cache, _| { - router.execute(api, write_cache, block, contract.clone(), msg) - }); - - // call reply if meaningful - if let Ok(mut r) = res { - if matches!(reply_on, ReplyOn::Always | ReplyOn::Success) { - let reply = Reply { - id, - result: SubMsgResult::Ok(SubMsgResponse { - events: r.events.clone(), - data: r.data, - }), - }; - // do reply and combine it with the original response - let reply_res = self._reply(api, router, storage, block, contract, reply)?; - // override data - r.data = reply_res.data; - // append the events - r.events.extend_from_slice(&reply_res.events); - } else { - // reply is not called, no data should be rerturned - r.data = None; - } - - Ok(r) - } else if let Err(e) = res { - if matches!(reply_on, ReplyOn::Always | ReplyOn::Error) { - let reply = Reply { - id, - result: SubMsgResult::Err(e.to_string()), - }; - self._reply(api, router, storage, block, contract, reply) - } else { - Err(e) - } - } else { - res - } - } - - fn _reply( - &self, - api: &dyn Api, - router: &dyn CosmosRouter, - storage: &mut dyn Storage, - block: &BlockInfo, - contract: Addr, - reply: Reply, - ) -> AnyResult { - let ok_attr = if reply.result.is_ok() { - "handle_success" - } else { - "handle_failure" - }; - let custom_event = Event::new("reply") - .add_attribute(CONTRACT_ATTR, &contract) - .add_attribute("mode", ok_attr); - - let res = self.call_reply(contract.clone(), api, storage, router, block, reply)?; - let (res, msgs) = self.build_app_response(&contract, custom_event, res); - self.process_response(api, router, storage, block, contract, res, msgs) - } - - // this captures all the events and data from the contract call. - // it does not handle the messages - fn build_app_response( - &self, - contract: &Addr, - custom_event: Event, // entry-point specific custom event added by x/wasm - response: Response, - ) -> (AppResponse, Vec>) { - let Response { - messages, - attributes, - events, - data, - .. - } = response; - - // always add custom event - let mut app_events = Vec::with_capacity(2 + events.len()); - app_events.push(custom_event); - - // we only emit the `wasm` event if some attributes are specified - if !attributes.is_empty() { - // turn attributes into event and place it first - let wasm_event = Event::new("wasm") - .add_attribute(CONTRACT_ATTR, contract) - .add_attributes(attributes); - app_events.push(wasm_event); - } - - // These need to get `wasm-` prefix to match the wasmd semantics (custom wasm messages cannot - // fake system level event types, like transfer from the bank module) - let wasm_events = events.into_iter().map(|mut ev| { - ev.ty = format!("wasm-{}", ev.ty); - ev.attributes - .insert(0, mock_wasmd_attr(CONTRACT_ATTR, contract)); - ev - }); - app_events.extend(wasm_events); - - let app = AppResponse { - events: app_events, - data, - }; - (app, messages) - } - - fn process_response( - &self, - api: &dyn Api, - router: &dyn CosmosRouter, - storage: &mut dyn Storage, - block: &BlockInfo, - contract: Addr, - response: AppResponse, - messages: Vec>, - ) -> AnyResult { - let AppResponse { mut events, data } = response; - - // recurse in all messages - let data = messages.into_iter().try_fold(data, |data, resend| { - let subres = - self.execute_submsg(api, router, storage, block, contract.clone(), resend)?; - events.extend_from_slice(&subres.events); - Ok::<_, anyhow::Error>(subres.data.or(data)) - })?; - - Ok(AppResponse { events, data }) - } - - /// This just creates an address and empty storage instance, returning the new address - /// You must call init after this to set up the contract properly. - /// These are separated into two steps to have cleaner return values. - pub fn register_contract( - &self, - storage: &mut dyn Storage, - code_id: usize, - creator: Addr, - admin: impl Into>, - label: String, - created: u64, - ) -> AnyResult { - if !self.codes.contains_key(&code_id) { - bail!("Cannot init contract with unregistered code id"); - } - - let addr = self.next_address(&prefixed_read(storage, NAMESPACE_WASM)); - - let info = ContractData { - code_id, - creator, - admin: admin.into(), - label, - created, - }; - self.save_contract(storage, &addr, &info)?; - Ok(addr) - } - - pub fn call_execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - address: Addr, - router: &dyn CosmosRouter, - block: &BlockInfo, - info: MessageInfo, - msg: Vec, - ) -> AnyResult> { - Self::verify_response(self.with_storage( - api, - storage, - router, - block, - address, - |contract, deps, env| contract.execute(deps, env, info, msg), - )?) - } - - pub fn call_instantiate( - &self, - address: Addr, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - info: MessageInfo, - msg: Vec, - ) -> AnyResult> { - Self::verify_response(self.with_storage( - api, - storage, - router, - block, - address, - |contract, deps, env| contract.instantiate(deps, env, info, msg), - )?) - } - - pub fn call_reply( - &self, - address: Addr, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - reply: Reply, - ) -> AnyResult> { - Self::verify_response(self.with_storage( - api, - storage, - router, - block, - address, - |contract, deps, env| contract.reply(deps, env, reply), - )?) - } - - pub fn call_sudo( - &self, - address: Addr, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - msg: Vec, - ) -> AnyResult> { - Self::verify_response(self.with_storage( - api, - storage, - router, - block, - address, - |contract, deps, env| contract.sudo(deps, env, msg), - )?) - } - - pub fn call_migrate( - &self, - address: Addr, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - msg: Vec, - ) -> AnyResult> { - Self::verify_response(self.with_storage( - api, - storage, - router, - block, - address, - |contract, deps, env| contract.migrate(deps, env, msg), - )?) - } - - fn get_env>(&self, address: T, block: &BlockInfo) -> Env { - Env { - block: block.clone(), - contract: ContractInfo { - address: address.into(), - }, - transaction: Some(TransactionInfo { index: 0 }), - } - } - - fn with_storage_readonly( - &self, - api: &dyn Api, - storage: &dyn Storage, - querier: &dyn Querier, - block: &BlockInfo, - address: Addr, - action: F, - ) -> AnyResult - where - F: FnOnce(&Box>, Deps, Env) -> AnyResult, - { - let contract = self.load_contract(storage, &address)?; - let handler = self - .codes - .get(&contract.code_id) - .ok_or(Error::UnregisteredCodeId(contract.code_id))?; - let storage = self.contract_storage_readonly(storage, &address); - let env = self.get_env(address, block); - - let deps = Deps { - storage: storage.as_ref(), - api: api.deref(), - querier: QuerierWrapper::new(querier), - }; - action(handler, deps, env) - } - - fn with_storage( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - router: &dyn CosmosRouter, - block: &BlockInfo, - address: Addr, - action: F, - ) -> AnyResult - where - F: FnOnce(&Box>, DepsMut, Env) -> AnyResult, - ExecC: DeserializeOwned, - { - let contract = self.load_contract(storage, &address)?; - let handler = self - .codes - .get(&contract.code_id) - .ok_or(Error::UnregisteredCodeId(contract.code_id))?; - - // We don't actually need a transaction here, as it is already embedded in a transactional. - // execute_submsg or App.execute_multi. - // However, we need to get write and read access to the same storage in two different objects, - // and this is the only way I know how to do so. - transactional(storage, |write_cache, read_store| { - let mut contract_storage = self.contract_storage(write_cache, &address); - let querier = RouterQuerier::new(router, api, read_store, block); - let env = self.get_env(address, block); - - let deps = DepsMut { - storage: contract_storage.as_mut(), - api: api.deref(), - querier: QuerierWrapper::new(&querier), - }; - action(handler, deps, env) - }) - } - - pub fn save_contract( - &self, - storage: &mut dyn Storage, - address: &Addr, - contract: &ContractData, - ) -> AnyResult<()> { - CONTRACTS - .save(&mut prefixed(storage, NAMESPACE_WASM), address, contract) - .map_err(Into::into) - } - - // FIXME: better addr generation - fn next_address(&self, storage: &dyn Storage) -> Addr { - // FIXME: quite inefficient if we actually had 100s of contracts - let count = CONTRACTS - .range_raw(storage, None, None, Order::Ascending) - .count(); - // we make this longer so it is not rejected by tests - // it is lowercase to be compatible with the MockApi implementation of cosmwasm-std >= 1.0.0-beta8 - Addr::unchecked(format!("contract{}", count)) - } -} - -// TODO: replace with code in utils - -#[derive(Clone, PartialEq, Message)] -struct InstantiateResponse { - #[prost(string, tag = "1")] - pub address: ::prost::alloc::string::String, - #[prost(bytes, tag = "2")] - pub data: ::prost::alloc::vec::Vec, -} - -// TODO: encode helpers in utils -fn instantiate_response(data: Option, contact_address: &Addr) -> Binary { - let data = data.unwrap_or_default().to_vec(); - let init_data = InstantiateResponse { - address: contact_address.into(), - data, - }; - let mut new_data = Vec::::with_capacity(init_data.encoded_len()); - // the data must encode successfully - init_data.encode(&mut new_data).unwrap(); - new_data.into() -} - -#[derive(Clone, PartialEq, Message)] -struct ExecuteResponse { - #[prost(bytes, tag = "1")] - pub data: ::prost::alloc::vec::Vec, -} - -// empty return if no data present in original -fn execute_response(data: Option) -> Option { - data.map(|d| { - let exec_data = ExecuteResponse { data: d.to_vec() }; - let mut new_data = Vec::::with_capacity(exec_data.encoded_len()); - // the data must encode successfully - exec_data.encode(&mut new_data).unwrap(); - new_data.into() - }) -} - -#[cfg(test)] -mod test { - use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}; - use cosmwasm_std::{coin, from_slice, to_vec, BankMsg, Coin, CosmosMsg, Empty, StdError}; - - use crate::app::Router; - use crate::bank::BankKeeper; - use crate::module::FailingModule; - use crate::staking::{DistributionKeeper, StakeKeeper}; - use crate::test_helpers::contracts::{caller, error, payout}; - use crate::test_helpers::EmptyMsg; - use crate::transactions::StorageTransaction; - - use super::*; - - /// Type alias for default build `Router` to make its reference in typical scenario - type BasicRouter = Router< - BankKeeper, - FailingModule, - WasmKeeper, - StakeKeeper, - DistributionKeeper, - >; - - fn mock_router() -> BasicRouter { - Router { - wasm: WasmKeeper::new(), - bank: BankKeeper::new(), - custom: FailingModule::new(), - staking: StakeKeeper::new(), - distribution: DistributionKeeper::new(), - } - } - - #[test] - fn register_contract() { - let api = MockApi::default(); - let mut wasm_storage = MockStorage::new(); - let mut keeper = WasmKeeper::new(); - let block = mock_env().block; - let code_id = keeper.store_code(error::contract(false)); - - transactional(&mut wasm_storage, |cache, _| { - // cannot register contract with unregistered codeId - keeper.register_contract( - cache, - code_id + 1, - Addr::unchecked("foobar"), - Addr::unchecked("admin"), - "label".to_owned(), - 1000, - ) - }) - .unwrap_err(); - - let contract_addr = transactional(&mut wasm_storage, |cache, _| { - // we can register a new instance of this code - keeper.register_contract( - cache, - code_id, - Addr::unchecked("foobar"), - Addr::unchecked("admin"), - "label".to_owned(), - 1000, - ) - }) - .unwrap(); - - // verify contract data are as expected - let contract_data = keeper.load_contract(&wasm_storage, &contract_addr).unwrap(); - - assert_eq!( - contract_data, - ContractData { - code_id, - creator: Addr::unchecked("foobar"), - admin: Some(Addr::unchecked("admin")), - label: "label".to_owned(), - created: 1000, - } - ); - - let err = transactional(&mut wasm_storage, |cache, _| { - // now, we call this contract and see the error message from the contract - let info = mock_info("foobar", &[]); - keeper.call_instantiate( - contract_addr.clone(), - &api, - cache, - &mock_router(), - &block, - info, - b"{}".to_vec(), - ) - }) - .unwrap_err(); - - // StdError from contract_error auto-converted to string - assert_eq!( - StdError::generic_err("Init failed"), - err.downcast().unwrap() - ); - - let err = transactional(&mut wasm_storage, |cache, _| { - // and the error for calling an unregistered contract - let info = mock_info("foobar", &[]); - keeper.call_instantiate( - Addr::unchecked("unregistered"), - &api, - cache, - &mock_router(), - &block, - info, - b"{}".to_vec(), - ) - }) - .unwrap_err(); - - // Default error message from router when not found - assert_eq!( - StdError::not_found("cw_multi_test::wasm::ContractData"), - err.downcast().unwrap() - ); - } - - #[test] - fn query_contract_into() { - let api = MockApi::default(); - let mut keeper = WasmKeeper::::new(); - let block = mock_env().block; - let code_id = keeper.store_code(payout::contract()); - - let mut wasm_storage = MockStorage::new(); - - let contract_addr = keeper - .register_contract( - &mut wasm_storage, - code_id, - Addr::unchecked("foobar"), - Addr::unchecked("admin"), - "label".to_owned(), - 1000, - ) - .unwrap(); - - let querier: MockQuerier = MockQuerier::new(&[]); - let query = WasmQuery::ContractInfo { - contract_addr: contract_addr.to_string(), - }; - let info = keeper - .query(&api, &wasm_storage, &querier, &block, query) - .unwrap(); - - let mut expected = ContractInfoResponse::new(code_id as u64, "foobar"); - expected.admin = Some("admin".to_owned()); - assert_eq!(expected, from_slice(&info).unwrap()); - } - - #[test] - fn can_dump_raw_wasm_state() { - let api = MockApi::default(); - let mut keeper = WasmKeeper::::new(); - let block = mock_env().block; - let code_id = keeper.store_code(payout::contract()); - - let mut wasm_storage = MockStorage::new(); - - let contract_addr = keeper - .register_contract( - &mut wasm_storage, - code_id, - Addr::unchecked("foobar"), - Addr::unchecked("admin"), - "label".to_owned(), - 1000, - ) - .unwrap(); - - // make a contract with state - let payout = coin(1500, "mlg"); - let msg = payout::InstantiateMessage { - payout: payout.clone(), - }; - keeper - .call_instantiate( - contract_addr.clone(), - &api, - &mut wasm_storage, - &mock_router(), - &block, - mock_info("foobar", &[]), - to_vec(&msg).unwrap(), - ) - .unwrap(); - - // dump state - let state = keeper.dump_wasm_raw(&wasm_storage, &contract_addr); - assert_eq!(state.len(), 2); - // check contents - let (k, v) = &state[0]; - assert_eq!(k.as_slice(), b"count"); - let count: u32 = from_slice(v).unwrap(); - assert_eq!(count, 1); - let (k, v) = &state[1]; - assert_eq!(k.as_slice(), b"payout"); - let stored_pay: payout::InstantiateMessage = from_slice(v).unwrap(); - assert_eq!(stored_pay.payout, payout); - } - - #[test] - fn contract_send_coins() { - let api = MockApi::default(); - let mut keeper = WasmKeeper::new(); - let block = mock_env().block; - let code_id = keeper.store_code(payout::contract()); - - let mut wasm_storage = MockStorage::new(); - let mut cache = StorageTransaction::new(&wasm_storage); - - let contract_addr = keeper - .register_contract( - &mut cache, - code_id, - Addr::unchecked("foobar"), - None, - "label".to_owned(), - 1000, - ) - .unwrap(); - - let payout = coin(100, "TGD"); - - // init the contract - let info = mock_info("foobar", &[]); - let init_msg = to_vec(&payout::InstantiateMessage { - payout: payout.clone(), - }) - .unwrap(); - let res = keeper - .call_instantiate( - contract_addr.clone(), - &api, - &mut cache, - &mock_router(), - &block, - info, - init_msg, - ) - .unwrap(); - assert_eq!(0, res.messages.len()); - - // execute the contract - let info = mock_info("foobar", &[]); - let res = keeper - .call_execute( - &api, - &mut cache, - contract_addr.clone(), - &mock_router(), - &block, - info, - b"{}".to_vec(), - ) - .unwrap(); - assert_eq!(1, res.messages.len()); - match &res.messages[0].msg { - CosmosMsg::Bank(BankMsg::Send { to_address, amount }) => { - assert_eq!(to_address.as_str(), "foobar"); - assert_eq!(amount.as_slice(), &[payout.clone()]); - } - m => panic!("Unexpected message {:?}", m), - } - - // and flush before query - cache.prepare().commit(&mut wasm_storage); - - // query the contract - let query = to_vec(&payout::QueryMsg::Payout {}).unwrap(); - let querier: MockQuerier = MockQuerier::new(&[]); - let data = keeper - .query_smart(contract_addr, &api, &wasm_storage, &querier, &block, query) - .unwrap(); - let res: payout::InstantiateMessage = from_slice(&data).unwrap(); - assert_eq!(res.payout, payout); - } - - fn assert_payout( - router: &WasmKeeper, - storage: &mut dyn Storage, - contract_addr: &Addr, - payout: &Coin, - ) { - let api = MockApi::default(); - let info = mock_info("silly", &[]); - let res = router - .call_execute( - &api, - storage, - contract_addr.clone(), - &mock_router(), - &mock_env().block, - info, - b"{}".to_vec(), - ) - .unwrap(); - assert_eq!(1, res.messages.len()); - match &res.messages[0].msg { - CosmosMsg::Bank(BankMsg::Send { to_address, amount }) => { - assert_eq!(to_address.as_str(), "silly"); - assert_eq!(amount.as_slice(), &[payout.clone()]); - } - m => panic!("Unexpected message {:?}", m), - } - } - - fn assert_no_contract(storage: &dyn Storage, contract_addr: &Addr) { - let contract = CONTRACTS.may_load(storage, contract_addr).unwrap(); - assert!(contract.is_none(), "{:?}", contract_addr); - } - - #[test] - fn multi_level_wasm_cache() { - let api = MockApi::default(); - let mut keeper = WasmKeeper::new(); - let block = mock_env().block; - let code_id = keeper.store_code(payout::contract()); - - let mut wasm_storage = MockStorage::new(); - - let payout1 = coin(100, "TGD"); - - // set contract 1 and commit (on router) - let contract1 = transactional(&mut wasm_storage, |cache, _| { - let contract = keeper - .register_contract( - cache, - code_id, - Addr::unchecked("foobar"), - None, - "".to_string(), - 1000, - ) - .unwrap(); - let info = mock_info("foobar", &[]); - let init_msg = to_vec(&payout::InstantiateMessage { - payout: payout1.clone(), - }) - .unwrap(); - keeper - .call_instantiate( - contract.clone(), - &api, - cache, - &mock_router(), - &block, - info, - init_msg, - ) - .unwrap(); - - Ok(contract) - }) - .unwrap(); - - let payout2 = coin(50, "BTC"); - let payout3 = coin(1234, "ATOM"); - - // create a new cache and check we can use contract 1 - let (contract2, contract3) = transactional(&mut wasm_storage, |cache, wasm_reader| { - assert_payout(&keeper, cache, &contract1, &payout1); - - // create contract 2 and use it - let contract2 = keeper - .register_contract( - cache, - code_id, - Addr::unchecked("foobar"), - None, - "".to_owned(), - 1000, - ) - .unwrap(); - let info = mock_info("foobar", &[]); - let init_msg = to_vec(&payout::InstantiateMessage { - payout: payout2.clone(), - }) - .unwrap(); - let _res = keeper - .call_instantiate( - contract2.clone(), - &api, - cache, - &mock_router(), - &block, - info, - init_msg, - ) - .unwrap(); - assert_payout(&keeper, cache, &contract2, &payout2); - - // create a level2 cache and check we can use contract 1 and contract 2 - let contract3 = transactional(cache, |cache2, read| { - assert_payout(&keeper, cache2, &contract1, &payout1); - assert_payout(&keeper, cache2, &contract2, &payout2); - - // create a contract on level 2 - let contract3 = keeper - .register_contract( - cache2, - code_id, - Addr::unchecked("foobar"), - None, - "".to_owned(), - 1000, - ) - .unwrap(); - let info = mock_info("johnny", &[]); - let init_msg = to_vec(&payout::InstantiateMessage { - payout: payout3.clone(), - }) - .unwrap(); - let _res = keeper - .call_instantiate( - contract3.clone(), - &api, - cache2, - &mock_router(), - &block, - info, - init_msg, - ) - .unwrap(); - assert_payout(&keeper, cache2, &contract3, &payout3); - - // ensure first cache still doesn't see this contract - assert_no_contract(read, &contract3); - Ok(contract3) - }) - .unwrap(); - - // after applying transaction, all contracts present on cache - assert_payout(&keeper, cache, &contract1, &payout1); - assert_payout(&keeper, cache, &contract2, &payout2); - assert_payout(&keeper, cache, &contract3, &payout3); - - // but not yet the root router - assert_no_contract(wasm_reader, &contract1); - assert_no_contract(wasm_reader, &contract2); - assert_no_contract(wasm_reader, &contract3); - - Ok((contract2, contract3)) - }) - .unwrap(); - - // ensure that it is now applied to the router - assert_payout(&keeper, &mut wasm_storage, &contract1, &payout1); - assert_payout(&keeper, &mut wasm_storage, &contract2, &payout2); - assert_payout(&keeper, &mut wasm_storage, &contract3, &payout3); - } - - fn assert_admin( - storage: &dyn Storage, - keeper: &WasmKeeper, - contract_addr: &impl ToString, - admin: Option, - ) { - let api = MockApi::default(); - let querier: MockQuerier = MockQuerier::new(&[]); - // query - let data = keeper - .query( - &api, - storage, - &querier, - &mock_env().block, - WasmQuery::ContractInfo { - contract_addr: contract_addr.to_string(), - }, - ) - .unwrap(); - let res: ContractInfoResponse = from_slice(&data).unwrap(); - assert_eq!(res.admin, admin.as_ref().map(Addr::to_string)); - } - - #[test] - fn update_clear_admin_works() { - let api = MockApi::default(); - let mut keeper = WasmKeeper::new(); - let block = mock_env().block; - let code_id = keeper.store_code(caller::contract()); - - let mut wasm_storage = MockStorage::new(); - - let admin: Addr = Addr::unchecked("admin"); - let new_admin: Addr = Addr::unchecked("new_admin"); - let normal_user: Addr = Addr::unchecked("normal_user"); - - let contract_addr = keeper - .register_contract( - &mut wasm_storage, - code_id, - Addr::unchecked("creator"), - admin.clone(), - "label".to_owned(), - 1000, - ) - .unwrap(); - - // init the contract - let info = mock_info("admin", &[]); - let init_msg = to_vec(&EmptyMsg {}).unwrap(); - let res = keeper - .call_instantiate( - contract_addr.clone(), - &api, - &mut wasm_storage, - &mock_router(), - &block, - info, - init_msg, - ) - .unwrap(); - assert_eq!(0, res.messages.len()); - - assert_admin(&wasm_storage, &keeper, &contract_addr, Some(admin.clone())); - - // non-admin should not be allowed to become admin on their own - keeper - .execute_wasm( - &api, - &mut wasm_storage, - &mock_router(), - &block, - normal_user.clone(), - WasmMsg::UpdateAdmin { - contract_addr: contract_addr.to_string(), - admin: normal_user.to_string(), - }, - ) - .unwrap_err(); - - // should still be admin - assert_admin(&wasm_storage, &keeper, &contract_addr, Some(admin.clone())); - - // admin should be allowed to transfers adminship - let res = keeper - .execute_wasm( - &api, - &mut wasm_storage, - &mock_router(), - &block, - admin, - WasmMsg::UpdateAdmin { - contract_addr: contract_addr.to_string(), - admin: new_admin.to_string(), - }, - ) - .unwrap(); - assert_eq!(res.events.len(), 0); - - // new_admin should now be admin - assert_admin( - &wasm_storage, - &keeper, - &contract_addr, - Some(new_admin.clone()), - ); - - // new_admin should now be able to clear to admin - let res = keeper - .execute_wasm( - &api, - &mut wasm_storage, - &mock_router(), - &block, - new_admin, - WasmMsg::ClearAdmin { - contract_addr: contract_addr.to_string(), - }, - ) - .unwrap(); - assert_eq!(res.events.len(), 0); - - // should have no admin now - assert_admin(&wasm_storage, &keeper, &contract_addr, None); - } -} diff --git a/packages/storage-macro/Cargo.toml b/packages/storage-macro/Cargo.toml deleted file mode 100644 index 39eceef0c..000000000 --- a/packages/storage-macro/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "cw-storage-macro" -version = "0.16.0" -authors = ["yoisha <48324733+y-pakorn@users.noreply.github.com>"] -edition = "2018" -description = "Macro helpers for storage-plus" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-plus" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" - -[lib] -proc-macro = true - -[dependencies] -syn = { version = "1.0.96", features = ["full"] } - -[dev-dependencies] -cosmwasm-std = { version = "1.1.0", default-features = false } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/storage-macro/NOTICE b/packages/storage-macro/NOTICE deleted file mode 100644 index 4ae1ccfe5..000000000 --- a/packages/storage-macro/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -CW-Storage-Macro: Macro helpers for storage-plus -Copyright (C) 2022 Confio GmbH - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/storage-macro/README.md b/packages/storage-macro/README.md deleted file mode 100644 index d21d9f571..000000000 --- a/packages/storage-macro/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# CW-Storage-Plus: Macro helpers for storage-plus - -Procedural macros helper for interacting with cw-storage-plus and cosmwasm-storage. - -## Current features - -Auto generate an `IndexList` impl for your indexes struct. - -```rust -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -struct TestStruct { - id: u64, - id2: u32, - addr: Addr, -} - -#[index_list(TestStruct)] // <- Add this line right here. -struct TestIndexes<'a> { - id: MultiIndex<'a, u32, TestStruct, u64>, - addr: UniqueIndex<'a, Addr, TestStruct>, -} -``` diff --git a/packages/storage-macro/src/lib.rs b/packages/storage-macro/src/lib.rs deleted file mode 100644 index 23ecc5c5f..000000000 --- a/packages/storage-macro/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -/*! -Procedural macros helper for interacting with cw-storage-plus and cosmwasm-storage. - -For more information on this package, please check out the -[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/storage-macro/README.md). -*/ - -use proc_macro::TokenStream; -use syn::{ - Ident, - __private::{quote::quote, Span}, - parse_macro_input, ItemStruct, -}; - -#[proc_macro_attribute] -pub fn index_list(attr: TokenStream, item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as ItemStruct); - - let ty = Ident::new(&attr.to_string(), Span::call_site()); - let struct_ty = input.ident.clone(); - - let names = input - .fields - .clone() - .into_iter() - .map(|e| { - let name = e.ident.unwrap(); - quote! { &self.#name } - }) - .collect::>(); - - let expanded = quote! { - #input - - impl cw_storage_plus::IndexList<#ty> for #struct_ty<'_> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn cw_storage_plus::Index<#ty>> = vec![#(#names),*]; - Box::new(v.into_iter()) - } - } - }; - - TokenStream::from(expanded) -} diff --git a/packages/storage-plus/Cargo.toml b/packages/storage-plus/Cargo.toml deleted file mode 100644 index 2855094c2..000000000 --- a/packages/storage-plus/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "cw-storage-plus" -version = "0.16.0" -authors = ["Ethan Frey "] -edition = "2018" -description = "Enhanced storage engines" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-plus" -homepage = "https://cosmwasm.com" - -[package.metadata.docs.rs] -all-features = true # include macro feature when building docs - -[features] -default = ["iterator"] -iterator = ["cosmwasm-std/iterator"] -macro = ["cw-storage-macro"] - -[lib] -# See https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options -bench = false - -[dependencies] -cosmwasm-std = { version = "1.1.0", default-features = false } -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw-storage-macro = { version = "0.16.0", optional = true, path = "../storage-macro" } - -[dev-dependencies] -criterion = { version = "0.3", features = ["html_reports"] } -rand = "0.8" - -[[bench]] -name = "main" -harness = false diff --git a/packages/storage-plus/NOTICE b/packages/storage-plus/NOTICE deleted file mode 100644 index 838a67f47..000000000 --- a/packages/storage-plus/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -CW-Storage-Plus: Enhanced/experimental storage engines for CosmWasm -Copyright (C) 2020 Confio OÜ - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/storage-plus/README.md b/packages/storage-plus/README.md deleted file mode 100644 index b87b84223..000000000 --- a/packages/storage-plus/README.md +++ /dev/null @@ -1,702 +0,0 @@ -# CW-Storage-Plus: Enhanced storage engines for CosmWasm - -After building `cosmwasm-storage`, we realized many of the design decisions were -limiting us and producing a lot of needless boilerplate. The decision was made to leave -those APIs stable for anyone wanting a very basic abstraction on the KV-store and to -build a much more powerful and complex ORM layer that can provide powerful accessors -using complex key types, which are transparently turned into bytes. - -This led to a number of breaking API changes in this package of the course of several -releases as we updated this with lots of experience, user feedback, and deep dives to harness -the full power of generics. - -**Status: beta** - -As of `cw-storage-plus` `v0.12` the API should be quite stable. -There are no major API breaking issues pending, and all API changes will be documented -in [`MIGRATING.md`](../../MIGRATING.md). - -This has been heavily used in many production-quality contracts. -The code has demonstrated itself to be stable and powerful. -It has not been audited, and Confio assumes no liability, but we consider it mature enough -to be the **standard storage layer** for your contracts. - -## Usage Overview - -We introduce two main classes to provide a productive abstraction -on top of `cosmwasm_std::Storage`. They are `Item`, which is -a typed wrapper around one database key, providing some helper functions -for interacting with it without dealing with raw bytes. And `Map`, -which allows you to store multiple unique typed objects under a prefix, -indexed by a simple or compound (eg. `(&[u8], &[u8])`) primary key. - -## Item - -The usage of an [`Item`](./src/item.rs) is pretty straight-forward. -You must simply provide the proper type, as well as a database key not -used by any other item. Then it will provide you with a nice interface -to interact with such data. - -If you are coming from using `Singleton`, the biggest change is that -we no longer store `Storage` inside, meaning we don't need read and write -variants of the object, just one type. Furthermore, we use `const fn` -to create the `Item`, allowing it to be defined as a global compile-time -constant rather than a function that must be constructed each time, -which saves gas as well as typing. - -Example Usage: - -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct Config { - pub owner: String, - pub max_tokens: i32, -} - -// note const constructor rather than 2 functions with Singleton -const CONFIG: Item = Item::new("config"); - -fn demo() -> StdResult<()> { - let mut store = MockStorage::new(); - - // may_load returns Option, so None if data is missing - // load returns T and Err(StdError::NotFound{}) if data is missing - let empty = CONFIG.may_load(&store)?; - assert_eq!(None, empty); - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg)?; - let loaded = CONFIG.load(&store)?; - assert_eq!(cfg, loaded); - - // update an item with a closure (includes read and write) - // returns the newly saved value - let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> { - c.max_tokens *= 2; - Ok(c) - })?; - assert_eq!(2468, output.max_tokens); - - // you can error in an update and nothing is saved - let failed = CONFIG.update(&mut store, |_| -> StdResult<_> { - Err(StdError::generic_err("failure mode")) - }); - assert!(failed.is_err()); - - // loading data will show the first update was saved - let loaded = CONFIG.load(&store)?; - let expected = Config { - owner: "admin".to_string(), - max_tokens: 2468, - }; - assert_eq!(expected, loaded); - - // we can remove data as well - CONFIG.remove(&mut store); - let empty = CONFIG.may_load(&store)?; - assert_eq!(None, empty); - - Ok(()) -} -``` - -## Map - -The usage of a [`Map`](./src/map.rs) is a little more complex, but -is still pretty straight-forward. You can imagine it as a storage-backed -`BTreeMap`, allowing key-value lookups with typed values. In addition, -we support not only simple binary keys (like `&[u8]`), but tuples, which are -combined. This allows us by example to store allowances as composite keys, -i.e. `(owner, spender)` to look up the balance. - -Beyond direct lookups, we have a super-power not found in Ethereum - -iteration. That's right, you can list all items in a `Map`, or only -part of them. We can efficiently allow pagination over these items as -well, starting at the point the last query ended, with low gas costs. -This requires the `iterator` feature to be enabled in `cw-storage-plus` -(which automatically enables it in `cosmwasm-std` as well, and which is -enabled by default). - -If you are coming from using `Bucket`, the biggest change is that -we no longer store `Storage` inside, meaning we don't need read and write -variants of the object, just one type. Furthermore, we use `const fn` -to create the `Bucket`, allowing it to be defined as a global compile-time -constant rather than a function that must be constructed each time, -which saves gas as well as typing. In addition, the composite indexes -(tuples) are more ergonomic and expressive of intention, and the range -interface has been improved. - -Here is an example with normal (simple) keys: - -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -struct Data { - pub name: String, - pub age: i32, -} - -const PEOPLE: Map<&str, Data> = Map::new("people"); - -fn demo() -> StdResult<()> { - let mut store = MockStorage::new(); - let data = Data { - name: "John".to_string(), - age: 32, - }; - - // load and save with extra key argument - let empty = PEOPLE.may_load(&store, "john")?; - assert_eq!(None, empty); - PEOPLE.save(&mut store, "john", &data)?; - let loaded = PEOPLE.load(&store, "john")?; - assert_eq!(data, loaded); - - // nothing on another key - let missing = PEOPLE.may_load(&store, "jack")?; - assert_eq!(None, missing); - - // update function for new or existing keys - let birthday = |d: Option| -> StdResult { - match d { - Some(one) => Ok(Data { - name: one.name, - age: one.age + 1, - }), - None => Ok(Data { - name: "Newborn".to_string(), - age: 0, - }), - } - }; - - let old_john = PEOPLE.update(&mut store, "john", birthday)?; - assert_eq!(33, old_john.age); - assert_eq!("John", old_john.name.as_str()); - - let new_jack = PEOPLE.update(&mut store, "jack", birthday)?; - assert_eq!(0, new_jack.age); - assert_eq!("Newborn", new_jack.name.as_str()); - - // update also changes the store - assert_eq!(old_john, PEOPLE.load(&store, "john")?); - assert_eq!(new_jack, PEOPLE.load(&store, "jack")?); - - // removing leaves us empty - PEOPLE.remove(&mut store, "john"); - let empty = PEOPLE.may_load(&store, "john")?; - assert_eq!(None, empty); - - Ok(()) -} -``` - -### Key types - -A `Map` key can be anything that implements the `PrimaryKey` trait. There are a series of implementations of -`PrimaryKey` already provided (see [keys.rs](./src/keys.rs)): - - - `impl<'a> PrimaryKey<'a> for &'a [u8]` - - `impl<'a> PrimaryKey<'a> for &'a str` - - `impl<'a> PrimaryKey<'a> for Vec` - - `impl<'a> PrimaryKey<'a> for String` - - `impl<'a> PrimaryKey<'a> for Addr` - - `impl<'a> PrimaryKey<'a> for &'a Addr` - - `impl<'a, T: PrimaryKey<'a> + Prefixer<'a>, U: PrimaryKey<'a>> PrimaryKey<'a> for (T, U)` - - `impl<'a, T: PrimaryKey<'a> + Prefixer<'a>, U: PrimaryKey<'a> + Prefixer<'a>, V: PrimaryKey<'a>> PrimaryKey<'a> for (T, U, V)` - - `PrimaryKey` implemented for unsigned integers up to `u128` - - `PrimaryKey` implemented for signed integers up to `i128` - -That means that byte and string slices, byte vectors, and strings, can be conveniently used as keys. -Moreover, some other types can be used as well, like addresses and address references, pairs, triples, and -integer types. - -If the key represents an address, we suggest using `&Addr` for keys in storage, instead of `String` or string slices. -This implies doing address validation through `addr_validate` on any address passed in via a message, to ensure it's a -legitimate address, and not random text which will fail later. -`pub fn addr_validate(&self, &str) -> Addr` in `deps.api` can be used for address validation, and the returned `Addr` -can then be conveniently used as key in a `Map` or similar structure. - -It's also convenient to use references (i.e. borrowed values) instead of values for keys (i.e. `&Addr` instead of `Addr`), -as that will typically save some cloning during key reading / writing. - -### Composite Keys - -There are times when we want to use multiple items as a key. For example, when -storing allowances based on account owner and spender. We could try to manually -concatenate them before calling, but that can lead to overlap, and is a bit -low-level for us. Also, by explicitly separating the keys, we can easily provide -helpers to do range queries over a prefix, such as "show me all allowances for -one owner" (first part of the composite key). Just like you'd expect from your -favorite database. - -Here's how we use it with composite keys. Just define a tuple as a key and use that -everywhere you used a single key above. - -```rust -// Note the tuple for primary key. We support one slice, or a 2 or 3-tuple. -// Adding longer tuples is possible, but unlikely to be needed. -const ALLOWANCE: Map<(&str, &str), u64> = Map::new("allow"); - -fn demo() -> StdResult<()> { - let mut store = MockStorage::new(); - - // save and load on a composite key - let empty = ALLOWANCE.may_load(&store, ("owner", "spender"))?; - assert_eq!(None, empty); - ALLOWANCE.save(&mut store, ("owner", "spender"), &777)?; - let loaded = ALLOWANCE.load(&store, ("owner", "spender"))?; - assert_eq!(777, loaded); - - // doesn't appear under other key (even if a concat would be the same) - let different = ALLOWANCE.may_load(&store, ("owners", "pender")).unwrap(); - assert_eq!(None, different); - - // simple update - ALLOWANCE.update(&mut store, ("owner", "spender"), |v| { - Ok(v.unwrap_or_default() + 222) - })?; - let loaded = ALLOWANCE.load(&store, ("owner", "spender"))?; - assert_eq!(999, loaded); - - Ok(()) -} -``` - -### Path - -Under the scenes, we create a `Path` from the `Map` when accessing a key. -`PEOPLE.load(&store, "jack") == PEOPLE.key("jack").load()`. -`Map.key()` returns a `Path`, which has the same interface as `Item`, -re-using the calculated path to this key. - -For simple keys, this is just a bit less typing and a bit less gas if you -use the same key for many calls. However, for composite keys, like -`("owner", "spender")` it is **much** less typing. And highly recommended anywhere -you will use a composite key even twice: - -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -struct Data { - pub name: String, - pub age: i32, -} - -const PEOPLE: Map<&str, Data> = Map::new("people"); -const ALLOWANCE: Map<(&str, &str), u64> = Map::new("allow"); - -fn demo() -> StdResult<()> { - let mut store = MockStorage::new(); - let data = Data { - name: "John".to_string(), - age: 32, - }; - - // create a Path one time to use below - let john = PEOPLE.key("john"); - - // Use this just like an Item above - let empty = john.may_load(&store)?; - assert_eq!(None, empty); - john.save(&mut store, &data)?; - let loaded = john.load(&store)?; - assert_eq!(data, loaded); - john.remove(&mut store); - let empty = john.may_load(&store)?; - assert_eq!(None, empty); - - // Same for composite keys, just use both parts in `key()`. - // Notice how much less verbose than the above example. - let allow = ALLOWANCE.key(("owner", "spender")); - allow.save(&mut store, &1234)?; - let loaded = allow.load(&store)?; - assert_eq!(1234, loaded); - allow.update(&mut store, |x| Ok(x.unwrap_or_default() * 2))?; - let loaded = allow.load(&store)?; - assert_eq!(2468, loaded); - - Ok(()) -} -``` - -### Prefix - -In addition to getting one particular item out of a map, we can iterate over the map -(or a subset of the map). This let us answer questions like "show me all tokens", -and we provide some nice [`Bound`](#bound) helpers to easily allow pagination or custom ranges. - -The general format is to get a `Prefix` by calling `map.prefix(k)`, where `k` is exactly -one less item than the normal key (If `map.key()` took `(&[u8], &[u8])`, then `map.prefix()` takes `&[u8]`. -If `map.key()` took `&[u8]`, `map.prefix()` takes `()`). Once we have a prefix space, we can iterate -over all items with `range(store, min, max, order)`. It supports `Order::Ascending` or `Order::Descending`. -`min` is the lower bound and `max` is the higher bound. - -If the `min` and `max` bounds are `None`, `range` will return all items under the prefix. You can use `.take(n)` to -limit the results to `n` items and start doing pagination. You can also set the `min` bound to -eg. `Bound::exclusive(last_value)` to start iterating over all items *after* the last value. Combined with -`take`, we easily have pagination support. You can also use `Bound::inclusive(x)` when you want to include any -perfect matches. - -### Bound - -`Bound` is a helper to build type-safe bounds on the keys or sub-keys you want to iterate over. -It also supports a raw (`Vec`) bounds specification, for the cases you don't want or can't use typed bounds. - -```rust -#[derive(Clone, Debug)] -pub enum Bound<'a, K: PrimaryKey<'a>> { - Inclusive((K, PhantomData<&'a bool>)), - Exclusive((K, PhantomData<&'a bool>)), - InclusiveRaw(Vec), - ExclusiveRaw(Vec), -} -``` - -To better understand the API, please check the following example: -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -struct Data { - pub name: String, - pub age: i32, -} - -const PEOPLE: Map<&str, Data> = Map::new("people"); -const ALLOWANCE: Map<(&str, &str), u64> = Map::new("allow"); - -fn demo() -> StdResult<()> { - let mut store = MockStorage::new(); - - // save and load on two keys - let data = Data { name: "John".to_string(), age: 32 }; - PEOPLE.save(&mut store, "john", &data)?; - let data2 = Data { name: "Jim".to_string(), age: 44 }; - PEOPLE.save(&mut store, "jim", &data2)?; - - // iterate over them all - let all: StdResult> = PEOPLE - .range(&store, None, None, Order::Ascending) - .collect(); - assert_eq!( - all?, - vec![("jim".to_vec(), data2), ("john".to_vec(), data.clone())] - ); - - // or just show what is after jim - let all: StdResult> = PEOPLE - .range( - &store, - Some(Bound::exclusive("jim")), - None, - Order::Ascending, - ) - .collect(); - assert_eq!(all?, vec![("john".to_vec(), data)]); - - // save and load on three keys, one under different owner - ALLOWANCE.save(&mut store, ("owner", "spender"), &1000)?; - ALLOWANCE.save(&mut store, ("owner", "spender2"), &3000)?; - ALLOWANCE.save(&mut store, ("owner2", "spender"), &5000)?; - - // get all under one key - let all: StdResult> = ALLOWANCE - .prefix("owner") - .range(&store, None, None, Order::Ascending) - .collect(); - assert_eq!( - all?, - vec![("spender".to_vec(), 1000), ("spender2".to_vec(), 3000)] - ); - - // Or ranges between two items (even reverse) - let all: StdResult> = ALLOWANCE - .prefix("owner") - .range( - &store, - Some(Bound::exclusive("spender")), - Some(Bound::inclusive("spender2")), - Order::Descending, - ) - .collect(); - assert_eq!(all?, vec![("spender2".to_vec(), 3000)]); - - Ok(()) -} -``` - -**NB**: For properly defining and using type-safe bounds over a `MultiIndex`, see [Type-safe bounds over `MultiIndex`](#type-safe-bounds-over-multiindex), -below. - -## IndexedMap - -Let's see one example of `IndexedMap` definition and usage, originally taken from the `cw721-base` contract. - -### Definition - -```rust -pub struct TokenIndexes<'a> { - pub owner: MultiIndex<'a, Addr, TokenInfo, String>, -} - -impl<'a> IndexList for TokenIndexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.owner]; - Box::new(v.into_iter()) - } -} - -pub fn tokens<'a>() -> IndexedMap<'a, &'a str, TokenInfo, TokenIndexes<'a>> { - let indexes = TokenIndexes { - owner: MultiIndex::new( - |d: &TokenInfo| d.owner.clone(), - "tokens", - "tokens__owner", - ), - }; - IndexedMap::new("tokens", indexes) -} -``` - -Let's discuss this piece by piece: -```rust -pub struct TokenIndexes<'a> { - pub owner: MultiIndex<'a, Addr, TokenInfo, String>, -} -``` - -These are the index definitions. Here there's only one index, called `owner`. There could be more, as public -members of the `TokenIndexes` struct. -We see that the `owner` index is a `MultiIndex`. A multi-index can have repeated values as keys. The primary key is -used internally as the last element of the multi-index key, to disambiguate repeated index values. -Like the name implies, this is an index over tokens, by owner. Given that an owner can have multiple tokens, -we need a `MultiIndex` to be able to list / iterate over all the tokens he has. - -The `TokenInfo` data will originally be stored by `token_id` (which is a string value). -You can see this in the token creation code: -```rust - tokens().update(deps.storage, &msg.token_id, |old| match old { - Some(_) => Err(ContractError::Claimed {}), - None => Ok(token), - })?; -``` -(Incidentally, this is using `update` instead of `save`, to avoid overwriting an already existing token). - -Given that `token_id` is a string value, we specify `String` as the last argument of the `MultiIndex` definition. -That way, the deserialization of the primary key will be done to the right type (an owned string). - -**NB**: In the particular case of a `MultiIndex`, and with the latest implementation of type-safe bounds, the definition of -this last type parameter is crucial, for properly using type-safe bounds. -See [Type-safe bounds over `MultiIndex`](#type-safe-bounds-over-multiindex), below. - -Then, this `TokenInfo` data will be indexed by token `owner` (which is an `Addr`). So that we can list all the tokens -an owner has. That's why the `owner` index key is `Addr`. - -Other important thing here is that the key (and its components, in the case of a composite key) must implement -the `PrimaryKey` trait. You can see that `Addr` does implement `PrimaryKey`: - -```rust -impl<'a> PrimaryKey<'a> for Addr { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - // this is simple, we don't add more prefixes - vec![Key::Ref(self.as_bytes())] - } -} -``` - ---- - -We can now see how it all works, taking a look at the remaining code: - -```rust -impl<'a> IndexList for TokenIndexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.owner]; - Box::new(v.into_iter()) - } -} -``` - -This implements the `IndexList` trait for `TokenIndexes`. - -**NB**: this code is more or less boiler-plate, and needed for the internals. Do not try to customize this; -just return a list of all indexes. -Implementing this trait serves two purposes (which are really one and the same): it allows the indexes -to be queried through `get_indexes`, and, it allows `TokenIndexes` to be treated as an `IndexList`. So that -it can be passed as a parameter during `IndexedMap` construction, below: - -```rust -pub fn tokens<'a>() -> IndexedMap<'a, &'a str, TokenInfo, TokenIndexes<'a>> { - let indexes = TokenIndexes { - owner: MultiIndex::new( - |d: &TokenInfo| d.owner.clone(), - "tokens", - "tokens__owner", - ), - }; - IndexedMap::new("tokens", indexes) -} -``` - -Here `tokens()` is just a helper function, that simplifies the `IndexedMap` construction for us. First the -index (es) is (are) created, and then, the `IndexedMap` is created and returned. - -During index creation, we must supply an index function per index -```rust - owner: MultiIndex::new(|d: &TokenInfo| d.owner.clone(), -``` -which is the one that will take the value of the original map and create the index key from it. -Of course, this requires that the elements required for the index key are present in the value. -Besides the index function, we must also supply the namespace of the pk, and the one for the new index. - ---- - -After that, we just create and return the `IndexedMap`: - -```rust - IndexedMap::new("tokens", indexes) -``` - -Here of course, the namespace of the pk must match the one used during index(es) creation. And, we pass our -`TokenIndexes` (as an `IndexList`-type parameter) as second argument. Connecting in this way the underlying `Map` -for the pk, with the defined indexes. - -So, `IndexedMap` (and the other `Indexed*` types) is just a wrapper / extension around `Map`, that provides -a number of index functions and namespaces to create indexes over the original `Map` data. It also implements -calling these index functions during value storage / update / removal, so that you can forget about it, -and just use the indexed data. - -### Usage - -An example of use, where `owner` is a `String` value passed as a parameter, and `start_after` and `limit` optionally -define the pagination range: - -Notice this uses `prefix()`, explained above in the `Map` section. - -```rust - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.map(Bound::exclusive); - let owner_addr = deps.api.addr_validate(&owner)?; - - let res: Result, _> = tokens() - .idx - .owner - .prefix(owner_addr) - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect(); - let tokens = res?; -``` -Now `tokens` contains `(token_id, TokenInfo)` pairs for the given `owner`. -The pk values are `Vec` in the case of `range_raw()`, but will be deserialized to the proper type using -`range()`; provided that the pk deserialization type (`String`, in this case) is correctly specified -in the `MultiIndex` definition (see [Index keys deserialization](#index-keys-deserialization), -below). - -Another example that is similar, but returning only the (raw) `token_id`s, using the `keys_raw()` method: -```rust - let pks: Vec<_> = tokens() - .idx - .owner - .prefix(owner_addr) - .keys_raw( - deps.storage, - start, - None, - Order::Ascending, - ) - .take(limit) - .collect(); -``` -Now `pks` contains `token_id` values (as raw `Vec`s) for the given `owner`. By using `keys` instead, -a deserialized key can be obtained, as detailed in the next section. - -### Index keys deserialization - -For `UniqueIndex` and `MultiIndex`, the primary key (`PK`) type needs to be specified, in order to deserialize -the primary key to it. -This `PK` type specification is also important for `MultiIndex` type-safe bounds, as the primary key -is part of the multi-index key. See next section, [Type-safe bounds over MultiIndex](#type-safe-bounds-over-multiindex). - -**NB**: This specification is still a manual (and therefore error-prone) process / setup, that will (if possible) -be automated in the future (https://github.com/CosmWasm/cw-plus/issues/531). - -### Type-safe bounds over MultiIndex - -In the particular case of `MultiIndex`, the primary key (`PK`) type parameter also defines the type of the (partial) bounds over -the index key (the part that corresponds to the primary key, that is). -So, to correctly use type-safe bounds over multi-indexes ranges, it is fundamental for this `PK` type -to be correctly defined, so that it matches the primary key type, or its (typically owned) deserialization variant. - -## VecDeque - -The usage of a [`VecDeque`](./src/deque.rs) is pretty straight-forward. -Conceptually it works like a storage-backed version of Rust std's `VecDeque` and can be used as a queue or stack. -It allows you to push and pop elements on both ends and also read the first or last element without mutating the deque. -You can also read a specific index directly. - -Example Usage: - -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -struct Data { - pub name: String, - pub age: i32, -} - -const DATA: Deque = Deque::new("data"); - -fn demo() -> StdResult<()> { - let mut store = MockStorage::new(); - - // read methods return a wrapped Option, so None if the deque is empty - let empty = DATA.front(&store)?; - assert_eq!(None, empty); - - // some example entries - let p1 = Data { - name: "admin".to_string(), - age: 1234, - }; - let p2 = Data { - name: "user".to_string(), - age: 123, - }; - - // use it like a queue by pushing and popping at opposite ends - DATA.push_back(&mut store, &p1)?; - DATA.push_back(&mut store, &p2)?; - - let admin = DATA.pop_front(&mut store)?; - assert_eq!(admin.as_ref(), Some(&p1)); - let user = DATA.pop_front(&mut store)?; - assert_eq!(user.as_ref(), Some(&p2)); - - // or push and pop at the same end to use it as a stack - DATA.push_back(&mut store, &p1)?; - DATA.push_back(&mut store, &p2)?; - - let user = DATA.pop_back(&mut store)?; - assert_eq!(user.as_ref(), Some(&p2)); - let admin = DATA.pop_back(&mut store)?; - assert_eq!(admin.as_ref(), Some(&p1)); - - // you can also iterate over it - DATA.push_front(&mut store, &p1)?; - DATA.push_front(&mut store, &p2)?; - - let all: StdResult> = DATA.iter(&store)?.collect(); - assert_eq!(all?, [p2, p1]); - - // or access an index directly - assert_eq!(DATA.get(&store, 0)?, Some(p2)); - assert_eq!(DATA.get(&store, 1)?, Some(p1)); - assert_eq!(DATA.get(&store, 3)?, None); - - Ok(()) -} -``` diff --git a/packages/storage-plus/benches/main.rs b/packages/storage-plus/benches/main.rs deleted file mode 100644 index a0623257f..000000000 --- a/packages/storage-plus/benches/main.rs +++ /dev/null @@ -1,161 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; - -use rand::Rng; -use std::mem; -use std::time::Duration; - -use cw_storage_plus::IntKey; - -fn bench_signed_int_key(c: &mut Criterion) { - let mut group = c.benchmark_group("Signed int keys"); - - fn k() -> i32 { - // let k: i32 = 0x42434445; - // k - rand::thread_rng().gen_range(i32::MIN..i32::MAX) - } - // For the asserts - let k_check = k(); - - type Buf = [u8; mem::size_of::()]; - - group.bench_function("i32 to_cw_bytes: xored (u32) + to_be_bytes", |b| { - #[inline] - fn to_cw_bytes(value: &i32) -> Buf { - (*value as u32 ^ i32::MIN as u32).to_be_bytes() - } - - assert_eq!(to_cw_bytes(&0), i32::to_cw_bytes(&0)); - assert_eq!(to_cw_bytes(&k_check), i32::to_cw_bytes(&k_check)); - assert_eq!( - to_cw_bytes(&k_check.wrapping_neg()), - i32::to_cw_bytes(&k_check.wrapping_neg()) - ); - - b.iter(|| { - let k = k(); - black_box(to_cw_bytes(&k)); - black_box(to_cw_bytes(&k.wrapping_neg())); - }); - }); - - group.bench_function("i32 to_cw_bytes: xored (u128) + to_be_bytes", |b| { - #[inline] - fn to_cw_bytes(value: &i32) -> Buf { - ((*value as u128 ^ i32::MIN as u128) as i32).to_be_bytes() - } - - assert_eq!(to_cw_bytes(&0), i32::to_cw_bytes(&0)); - assert_eq!(to_cw_bytes(&k_check), i32::to_cw_bytes(&k_check)); - assert_eq!( - to_cw_bytes(&k_check.wrapping_neg()), - i32::to_cw_bytes(&k_check.wrapping_neg()) - ); - - b.iter(|| { - let k = k(); - black_box(to_cw_bytes(&k)); - black_box(to_cw_bytes(&k.wrapping_neg())); - }); - }); - - group.bench_function("i32 to_cw_bytes: mut to_be_bytes + xor", |b| { - #[inline] - fn to_cw_bytes(value: &i32) -> Buf { - let mut buf = i32::to_be_bytes(*value); - buf[0] ^= 0x80; - buf - } - - assert_eq!(to_cw_bytes(&0), i32::to_cw_bytes(&0)); - assert_eq!(to_cw_bytes(&k_check), i32::to_cw_bytes(&k_check)); - assert_eq!( - to_cw_bytes(&k_check.wrapping_neg()), - i32::to_cw_bytes(&k_check.wrapping_neg()) - ); - - b.iter(|| { - let k = k(); - black_box(to_cw_bytes(&k)); - black_box(to_cw_bytes(&k.wrapping_neg())); - }); - }); - - group.bench_function("i32 to_cw_bytes: branching plus / minus", |b| { - #[inline] - fn to_cw_bytes(value: &i32) -> Buf { - if value >= &0i32 { - ((*value as u32).wrapping_sub(i32::MIN as u32)).to_be_bytes() - } else { - ((*value as u32).wrapping_add(i32::MIN as u32)).to_be_bytes() - } - } - - assert_eq!(to_cw_bytes(&0), i32::to_cw_bytes(&0)); - assert_eq!(to_cw_bytes(&k_check), i32::to_cw_bytes(&k_check)); - assert_eq!( - to_cw_bytes(&k_check.wrapping_neg()), - i32::to_cw_bytes(&k_check.wrapping_neg()) - ); - - b.iter(|| { - let k = k(); - black_box(to_cw_bytes(&k)); - black_box(to_cw_bytes(&k.wrapping_neg())); - }); - }); - - group.finish(); -} - -fn bench_unsigned_int_key(c: &mut Criterion) { - let mut group = c.benchmark_group("Unsigned int keys"); - - fn k() -> u32 { - // let k: u32 = 0x42434445; - // k - rand::thread_rng().gen_range(u32::MIN..u32::MAX) - } - // For the asserts - let k_check = k(); - - type Buf = [u8; mem::size_of::()]; - - group.bench_function("u32 to_cw_bytes", |b| { - #[inline] - fn to_cw_bytes(value: &u32) -> Buf { - value.to_be_bytes() - } - - assert_eq!(to_cw_bytes(&0), u32::to_cw_bytes(&0)); - assert_eq!(to_cw_bytes(&k_check), u32::to_cw_bytes(&k_check)); - - b.iter(|| { - let k = k(); - black_box(to_cw_bytes(&k)); - black_box(to_cw_bytes(&k)); // twice for comparability - }); - }); - - group.finish(); -} - -fn make_config() -> Criterion { - Criterion::default() - .without_plots() - .measurement_time(Duration::new(5, 0)) - .sample_size(10) - .configure_from_args() -} - -criterion_group!( - name = signed_int_key; - config = make_config(); - targets = bench_signed_int_key -); -criterion_group!( - name = unsigned_int_key; - config = make_config(); - targets = bench_unsigned_int_key -); -criterion_main!(signed_int_key, unsigned_int_key); diff --git a/packages/storage-plus/src/bound.rs b/packages/storage-plus/src/bound.rs deleted file mode 100644 index 597b569b9..000000000 --- a/packages/storage-plus/src/bound.rs +++ /dev/null @@ -1,184 +0,0 @@ -#![cfg(feature = "iterator")] - -use cosmwasm_std::Addr; -use std::marker::PhantomData; - -use crate::de::KeyDeserialize; -use crate::{Prefixer, PrimaryKey}; - -/// `RawBound` is used to define the two ends of a range, more explicit than `Option`. -/// `None` means that we don't limit that side of the range at all. -/// `Inclusive` means we use the given bytes as a limit and *include* anything at that exact key. -/// `Exclusive` means we use the given bytes as a limit and *exclude* anything at that exact key. -/// See `Bound` for a type safe way to build these bounds. -#[derive(Clone, Debug)] -pub enum RawBound { - Inclusive(Vec), - Exclusive(Vec), -} - -/// `Bound` is used to define the two ends of a range. -/// `None` means that we don't limit that side of the range at all. -/// `Inclusive` means we use the given value as a limit and *include* anything at that exact key. -/// `Exclusive` means we use the given value as a limit and *exclude* anything at that exact key. -#[derive(Clone, Debug)] -pub enum Bound<'a, K: PrimaryKey<'a>> { - Inclusive((K, PhantomData<&'a bool>)), - Exclusive((K, PhantomData<&'a bool>)), - InclusiveRaw(Vec), - ExclusiveRaw(Vec), -} - -impl<'a, K: PrimaryKey<'a>> Bound<'a, K> { - pub fn inclusive>(k: T) -> Self { - Self::Inclusive((k.into(), PhantomData)) - } - - pub fn exclusive>(k: T) -> Self { - Self::Exclusive((k.into(), PhantomData)) - } - - pub fn to_raw_bound(&self) -> RawBound { - match self { - Bound::Inclusive((k, _)) => RawBound::Inclusive(k.joined_key()), - Bound::Exclusive((k, _)) => RawBound::Exclusive(k.joined_key()), - Bound::ExclusiveRaw(raw_k) => RawBound::Exclusive(raw_k.clone()), - Bound::InclusiveRaw(raw_k) => RawBound::Inclusive(raw_k.clone()), - } - } -} - -#[derive(Clone, Debug)] -pub enum PrefixBound<'a, K: Prefixer<'a>> { - Inclusive((K, PhantomData<&'a bool>)), - Exclusive((K, PhantomData<&'a bool>)), -} - -impl<'a, K: Prefixer<'a>> PrefixBound<'a, K> { - pub fn inclusive>(k: T) -> Self { - Self::Inclusive((k.into(), PhantomData)) - } - - pub fn exclusive>(k: T) -> Self { - Self::Exclusive((k.into(), PhantomData)) - } - - pub fn to_raw_bound(&self) -> RawBound { - match self { - PrefixBound::Exclusive((k, _)) => RawBound::Exclusive(k.joined_prefix()), - PrefixBound::Inclusive((k, _)) => RawBound::Inclusive(k.joined_prefix()), - } - } -} - -pub trait Bounder<'a>: PrimaryKey<'a> + Sized { - fn inclusive_bound(self) -> Option>; - fn exclusive_bound(self) -> Option>; -} - -impl<'a> Bounder<'a> for () { - fn inclusive_bound(self) -> Option> { - None - } - fn exclusive_bound(self) -> Option> { - None - } -} - -impl<'a> Bounder<'a> for &'a [u8] { - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -impl< - 'a, - T: PrimaryKey<'a> + KeyDeserialize + Prefixer<'a> + Clone, - U: PrimaryKey<'a> + KeyDeserialize + Clone, - > Bounder<'a> for (T, U) -{ - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -impl< - 'a, - T: PrimaryKey<'a> + Prefixer<'a> + Clone, - U: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize + Clone, - V: PrimaryKey<'a> + KeyDeserialize + Clone, - > Bounder<'a> for (T, U, V) -{ - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -impl<'a> Bounder<'a> for &'a str { - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -impl<'a> Bounder<'a> for String { - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -impl<'a> Bounder<'a> for Vec { - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -impl<'a> Bounder<'a> for &'a Addr { - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -impl<'a> Bounder<'a> for Addr { - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } -} - -macro_rules! integer_bound { - (for $($t:ty),+) => { - $(impl<'a> Bounder<'a> for $t { - fn inclusive_bound(self) -> Option> { - Some(Bound::inclusive(self)) - } - fn exclusive_bound(self) -> Option> { - Some(Bound::exclusive(self)) - } - })* - } -} - -integer_bound!(for i8, u8, i16, u16, i32, u32, i64, u64); diff --git a/packages/storage-plus/src/de.rs b/packages/storage-plus/src/de.rs deleted file mode 100644 index 046d8b9cb..000000000 --- a/packages/storage-plus/src/de.rs +++ /dev/null @@ -1,268 +0,0 @@ -use std::array::TryFromSliceError; -use std::convert::TryInto; - -use cosmwasm_std::{Addr, StdError, StdResult}; - -use crate::int_key::IntKey; - -pub trait KeyDeserialize { - type Output: Sized; - - fn from_vec(value: Vec) -> StdResult; - - fn from_slice(value: &[u8]) -> StdResult { - Self::from_vec(value.to_vec()) - } -} - -impl KeyDeserialize for () { - type Output = (); - - #[inline(always)] - fn from_vec(_value: Vec) -> StdResult { - Ok(()) - } -} - -impl KeyDeserialize for Vec { - type Output = Vec; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Ok(value) - } -} - -impl KeyDeserialize for &Vec { - type Output = Vec; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Ok(value) - } -} - -impl KeyDeserialize for &[u8] { - type Output = Vec; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Ok(value) - } -} - -impl KeyDeserialize for String { - type Output = String; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - String::from_utf8(value).map_err(StdError::invalid_utf8) - } -} - -impl KeyDeserialize for &String { - type Output = String; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Self::Output::from_vec(value) - } -} - -impl KeyDeserialize for &str { - type Output = String; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Self::Output::from_vec(value) - } -} - -impl KeyDeserialize for Addr { - type Output = Addr; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Ok(Addr::unchecked(String::from_vec(value)?)) - } -} - -impl KeyDeserialize for &Addr { - type Output = Addr; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Self::Output::from_vec(value) - } -} - -macro_rules! integer_de { - (for $($t:ty),+) => { - $(impl KeyDeserialize for $t { - type Output = $t; - - #[inline(always)] - fn from_vec(value: Vec) -> StdResult { - Ok(<$t>::from_cw_bytes(value.as_slice().try_into() - .map_err(|err: TryFromSliceError| StdError::generic_err(err.to_string()))?)) - } - })* - } -} - -integer_de!(for i8, u8, i16, u16, i32, u32, i64, u64, i128, u128); - -fn parse_length(value: &[u8]) -> StdResult { - Ok(u16::from_be_bytes( - value - .try_into() - .map_err(|_| StdError::generic_err("Could not read 2 byte length"))?, - ) - .into()) -} - -impl KeyDeserialize for (T, U) { - type Output = (T::Output, U::Output); - - #[inline(always)] - fn from_vec(mut value: Vec) -> StdResult { - let mut tu = value.split_off(2); - let t_len = parse_length(&value)?; - let u = tu.split_off(t_len); - - Ok((T::from_vec(tu)?, U::from_vec(u)?)) - } -} - -impl KeyDeserialize for (T, U, V) { - type Output = (T::Output, U::Output, V::Output); - - #[inline(always)] - fn from_vec(mut value: Vec) -> StdResult { - let mut tuv = value.split_off(2); - let t_len = parse_length(&value)?; - let mut len_uv = tuv.split_off(t_len); - - let mut uv = len_uv.split_off(2); - let u_len = parse_length(&len_uv)?; - let v = uv.split_off(u_len); - - Ok((T::from_vec(tuv)?, U::from_vec(uv)?, V::from_vec(v)?)) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::PrimaryKey; - - const BYTES: &[u8] = b"Hello"; - const STRING: &str = "Hello"; - - #[test] - #[allow(clippy::unit_cmp)] - fn deserialize_empty_works() { - assert_eq!(<()>::from_slice(BYTES).unwrap(), ()); - } - - #[test] - fn deserialize_bytes_works() { - assert_eq!(>::from_slice(BYTES).unwrap(), BYTES); - assert_eq!(<&Vec>::from_slice(BYTES).unwrap(), BYTES); - assert_eq!(<&[u8]>::from_slice(BYTES).unwrap(), BYTES); - } - - #[test] - fn deserialize_string_works() { - assert_eq!(::from_slice(BYTES).unwrap(), STRING); - assert_eq!(<&String>::from_slice(BYTES).unwrap(), STRING); - assert_eq!(<&str>::from_slice(BYTES).unwrap(), STRING); - } - - #[test] - fn deserialize_broken_string_errs() { - assert!(matches!( - ::from_slice(b"\xc3").err(), - Some(StdError::InvalidUtf8 { .. }) - )); - } - - #[test] - fn deserialize_addr_works() { - assert_eq!(::from_slice(BYTES).unwrap(), Addr::unchecked(STRING)); - assert_eq!(<&Addr>::from_slice(BYTES).unwrap(), Addr::unchecked(STRING)); - } - - #[test] - fn deserialize_broken_addr_errs() { - assert!(matches!( - ::from_slice(b"\xc3").err(), - Some(StdError::InvalidUtf8 { .. }) - )); - } - - #[test] - fn deserialize_naked_integer_works() { - assert_eq!(u8::from_slice(&[1]).unwrap(), 1u8); - assert_eq!(i8::from_slice(&[127]).unwrap(), -1i8); - assert_eq!(i8::from_slice(&[128]).unwrap(), 0i8); - - assert_eq!(u16::from_slice(&[1, 0]).unwrap(), 256u16); - assert_eq!(i16::from_slice(&[128, 0]).unwrap(), 0i16); - assert_eq!(i16::from_slice(&[127, 255]).unwrap(), -1i16); - - assert_eq!(u32::from_slice(&[1, 0, 0, 0]).unwrap(), 16777216u32); - assert_eq!(i32::from_slice(&[128, 0, 0, 0]).unwrap(), 0i32); - assert_eq!(i32::from_slice(&[127, 255, 255, 255]).unwrap(), -1i32); - - assert_eq!( - u64::from_slice(&[1, 0, 0, 0, 0, 0, 0, 0]).unwrap(), - 72057594037927936u64 - ); - assert_eq!(i64::from_slice(&[128, 0, 0, 0, 0, 0, 0, 0]).unwrap(), 0i64); - assert_eq!( - i64::from_slice(&[127, 255, 255, 255, 255, 255, 255, 255]).unwrap(), - -1i64 - ); - - assert_eq!( - u128::from_slice(&[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap(), - 1329227995784915872903807060280344576u128 - ); - assert_eq!( - i128::from_slice(&[128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap(), - 0i128 - ); - assert_eq!( - i128::from_slice(&[ - 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 - ]) - .unwrap(), - -1i128 - ); - assert_eq!( - i128::from_slice(&[ - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 - ]) - .unwrap(), - 170141183460469231731687303715884105727i128, - ); - } - - #[test] - fn deserialize_tuple_works() { - assert_eq!( - <(&[u8], &str)>::from_slice((BYTES, STRING).joined_key().as_slice()).unwrap(), - (BYTES.to_vec(), STRING.to_string()) - ); - } - - #[test] - fn deserialize_triple_works() { - assert_eq!( - <(&[u8], u32, &str)>::from_slice((BYTES, 1234u32, STRING).joined_key().as_slice()) - .unwrap(), - (BYTES.to_vec(), 1234, STRING.to_string()) - ); - } -} diff --git a/packages/storage-plus/src/deque.rs b/packages/storage-plus/src/deque.rs deleted file mode 100644 index 01932a5ea..000000000 --- a/packages/storage-plus/src/deque.rs +++ /dev/null @@ -1,627 +0,0 @@ -use std::{any::type_name, convert::TryInto, marker::PhantomData}; - -use cosmwasm_std::{to_vec, StdError, StdResult, Storage}; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::helpers::{may_deserialize, namespaces_with_key}; - -// metadata keys need to have different length than the position type (4 bytes) to prevent collisions -const TAIL_KEY: &[u8] = b"t"; -const HEAD_KEY: &[u8] = b"h"; - -/// A deque stores multiple items at the given key. It provides efficient FIFO and LIFO access, -/// as well as direct index access. -/// -/// It has a maximum capacity of `u32::MAX - 1`. Make sure to never exceed that number when using this type. -/// If you do, the methods won't work as intended anymore. -pub struct Deque<'a, T> { - // prefix of the deque items - namespace: &'a [u8], - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - item_type: PhantomData, -} - -impl<'a, T> Deque<'a, T> { - pub const fn new(prefix: &'a str) -> Self { - Self { - namespace: prefix.as_bytes(), - item_type: PhantomData, - } - } -} - -impl<'a, T: Serialize + DeserializeOwned> Deque<'a, T> { - /// Adds the given value to the end of the deque - pub fn push_back(&self, storage: &mut dyn Storage, value: &T) -> StdResult<()> { - // save value - let pos = self.tail(storage)?; - self.set_unchecked(storage, pos, value)?; - // update tail - self.set_tail(storage, pos.wrapping_add(1)); - - Ok(()) - } - - /// Adds the given value to the front of the deque - pub fn push_front(&self, storage: &mut dyn Storage, value: &T) -> StdResult<()> { - // need to subtract first, because head potentially points to existing element - let pos = self.head(storage)?.wrapping_sub(1); - self.set_unchecked(storage, pos, value)?; - // update head - self.set_head(storage, pos); - - Ok(()) - } - - /// Removes the last element of the deque and returns it - pub fn pop_back(&self, storage: &mut dyn Storage) -> StdResult> { - // get position - let pos = self.tail(storage)?.wrapping_sub(1); - let value = self.get_unchecked(storage, pos)?; - if value.is_some() { - self.remove_unchecked(storage, pos); - // only update tail if a value was popped - self.set_tail(storage, pos); - } - Ok(value) - } - - /// Removes the first element of the deque and returns it - pub fn pop_front(&self, storage: &mut dyn Storage) -> StdResult> { - // get position - let pos = self.head(storage)?; - let value = self.get_unchecked(storage, pos)?; - if value.is_some() { - self.remove_unchecked(storage, pos); - // only update head if a value was popped - self.set_head(storage, pos.wrapping_add(1)); - } - Ok(value) - } - - /// Returns the first element of the deque without removing it - pub fn front(&self, storage: &dyn Storage) -> StdResult> { - let pos = self.head(storage)?; - self.get_unchecked(storage, pos) - } - - /// Returns the first element of the deque without removing it - pub fn back(&self, storage: &dyn Storage) -> StdResult> { - let pos = self.tail(storage)?.wrapping_sub(1); - self.get_unchecked(storage, pos) - } - - /// Gets the length of the deque. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &dyn Storage) -> StdResult { - Ok(calc_len(self.head(storage)?, self.tail(storage)?)) - } - - /// Returns `true` if the deque contains no elements. - pub fn is_empty(&self, storage: &dyn Storage) -> StdResult { - Ok(self.len(storage)? == 0) - } - - /// Gets the head position from storage. - /// - /// Unless the deque is empty, this points to the first element. - #[inline] - fn head(&self, storage: &dyn Storage) -> StdResult { - self.read_meta_key(storage, HEAD_KEY) - } - - /// Gets the tail position from storage. - /// - /// This points to the first empty position after the last element. - #[inline] - fn tail(&self, storage: &dyn Storage) -> StdResult { - self.read_meta_key(storage, TAIL_KEY) - } - - #[inline] - fn set_head(&self, storage: &mut dyn Storage, value: u32) { - self.set_meta_key(storage, HEAD_KEY, value); - } - - #[inline] - fn set_tail(&self, storage: &mut dyn Storage, value: u32) { - self.set_meta_key(storage, TAIL_KEY, value); - } - - /// Helper method for `tail` and `head` methods to handle reading the value from storage - fn read_meta_key(&self, storage: &dyn Storage, key: &[u8]) -> StdResult { - let full_key = namespaces_with_key(&[self.namespace], key); - storage - .get(&full_key) - .map(|vec| { - Ok(u32::from_be_bytes( - vec.as_slice() - .try_into() - .map_err(|e| StdError::parse_err("u32", e))?, - )) - }) - .unwrap_or(Ok(0)) - } - - /// Helper method for `set_tail` and `set_head` methods to write to storage - #[inline] - fn set_meta_key(&self, storage: &mut dyn Storage, key: &[u8], value: u32) { - let full_key = namespaces_with_key(&[self.namespace], key); - storage.set(&full_key, &value.to_be_bytes()); - } - - /// Returns the value at the given position in the queue or `None` if the index is out of bounds - pub fn get(&self, storage: &dyn Storage, pos: u32) -> StdResult> { - let head = self.head(storage)?; - let tail = self.tail(storage)?; - - if pos >= calc_len(head, tail) { - // out of bounds - return Ok(None); - } - - let pos = head.wrapping_add(pos); - self.get_unchecked(storage, pos) - .and_then(|v| v.ok_or_else(|| StdError::not_found(format!("deque position {}", pos)))) - .map(Some) - } - - /// Tries to get the value at the given position - /// Used internally - fn get_unchecked(&self, storage: &dyn Storage, pos: u32) -> StdResult> { - let prefixed_key = namespaces_with_key(&[self.namespace], &pos.to_be_bytes()); - may_deserialize(&storage.get(&prefixed_key)) - } - - /// Removes the value at the given position - /// Used internally - fn remove_unchecked(&self, storage: &mut dyn Storage, pos: u32) { - let prefixed_key = namespaces_with_key(&[self.namespace], &pos.to_be_bytes()); - storage.remove(&prefixed_key); - } - - /// Tries to set the value at the given position - /// Used internally when pushing - fn set_unchecked(&self, storage: &mut dyn Storage, pos: u32, value: &T) -> StdResult<()> { - let prefixed_key = namespaces_with_key(&[self.namespace], &pos.to_be_bytes()); - storage.set(&prefixed_key, &to_vec(value)?); - - Ok(()) - } -} - -// used internally to avoid additional storage loads -#[inline] -fn calc_len(head: u32, tail: u32) -> u32 { - tail.wrapping_sub(head) -} - -impl<'a, T: Serialize + DeserializeOwned> Deque<'a, T> { - pub fn iter(&self, storage: &'a dyn Storage) -> StdResult> { - Ok(DequeIter { - deque: self, - storage, - start: self.head(storage)?, - end: self.tail(storage)?, - }) - } -} - -pub struct DequeIter<'a, T> -where - T: Serialize + DeserializeOwned, -{ - deque: &'a Deque<'a, T>, - storage: &'a dyn Storage, - start: u32, - end: u32, -} - -impl<'a, T> Iterator for DequeIter<'a, T> -where - T: Serialize + DeserializeOwned, -{ - type Item = StdResult; - - fn next(&mut self) -> Option { - if self.start == self.end { - return None; - } - - let item = self - .deque - .get_unchecked(self.storage, self.start) - .and_then(|item| item.ok_or_else(|| StdError::not_found(type_name::()))); - self.start = self.start.wrapping_add(1); - - Some(item) - } - - fn size_hint(&self) -> (usize, Option) { - let len = calc_len(self.start, self.end) as usize; - (len, Some(len)) - } - - // The default implementation calls `next` repeatedly, which is very costly in our case. - // It is used when skipping over items, so this allows cheap skipping. - // - // Once `advance_by` is stabilized, we can implement that instead (`nth` calls it internally). - fn nth(&mut self, n: usize) -> Option { - // make sure that we don't skip past the end - if calc_len(self.start, self.end) < n as u32 { - // mark as empty - self.start = self.end; - } else { - self.start = self.start.wrapping_add(n as u32); - } - self.next() - } -} - -impl<'a, T> DoubleEndedIterator for DequeIter<'a, T> -where - T: Serialize + DeserializeOwned, -{ - fn next_back(&mut self) -> Option { - if self.start == self.end { - return None; - } - - let item = self - .deque - .get_unchecked(self.storage, self.end.wrapping_sub(1)) // end points to position after last element - .and_then(|item| item.ok_or_else(|| StdError::not_found(type_name::()))); - self.end = self.end.wrapping_sub(1); - - Some(item) - } - - // see [`DequeIter::nth`] - fn nth_back(&mut self, n: usize) -> Option { - // make sure that we don't skip past the start - if calc_len(self.start, self.end) < n as u32 { - // mark as empty - self.end = self.start; - } else { - self.end = self.end.wrapping_sub(n as u32); - } - self.next_back() - } -} -#[cfg(test)] -mod tests { - use crate::deque::Deque; - - use cosmwasm_std::testing::MockStorage; - use cosmwasm_std::{StdError, StdResult}; - use serde::{Deserialize, Serialize}; - - #[test] - fn push_and_pop() { - const PEOPLE: Deque = Deque::new("people"); - let mut store = MockStorage::new(); - - // push some entries - PEOPLE.push_back(&mut store, &"jack".to_owned()).unwrap(); - PEOPLE.push_back(&mut store, &"john".to_owned()).unwrap(); - PEOPLE.push_back(&mut store, &"joanne".to_owned()).unwrap(); - - // pop them, should be in correct order - assert_eq!("jack", PEOPLE.pop_front(&mut store).unwrap().unwrap()); - assert_eq!("john", PEOPLE.pop_front(&mut store).unwrap().unwrap()); - - // push again in-between - PEOPLE.push_back(&mut store, &"jason".to_owned()).unwrap(); - - // pop last person from first batch - assert_eq!("joanne", PEOPLE.pop_front(&mut store).unwrap().unwrap()); - - // pop the entry pushed in-between - assert_eq!("jason", PEOPLE.pop_front(&mut store).unwrap().unwrap()); - - // nothing after that - assert_eq!(None, PEOPLE.pop_front(&mut store).unwrap()); - - // now push to the front - PEOPLE.push_front(&mut store, &"pascal".to_owned()).unwrap(); - PEOPLE.push_front(&mut store, &"peter".to_owned()).unwrap(); - PEOPLE.push_front(&mut store, &"paul".to_owned()).unwrap(); - - assert_eq!("pascal", PEOPLE.pop_back(&mut store).unwrap().unwrap()); - assert_eq!("paul", PEOPLE.pop_front(&mut store).unwrap().unwrap()); - assert_eq!("peter", PEOPLE.pop_back(&mut store).unwrap().unwrap()); - } - - #[test] - fn length() { - let deque: Deque = Deque::new("test"); - let mut store = MockStorage::new(); - - assert_eq!(deque.len(&store).unwrap(), 0); - assert!(deque.is_empty(&store).unwrap()); - - // push some entries - deque.push_front(&mut store, &1234).unwrap(); - deque.push_back(&mut store, &2345).unwrap(); - deque.push_front(&mut store, &3456).unwrap(); - deque.push_back(&mut store, &4567).unwrap(); - assert_eq!(deque.len(&store).unwrap(), 4); - assert!(!deque.is_empty(&store).unwrap()); - - // pop some - deque.pop_front(&mut store).unwrap(); - deque.pop_back(&mut store).unwrap(); - deque.pop_front(&mut store).unwrap(); - assert_eq!(deque.len(&store).unwrap(), 1); - assert!(!deque.is_empty(&store).unwrap()); - - // pop the last one - deque.pop_front(&mut store).unwrap(); - assert_eq!(deque.len(&store).unwrap(), 0); - assert!(deque.is_empty(&store).unwrap()); - - // should stay 0 after that - assert_eq!(deque.pop_back(&mut store).unwrap(), None); - assert_eq!( - deque.len(&store).unwrap(), - 0, - "popping from empty deque should keep length 0" - ); - assert!(deque.is_empty(&store).unwrap()); - } - - #[test] - fn iterator() { - let deque: Deque = Deque::new("test"); - let mut store = MockStorage::new(); - - // push some items - deque.push_back(&mut store, &1).unwrap(); - deque.push_back(&mut store, &2).unwrap(); - deque.push_back(&mut store, &3).unwrap(); - deque.push_back(&mut store, &4).unwrap(); - - let items: StdResult> = deque.iter(&store).unwrap().collect(); - assert_eq!(items.unwrap(), [1, 2, 3, 4]); - - // nth should work correctly - let mut iter = deque.iter(&store).unwrap(); - assert_eq!(iter.nth(6), None); - assert_eq!(iter.start, iter.end, "iter should detect skipping too far"); - assert_eq!(iter.next(), None); - - let mut iter = deque.iter(&store).unwrap(); - assert_eq!(iter.nth(1).unwrap().unwrap(), 2); - assert_eq!(iter.next().unwrap().unwrap(), 3); - } - - #[test] - fn reverse_iterator() { - let deque: Deque = Deque::new("test"); - let mut store = MockStorage::new(); - - // push some items - deque.push_back(&mut store, &1).unwrap(); - deque.push_back(&mut store, &2).unwrap(); - deque.push_back(&mut store, &3).unwrap(); - deque.push_back(&mut store, &4).unwrap(); - - let items: StdResult> = deque.iter(&store).unwrap().rev().collect(); - assert_eq!(items.unwrap(), [4, 3, 2, 1]); - - // nth should work correctly - let mut iter = deque.iter(&store).unwrap(); - assert_eq!(iter.nth_back(6), None); - assert_eq!(iter.start, iter.end, "iter should detect skipping too far"); - assert_eq!(iter.next_back(), None); - - let mut iter = deque.iter(&store).unwrap().rev(); - assert_eq!(iter.nth(1).unwrap().unwrap(), 3); - assert_eq!(iter.next().unwrap().unwrap(), 2); - - // mixed - let mut iter = deque.iter(&store).unwrap(); - assert_eq!(iter.next().unwrap().unwrap(), 1); - assert_eq!(iter.next_back().unwrap().unwrap(), 4); - assert_eq!(iter.next_back().unwrap().unwrap(), 3); - assert_eq!(iter.next().unwrap().unwrap(), 2); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_back(), None); - } - - #[test] - fn wrapping() { - let deque: Deque = Deque::new("test"); - let mut store = MockStorage::new(); - - // simulate deque that was pushed and popped `u32::MAX` times - deque.set_head(&mut store, u32::MAX); - deque.set_tail(&mut store, u32::MAX); - - // should be empty - assert_eq!(deque.pop_front(&mut store).unwrap(), None); - assert_eq!(deque.len(&store).unwrap(), 0); - - // pushing should still work - deque.push_back(&mut store, &1).unwrap(); - assert_eq!( - deque.len(&store).unwrap(), - 1, - "length should calculate correctly, even when wrapping" - ); - assert_eq!( - deque.pop_front(&mut store).unwrap(), - Some(1), - "popping should work, even when wrapping" - ); - } - - #[test] - fn wrapping_iterator() { - let deque: Deque = Deque::new("test"); - let mut store = MockStorage::new(); - - deque.set_head(&mut store, u32::MAX); - deque.set_tail(&mut store, u32::MAX); - - deque.push_back(&mut store, &1).unwrap(); - deque.push_back(&mut store, &2).unwrap(); - deque.push_back(&mut store, &3).unwrap(); - deque.push_back(&mut store, &4).unwrap(); - deque.push_back(&mut store, &5).unwrap(); - - let mut iter = deque.iter(&store).unwrap(); - assert_eq!(iter.next().unwrap().unwrap(), 1); - assert_eq!(iter.next().unwrap().unwrap(), 2); - assert_eq!(iter.next_back().unwrap().unwrap(), 5); - assert_eq!(iter.nth(1).unwrap().unwrap(), 4); - assert_eq!(iter.nth(1), None); - assert_eq!(iter.start, iter.end); - } - - #[test] - fn front_back() { - let deque: Deque = Deque::new("test"); - let mut store = MockStorage::new(); - - assert_eq!(deque.back(&store).unwrap(), None); - deque.push_back(&mut store, &1).unwrap(); - assert_eq!(deque.back(&store).unwrap(), Some(1)); - assert_eq!(deque.front(&store).unwrap(), Some(1)); - deque.push_back(&mut store, &2).unwrap(); - assert_eq!(deque.back(&store).unwrap(), Some(2)); - assert_eq!(deque.front(&store).unwrap(), Some(1)); - deque.push_front(&mut store, &3).unwrap(); - assert_eq!(deque.back(&store).unwrap(), Some(2)); - assert_eq!(deque.front(&store).unwrap(), Some(3)); - } - - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] - struct Data { - pub name: String, - pub age: i32, - } - - const DATA: Deque = Deque::new("data"); - - #[test] - fn readme_works() -> StdResult<()> { - let mut store = MockStorage::new(); - - // read methods return a wrapped Option, so None if the deque is empty - let empty = DATA.front(&store)?; - assert_eq!(None, empty); - - // some example entries - let p1 = Data { - name: "admin".to_string(), - age: 1234, - }; - let p2 = Data { - name: "user".to_string(), - age: 123, - }; - - // use it like a queue by pushing and popping at opposite ends - DATA.push_back(&mut store, &p1)?; - DATA.push_back(&mut store, &p2)?; - - let admin = DATA.pop_front(&mut store)?; - assert_eq!(admin.as_ref(), Some(&p1)); - let user = DATA.pop_front(&mut store)?; - assert_eq!(user.as_ref(), Some(&p2)); - - // or push and pop at the same end to use it as a stack - DATA.push_back(&mut store, &p1)?; - DATA.push_back(&mut store, &p2)?; - - let user = DATA.pop_back(&mut store)?; - assert_eq!(user.as_ref(), Some(&p2)); - let admin = DATA.pop_back(&mut store)?; - assert_eq!(admin.as_ref(), Some(&p1)); - - // you can also iterate over it - DATA.push_front(&mut store, &p1)?; - DATA.push_front(&mut store, &p2)?; - - let all: StdResult> = DATA.iter(&store)?.collect(); - assert_eq!(all?, [p2.clone(), p1.clone()]); - - // or access an index directly - assert_eq!(DATA.get(&store, 0)?, Some(p2)); - assert_eq!(DATA.get(&store, 1)?, Some(p1)); - assert_eq!(DATA.get(&store, 3)?, None); - - Ok(()) - } - - #[test] - fn iterator_errors_when_item_missing() { - let mut store = MockStorage::new(); - - let deque = Deque::new("error_test"); - - deque.push_back(&mut store, &1u32).unwrap(); - // manually remove it - deque.remove_unchecked(&mut store, 0); - - let mut iter = deque.iter(&store).unwrap(); - - assert!( - matches!(iter.next(), Some(Err(StdError::NotFound { .. }))), - "iterator should error when item is missing" - ); - - let mut iter = deque.iter(&store).unwrap().rev(); - - assert!( - matches!(iter.next(), Some(Err(StdError::NotFound { .. }))), - "reverse iterator should error when item is missing" - ); - } - - #[test] - fn get() { - let mut store = MockStorage::new(); - - let deque = Deque::new("test"); - - deque.push_back(&mut store, &1u32).unwrap(); - deque.push_back(&mut store, &2).unwrap(); - - assert_eq!(deque.get(&store, 0).unwrap(), Some(1)); - assert_eq!(deque.get(&store, 1).unwrap(), Some(2)); - assert_eq!( - deque.get(&store, 2).unwrap(), - None, - "out of bounds access should return None" - ); - - // manually remove storage item - deque.remove_unchecked(&mut store, 1); - - assert!( - matches!(deque.get(&store, 1), Err(StdError::NotFound { .. })), - "missing deque item should error" - ); - - // start fresh - let deque = Deque::new("test2"); - - deque.push_back(&mut store, &0u32).unwrap(); - deque.push_back(&mut store, &1).unwrap(); - // push to front to move the head index - deque.push_front(&mut store, &u32::MAX).unwrap(); - deque.push_front(&mut store, &(u32::MAX - 1)).unwrap(); - - assert_eq!(deque.get(&store, 0).unwrap().unwrap(), u32::MAX - 1); - assert_eq!(deque.get(&store, 1).unwrap().unwrap(), u32::MAX); - assert_eq!(deque.get(&store, 2).unwrap().unwrap(), 0); - assert_eq!(deque.get(&store, 3).unwrap().unwrap(), 1); - assert_eq!( - deque.get(&store, 5).unwrap(), - None, - "out of bounds access should return None" - ); - } -} diff --git a/packages/storage-plus/src/endian.rs b/packages/storage-plus/src/endian.rs deleted file mode 100644 index eeab87845..000000000 --- a/packages/storage-plus/src/endian.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! This code is inspired by (and partially borrowed from) -//! https://docs.rs/endiannezz/0.5.2/endiannezz/trait.Primitive.html -//! but there was a lot in that crate I did not want, the name did not inspire -//! confidence, and I wanted a different return value, so I just took the code -//! to modify slightly. - -// TODO: figure out these macros and let us replace (self: Self) with self -#![allow(clippy::needless_arbitrary_self_type)] - -use std::mem; - -pub trait Endian: Sized + Copy { - type Buf: AsRef<[u8]> + AsMut<[u8]> + Into> + Default; - - fn to_le_bytes(self) -> Self::Buf; - fn to_be_bytes(self) -> Self::Buf; - - fn from_le_bytes(bytes: Self::Buf) -> Self; - fn from_be_bytes(bytes: Self::Buf) -> Self; -} - -macro_rules! delegate { - ($ty:ty, [$($method:ident),* $(,)?], ($param:ident : $param_ty:ty) -> $ret:ty) => { - delegate!(@inner $ty, [$($method),*], $param, $param_ty, $ret); - }; - (@inner $ty:ty, [$($method:ident),*], $param:ident, $param_ty:ty, $ret:ty) => { - $( - #[inline] - fn $method ($param: $param_ty) -> $ret { <$ty>::$method($param) } - )* - }; -} - -macro_rules! impl_primitives { - ($($ty:ty),* $(,)?) => { - $( - impl Endian for $ty { - type Buf = [u8; mem::size_of::<$ty>()]; - - delegate!($ty, [ - to_le_bytes, - to_be_bytes, - ], (self: Self) -> Self::Buf); - - delegate!($ty, [ - from_le_bytes, - from_be_bytes, - ], (bytes: Self::Buf) -> Self); - } - )* - }; -} - -#[rustfmt::skip] -impl_primitives![ - i8, i16, i32, i64, i128, - u8, u16, u32, u64, u128, -]; diff --git a/packages/storage-plus/src/helpers.rs b/packages/storage-plus/src/helpers.rs deleted file mode 100644 index 701b014fe..000000000 --- a/packages/storage-plus/src/helpers.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! This module is an implemention of a namespacing scheme described -//! in https://github.com/webmaster128/key-namespacing#length-prefixed-keys -//! -//! Everything in this file is only responsible for building such keys -//! and is in no way specific to any kind of storage. - -use serde::de::DeserializeOwned; -use std::any::type_name; - -use crate::keys::Key; - -use cosmwasm_std::{ - from_slice, to_vec, Addr, Binary, ContractResult, CustomQuery, QuerierWrapper, QueryRequest, - StdError, StdResult, SystemResult, WasmQuery, -}; - -/// may_deserialize parses json bytes from storage (Option), returning Ok(None) if no data present -/// -/// value is an odd type, but this is meant to be easy to use with output from storage.get (Option>) -/// and value.map(|s| s.as_slice()) seems trickier than &value -pub(crate) fn may_deserialize( - value: &Option>, -) -> StdResult> { - match value { - Some(vec) => Ok(Some(from_slice(vec)?)), - None => Ok(None), - } -} - -/// must_deserialize parses json bytes from storage (Option), returning NotFound error if no data present -pub(crate) fn must_deserialize(value: &Option>) -> StdResult { - match value { - Some(vec) => from_slice(vec), - None => Err(StdError::not_found(type_name::())), - } -} - -/// This is equivalent concat(to_length_prefixed_nested(namespaces), key) -/// But more efficient when the intermediate namespaces often must be recalculated -pub(crate) fn namespaces_with_key(namespaces: &[&[u8]], key: &[u8]) -> Vec { - let mut size = key.len(); - for &namespace in namespaces { - size += namespace.len() + 2; - } - - let mut out = Vec::with_capacity(size); - for &namespace in namespaces { - out.extend_from_slice(&encode_length(namespace)); - out.extend_from_slice(namespace); - } - out.extend_from_slice(key); - out -} - -/// Customization of namespaces_with_key for when -/// there are multiple sets we do not want to combine just to call this -pub(crate) fn nested_namespaces_with_key( - top_names: &[&[u8]], - sub_names: &[Key], - key: &[u8], -) -> Vec { - let mut size = key.len(); - for &namespace in top_names { - size += namespace.len() + 2; - } - for namespace in sub_names { - size += namespace.as_ref().len() + 2; - } - - let mut out = Vec::with_capacity(size); - for &namespace in top_names { - out.extend_from_slice(&encode_length(namespace)); - out.extend_from_slice(namespace); - } - for namespace in sub_names { - out.extend_from_slice(&encode_length(namespace.as_ref())); - out.extend_from_slice(namespace.as_ref()); - } - out.extend_from_slice(key); - out -} - -/// Encodes the length of a given namespace as a 2 byte big endian encoded integer -pub(crate) fn encode_length(namespace: &[u8]) -> [u8; 2] { - if namespace.len() > 0xFFFF { - panic!("only supports namespaces up to length 0xFFFF") - } - let length_bytes = (namespace.len() as u32).to_be_bytes(); - [length_bytes[2], length_bytes[3]] -} - -/// Use this in Map/SnapshotMap/etc when you want to provide a QueryRaw helper. -/// This is similar to querier.query(WasmQuery::Raw{}), except it does NOT parse the -/// result, but return a possibly empty Binary to be handled by the calling code. -/// That is essential to handle b"" as None. -pub(crate) fn query_raw( - querier: &QuerierWrapper, - contract_addr: Addr, - key: Binary, -) -> StdResult { - let request: QueryRequest = WasmQuery::Raw { - contract_addr: contract_addr.into(), - key, - } - .into(); - - let raw = to_vec(&request).map_err(|serialize_err| { - StdError::generic_err(format!("Serializing QueryRequest: {}", serialize_err)) - })?; - match 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(value), - } -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::{to_vec, StdError}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Person { - pub name: String, - pub age: i32, - } - - #[test] - fn encode_length_works() { - assert_eq!(encode_length(b""), *b"\x00\x00"); - assert_eq!(encode_length(b"a"), *b"\x00\x01"); - assert_eq!(encode_length(b"aa"), *b"\x00\x02"); - assert_eq!(encode_length(b"aaa"), *b"\x00\x03"); - assert_eq!(encode_length(&vec![1; 255]), *b"\x00\xff"); - assert_eq!(encode_length(&vec![1; 256]), *b"\x01\x00"); - assert_eq!(encode_length(&vec![1; 12345]), *b"\x30\x39"); - assert_eq!(encode_length(&vec![1; 65535]), *b"\xff\xff"); - } - - #[test] - #[should_panic(expected = "only supports namespaces up to length 0xFFFF")] - fn encode_length_panics_for_large_values() { - encode_length(&vec![1; 65536]); - } - - #[test] - fn may_deserialize_handles_some() { - let person = Person { - name: "Maria".to_string(), - age: 42, - }; - let value = to_vec(&person).unwrap(); - - let may_parse: Option = may_deserialize(&Some(value)).unwrap(); - assert_eq!(may_parse, Some(person)); - } - - #[test] - fn may_deserialize_handles_none() { - let may_parse = may_deserialize::(&None).unwrap(); - assert_eq!(may_parse, None); - } - - #[test] - fn must_deserialize_handles_some() { - let person = Person { - name: "Maria".to_string(), - age: 42, - }; - let value = to_vec(&person).unwrap(); - let loaded = Some(value); - - let parsed: Person = must_deserialize(&loaded).unwrap(); - assert_eq!(parsed, person); - } - - #[test] - fn must_deserialize_handles_none() { - let parsed = must_deserialize::(&None); - match parsed.unwrap_err() { - StdError::NotFound { kind, .. } => { - assert_eq!(kind, "cw_storage_plus::helpers::test::Person") - } - e => panic!("Unexpected error {}", e), - } - } -} diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs deleted file mode 100644 index 4ea2e4e27..000000000 --- a/packages/storage-plus/src/indexed_map.rs +++ /dev/null @@ -1,1735 +0,0 @@ -// this module requires iterator to be useful at all -#![cfg(feature = "iterator")] - -use crate::PrefixBound; -use cosmwasm_std::{StdError, StdResult, Storage}; -use serde::de::DeserializeOwned; -use serde::Serialize; - -use crate::de::KeyDeserialize; -use crate::indexes::Index; -use crate::iter_helpers::{deserialize_kv, deserialize_v}; -use crate::keys::{Prefixer, PrimaryKey}; -use crate::map::Map; -use crate::prefix::{namespaced_prefix_range, Prefix}; -use crate::{Bound, Path}; - -pub trait IndexList { - fn get_indexes(&'_ self) -> Box> + '_>; -} - -// TODO: remove traits here and make this const fn new -/// `IndexedMap` works like a `Map` but has a secondary index -pub struct IndexedMap<'a, K, T, I> -where - K: PrimaryKey<'a>, - T: Serialize + DeserializeOwned + Clone, - I: IndexList, -{ - pk_namespace: &'a [u8], - primary: Map<'a, K, T>, - /// This is meant to be read directly to get the proper types, like: - /// map.idx.owner.items(...) - pub idx: I, -} - -impl<'a, K, T, I> IndexedMap<'a, K, T, I> -where - K: PrimaryKey<'a>, - T: Serialize + DeserializeOwned + Clone, - I: IndexList, -{ - // TODO: remove traits here and make this const fn new - pub fn new(pk_namespace: &'a str, indexes: I) -> Self { - IndexedMap { - pk_namespace: pk_namespace.as_bytes(), - primary: Map::new(pk_namespace), - idx: indexes, - } - } - - pub fn key(&self, k: K) -> Path { - self.primary.key(k) - } -} - -impl<'a, K, T, I> IndexedMap<'a, K, T, I> -where - K: PrimaryKey<'a>, - T: Serialize + DeserializeOwned + Clone, - I: IndexList, -{ - /// save will serialize the model and store, returns an error on serialization issues. - /// this must load the old value to update the indexes properly - /// if you loaded the old value earlier in the same function, use replace to avoid needless db reads - pub fn save(&self, store: &mut dyn Storage, key: K, data: &T) -> StdResult<()> { - let old_data = self.may_load(store, key.clone())?; - self.replace(store, key, Some(data), old_data.as_ref()) - } - - pub fn remove(&self, store: &mut dyn Storage, key: K) -> StdResult<()> { - let old_data = self.may_load(store, key.clone())?; - self.replace(store, key, None, old_data.as_ref()) - } - - /// replace writes data to key. old_data must be the current stored value (from a previous load) - /// and is used to properly update the index. This is used by save, replace, and update - /// and can be called directly if you want to optimize - pub fn replace( - &self, - store: &mut dyn Storage, - key: K, - data: Option<&T>, - old_data: Option<&T>, - ) -> StdResult<()> { - // this is the key *relative* to the primary map namespace - let pk = key.joined_key(); - if let Some(old) = old_data { - for index in self.idx.get_indexes() { - index.remove(store, &pk, old)?; - } - } - if let Some(updated) = data { - for index in self.idx.get_indexes() { - index.save(store, &pk, updated)?; - } - self.primary.save(store, key, updated)?; - } else { - self.primary.remove(store, key); - } - Ok(()) - } - - /// 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. - /// - /// If the data exists, `action(Some(value))` is called. Otherwise `action(None)` is called. - pub fn update(&self, store: &mut dyn Storage, key: K, action: A) -> Result - where - A: FnOnce(Option) -> Result, - E: From, - { - let input = self.may_load(store, key.clone())?; - let old_val = input.clone(); - let output = action(input)?; - self.replace(store, key, Some(&output), old_val.as_ref())?; - Ok(output) - } - - // Everything else, that doesn't touch indexers, is just pass-through from self.core, - // thus can be used from while iterating over indexes - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, store: &dyn Storage, key: K) -> StdResult { - self.primary.load(store, key) - } - - /// 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, store: &dyn Storage, key: K) -> StdResult> { - self.primary.may_load(store, key) - } - - /// Returns true if storage contains this key, without parsing or interpreting the contents. - pub fn has(&self, store: &dyn Storage, k: K) -> bool { - self.primary.key(k).has(store) - } - - // use no_prefix to scan -> range - fn no_prefix_raw(&self) -> Prefix, T, K> { - Prefix::new(self.pk_namespace, &[]) - } - - /// Clears the map, removing all elements. - pub fn clear(&self, store: &mut dyn Storage) { - const TAKE: usize = 10; - let mut cleared = false; - - while !cleared { - let paths = self - .no_prefix_raw() - .keys_raw(store, None, None, cosmwasm_std::Order::Ascending) - .map(|raw_key| Path::::new(self.pk_namespace, &[raw_key.as_slice()])) - // Take just TAKE elements to prevent possible heap overflow if the Map is big. - .take(TAKE) - .collect::>(); - - paths.iter().for_each(|path| store.remove(path)); - - cleared = paths.len() < TAKE; - } - } - - /// Returns `true` if the map is empty. - pub fn is_empty(&self, store: &dyn Storage) -> bool { - self.no_prefix_raw() - .keys_raw(store, None, None, cosmwasm_std::Order::Ascending) - .next() - .is_none() - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T, I> IndexedMap<'a, K, T, I> -where - K: PrimaryKey<'a>, - T: Serialize + DeserializeOwned + Clone, - I: IndexList, -{ - /// While `range_raw` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range_raw` accepts bounds for the lowest and highest elements of the `Prefix` - /// itself, and iterates over those (inclusively or exclusively, depending on `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box>> + 'c> - where - T: 'c, - 'a: 'c, - { - let mapped = - namespaced_prefix_range(store, self.pk_namespace, min, max, order).map(deserialize_v); - Box::new(mapped) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T, I> IndexedMap<'a, K, T, I> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a>, - I: IndexList, -{ - pub fn sub_prefix(&self, p: K::SubPrefix) -> Prefix { - Prefix::new(self.pk_namespace, &p.prefix()) - } - - pub fn prefix(&self, p: K::Prefix) -> Prefix { - Prefix::new(self.pk_namespace, &p.prefix()) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T, I> IndexedMap<'a, K, T, I> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a> + KeyDeserialize, - I: IndexList, -{ - /// While `range` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range` accepts bounds for the lowest and highest elements of the - /// `Prefix` itself, and iterates over those (inclusively or exclusively, depending on - /// `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - 'a: 'c, - K: 'c, - K::Output: 'static, - { - let mapped = namespaced_prefix_range(store, self.pk_namespace, min, max, order) - .map(deserialize_kv::); - Box::new(mapped) - } - - pub fn range_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box>> + 'c> - where - T: 'c, - { - self.no_prefix_raw().range_raw(store, min, max, order) - } - - pub fn keys_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> { - self.no_prefix_raw().keys_raw(store, min, max, order) - } - - pub fn range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().range(store, min, max, order) - } - - pub fn keys<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().keys(store, min, max, order) - } - - fn no_prefix(&self) -> Prefix { - Prefix::new(self.pk_namespace, &[]) - } -} - -#[cfg(test)] -mod test { - use super::*; - - use crate::indexes::test::{index_string_tuple, index_tuple}; - use crate::{MultiIndex, UniqueIndex}; - use cosmwasm_std::testing::MockStorage; - use cosmwasm_std::{MemoryStorage, Order}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] - struct Data { - pub name: String, - pub last_name: String, - pub age: u32, - } - - struct DataIndexes<'a> { - // Last type parameters are for signaling pk deserialization - pub name: MultiIndex<'a, String, Data, String>, - pub age: UniqueIndex<'a, u32, Data, String>, - pub name_lastname: UniqueIndex<'a, (Vec, Vec), Data, String>, - } - - // Future Note: this can likely be macro-derived - impl<'a> IndexList for DataIndexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.name, &self.age, &self.name_lastname]; - Box::new(v.into_iter()) - } - } - - // For composite multi index tests - struct DataCompositeMultiIndex<'a> { - // Last type parameter is for signaling pk deserialization - pub name_age: MultiIndex<'a, (Vec, u32), Data, String>, - } - - // Future Note: this can likely be macro-derived - impl<'a> IndexList for DataCompositeMultiIndex<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.name_age]; - Box::new(v.into_iter()) - } - } - - // Can we make it easier to define this? (less wordy generic) - fn build_map<'a>() -> IndexedMap<'a, &'a str, Data, DataIndexes<'a>> { - let indexes = DataIndexes { - name: MultiIndex::new(|_pk, d| d.name.clone(), "data", "data__name"), - age: UniqueIndex::new(|d| d.age, "data__age"), - name_lastname: UniqueIndex::new( - |d| index_string_tuple(&d.name, &d.last_name), - "data__name_lastname", - ), - }; - IndexedMap::new("data", indexes) - } - - fn save_data<'a>( - store: &mut MockStorage, - map: &IndexedMap<'a, &'a str, Data, DataIndexes<'a>>, - ) -> (Vec<&'a str>, Vec) { - let mut pks = vec![]; - let mut datas = vec![]; - let data = Data { - name: "Maria".to_string(), - last_name: "Doe".to_string(), - age: 42, - }; - let pk = "1"; - map.save(store, pk, &data).unwrap(); - pks.push(pk); - datas.push(data); - - // same name (multi-index), different last name, different age => ok - let data = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 23, - }; - let pk = "2"; - map.save(store, pk, &data).unwrap(); - pks.push(pk); - datas.push(data); - - // different name, different last name, different age => ok - let data = Data { - name: "John".to_string(), - last_name: "Wayne".to_string(), - age: 32, - }; - let pk = "3"; - map.save(store, pk, &data).unwrap(); - pks.push(pk); - datas.push(data); - - let data = Data { - name: "Maria Luisa".to_string(), - last_name: "Rodriguez".to_string(), - age: 12, - }; - let pk = "4"; - map.save(store, pk, &data).unwrap(); - pks.push(pk); - datas.push(data); - - let data = Data { - name: "Marta".to_string(), - last_name: "After".to_string(), - age: 90, - }; - let pk = "5"; - map.save(store, pk, &data).unwrap(); - pks.push(pk); - datas.push(data); - - (pks, datas) - } - - #[test] - fn store_and_load_by_index() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - let pk = pks[0]; - let data = &datas[0]; - - // load it properly - let loaded = map.load(&store, pk).unwrap(); - assert_eq!(*data, loaded); - - let count = map - .idx - .name - .prefix("Maria".to_string()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(2, count); - - // load it by secondary index - let marias: Vec<_> = map - .idx - .name - .prefix("Maria".to_string()) - .range_raw(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - assert_eq!(2, marias.len()); - let (k, v) = &marias[0]; - assert_eq!(pk, String::from_slice(k).unwrap()); - assert_eq!(data, v); - - // other index doesn't match (1 byte after) - let count = map - .idx - .name - .prefix("Marib".to_string()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(0, count); - - // other index doesn't match (1 byte before) - let count = map - .idx - .name - .prefix("Mari`".to_string()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(0, count); - - // other index doesn't match (longer) - let count = map - .idx - .name - .prefix("Maria5".to_string()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(0, count); - - // In a MultiIndex, the index key is composed by the index and the primary key. - // Primary key may be empty (so that to iterate over all elements that match just the index) - let key = ("Maria".to_string(), "".to_string()); - // Iterate using an inclusive bound over the key - let marias = map - .idx - .name - .range_raw(&store, Some(Bound::inclusive(key)), None, Order::Ascending) - .collect::>>() - .unwrap(); - // gets from the first "Maria" until the end - assert_eq!(4, marias.len()); - - // This is equivalent to using prefix_range - let key = "Maria".to_string(); - let marias2 = map - .idx - .name - .prefix_range_raw( - &store, - Some(PrefixBound::inclusive(key)), - None, - Order::Ascending, - ) - .collect::>>() - .unwrap(); - assert_eq!(4, marias2.len()); - assert_eq!(marias, marias2); - - // Build key including a non-empty pk - let key = ("Maria".to_string(), "1".to_string()); - // Iterate using a (exclusive) bound over the key. - // (Useful for pagination / continuation contexts). - let count = map - .idx - .name - .range_raw(&store, Some(Bound::exclusive(key)), None, Order::Ascending) - .count(); - // gets from the 2nd "Maria" until the end - assert_eq!(3, count); - - // index_key() over UniqueIndex works. - let age_key = 23u32; - // Iterate using a (inclusive) bound over the key. - let count = map - .idx - .age - .range_raw( - &store, - Some(Bound::inclusive(age_key)), - None, - Order::Ascending, - ) - .count(); - // gets all the greater than or equal to 23 years old people - assert_eq!(4, count); - - // match on proper age - let proper = 42u32; - let aged = map.idx.age.item(&store, proper).unwrap().unwrap(); - assert_eq!(pk, String::from_vec(aged.0).unwrap()); - assert_eq!(*data, aged.1); - - // no match on wrong age - let too_old = 43u32; - let aged = map.idx.age.item(&store, too_old).unwrap(); - assert_eq!(None, aged); - } - - #[test] - fn existence() { - let mut store = MockStorage::new(); - let map = build_map(); - let (pks, _) = save_data(&mut store, &map); - - assert!(map.has(&store, pks[0])); - assert!(!map.has(&store, "6")); - } - - #[test] - fn range_raw_simple_key_by_multi_index() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk = "5627"; - map.save(&mut store, pk, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk = "5628"; - map.save(&mut store, pk, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 24, - }; - let pk = "5629"; - map.save(&mut store, pk, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 12, - }; - let pk = "5630"; - map.save(&mut store, pk, &data4).unwrap(); - - let marias: Vec<_> = map - .idx - .name - .prefix("Maria".to_string()) - .range_raw(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Pks, sorted by (descending) pk - assert_eq!(marias[0].0, b"5629"); - assert_eq!(marias[1].0, b"5627"); - // Data is correct - assert_eq!(marias[0].1, data3); - assert_eq!(marias[1].1, data1); - } - - #[test] - fn range_simple_key_by_multi_index() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk = "5627"; - map.save(&mut store, pk, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk = "5628"; - map.save(&mut store, pk, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 24, - }; - let pk = "5629"; - map.save(&mut store, pk, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 12, - }; - let pk = "5630"; - map.save(&mut store, pk, &data4).unwrap(); - - let marias: Vec<_> = map - .idx - .name - .prefix("Maria".to_string()) - .range(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Pks, sorted by (descending) pk - assert_eq!(marias[0].0, "5629"); - assert_eq!(marias[1].0, "5627"); - // Data is correct - assert_eq!(marias[0].1, data3); - assert_eq!(marias[1].1, data1); - } - - #[test] - fn range_raw_composite_key_by_multi_index() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = IndexedMap::new("data", indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1: &[u8] = b"5627"; - map.save(&mut store, pk1, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2: &[u8] = b"5628"; - map.save(&mut store, pk2, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3: &[u8] = b"5629"; - map.save(&mut store, pk3, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4: &[u8] = b"5630"; - map.save(&mut store, pk4, &data4).unwrap(); - - let marias: Vec<_> = map - .idx - .name_age - .sub_prefix(b"Maria".to_vec()) - .range_raw(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Pks, sorted by (descending) age - assert_eq!(pk1, marias[0].0); - assert_eq!(pk3, marias[1].0); - - // Data - assert_eq!(data1, marias[0].1); - assert_eq!(data3, marias[1].1); - } - - #[test] - fn range_composite_key_by_multi_index() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = IndexedMap::new("data", indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = "5627"; - map.save(&mut store, pk1, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = "5628"; - map.save(&mut store, pk2, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = "5629"; - map.save(&mut store, pk3, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = "5630"; - map.save(&mut store, pk4, &data4).unwrap(); - - let marias: Vec<_> = map - .idx - .name_age - .sub_prefix(b"Maria".to_vec()) - .range(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Pks, sorted by (descending) age - assert_eq!(pk1, marias[0].0); - assert_eq!(pk3, marias[1].0); - - // Data - assert_eq!(data1, marias[0].1); - assert_eq!(data3, marias[1].1); - } - - #[test] - fn unique_index_enforced() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - // different name, different last name, same age => error - let data5 = Data { - name: "Marcel".to_string(), - last_name: "Laurens".to_string(), - age: 42, - }; - let pk5 = "4"; - - // enforce this returns some error - map.save(&mut store, pk5, &data5).unwrap_err(); - - // query by unique key - // match on proper age - let age42 = 42u32; - let (k, v) = map.idx.age.item(&store, age42).unwrap().unwrap(); - assert_eq!(String::from_vec(k).unwrap(), pks[0]); - assert_eq!(v.name, datas[0].name); - assert_eq!(v.age, datas[0].age); - - // match on other age - let age23 = 23u32; - let (k, v) = map.idx.age.item(&store, age23).unwrap().unwrap(); - assert_eq!(String::from_vec(k).unwrap(), pks[1]); - assert_eq!(v.name, datas[1].name); - assert_eq!(v.age, datas[1].age); - - // if we delete the first one, we can add the blocked one - map.remove(&mut store, pks[0]).unwrap(); - map.save(&mut store, pk5, &data5).unwrap(); - // now 42 is the new owner - let (k, v) = map.idx.age.item(&store, age42).unwrap().unwrap(); - assert_eq!(String::from_vec(k).unwrap(), pk5); - assert_eq!(v.name, data5.name); - assert_eq!(v.age, data5.age); - } - - #[test] - fn unique_index_enforced_composite_key() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - save_data(&mut store, &map); - - // same name, same lastname => error - let data5 = Data { - name: "Maria".to_string(), - last_name: "Doe".to_string(), - age: 24, - }; - let pk5 = "5"; - // enforce this returns some error - map.save(&mut store, pk5, &data5).unwrap_err(); - } - - #[test] - fn remove_and_update_reflected_on_indexes() { - let mut store = MockStorage::new(); - let map = build_map(); - - let name_count = |map: &IndexedMap<&str, Data, DataIndexes>, - store: &MemoryStorage, - name: &str| - -> usize { - map.idx - .name - .prefix(name.to_string()) - .keys_raw(store, None, None, Order::Ascending) - .count() - }; - - // save data - let (pks, _) = save_data(&mut store, &map); - - // find 2 Marias, 1 John, and no Mary - assert_eq!(name_count(&map, &store, "Maria"), 2); - assert_eq!(name_count(&map, &store, "John"), 1); - assert_eq!(name_count(&map, &store, "Maria Luisa"), 1); - assert_eq!(name_count(&map, &store, "Mary"), 0); - - // remove maria 2 - map.remove(&mut store, pks[1]).unwrap(); - - // change john to mary - map.update(&mut store, pks[2], |d| -> StdResult<_> { - let mut x = d.unwrap(); - assert_eq!(&x.name, "John"); - x.name = "Mary".to_string(); - Ok(x) - }) - .unwrap(); - - // find 1 maria, 1 maria luisa, no john, and 1 mary - assert_eq!(name_count(&map, &store, "Maria"), 1); - assert_eq!(name_count(&map, &store, "Maria Luisa"), 1); - assert_eq!(name_count(&map, &store, "John"), 0); - assert_eq!(name_count(&map, &store, "Mary"), 1); - } - - #[test] - fn range_raw_simple_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .age - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let ages = res.unwrap(); - - let count = ages.len(); - assert_eq!(5, count); - - // The pks, sorted by age ascending - assert_eq!(pks[3], String::from_slice(&ages[0].0).unwrap()); // 12 - assert_eq!(pks[1], String::from_slice(&ages[1].0).unwrap()); // 23 - assert_eq!(pks[2], String::from_slice(&ages[2].0).unwrap()); // 32 - assert_eq!(pks[0], String::from_slice(&ages[3].0).unwrap()); // 42 - assert_eq!(pks[4], String::from_slice(&ages[4].0).unwrap()); // 90 - - // The associated data - assert_eq!(datas[3], ages[0].1); - assert_eq!(datas[1], ages[1].1); - assert_eq!(datas[2], ages[2].1); - assert_eq!(datas[0], ages[3].1); - assert_eq!(datas[4], ages[4].1); - } - - #[test] - fn range_simple_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .age - .range(&store, None, None, Order::Ascending) - .collect(); - let ages = res.unwrap(); - - let count = ages.len(); - assert_eq!(5, count); - - // The pks, sorted by age ascending - assert_eq!(pks[3], ages[0].0); - assert_eq!(pks[1], ages[1].0); - assert_eq!(pks[2], ages[2].0); - assert_eq!(pks[0], ages[3].0); - assert_eq!(pks[4], ages[4].0); - - // The associated data - assert_eq!(datas[3], ages[0].1); - assert_eq!(datas[1], ages[1].1); - assert_eq!(datas[2], ages[2].1); - assert_eq!(datas[0], ages[3].1); - assert_eq!(datas[4], ages[4].1); - } - - #[test] - fn range_raw_composite_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .name_lastname - .prefix(b"Maria".to_vec()) - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let marias = res.unwrap(); - - // Only two people are called "Maria" - let count = marias.len(); - assert_eq!(2, count); - - // The pks - assert_eq!(pks[0], String::from_slice(&marias[0].0).unwrap()); - assert_eq!(pks[1], String::from_slice(&marias[1].0).unwrap()); - - // The associated data - assert_eq!(datas[0], marias[0].1); - assert_eq!(datas[1], marias[1].1); - } - - #[test] - fn range_composite_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .name_lastname - .prefix(b"Maria".to_vec()) - .range(&store, None, None, Order::Ascending) - .collect(); - let marias = res.unwrap(); - - // Only two people are called "Maria" - let count = marias.len(); - assert_eq!(2, count); - - // The pks - assert_eq!(pks[0], marias[0].0); - assert_eq!(pks[1], marias[1].0); - - // The associated data - assert_eq!(datas[0], marias[0].1); - assert_eq!(datas[1], marias[1].1); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_string_key() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - // let's try to iterate! - let all: StdResult> = map.range(&store, None, None, Order::Ascending).collect(); - let all = all.unwrap(); - assert_eq!( - all, - pks.clone() - .into_iter() - .map(str::to_string) - .zip(datas.clone().into_iter()) - .collect::>() - ); - - // let's try to iterate over a range - let all: StdResult> = map - .range(&store, Some(Bound::inclusive("3")), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!( - all, - pks.into_iter() - .map(str::to_string) - .zip(datas.into_iter()) - .rev() - .take(3) - .rev() - .collect::>() - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_simple_string_key() { - let mut store = MockStorage::new(); - let map = build_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - // Let's prefix and iterate. - // This is similar to calling range() directly, but added here for completeness / prefix - // type checks - let all: StdResult> = map - .prefix(()) - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!( - all, - pks.clone() - .into_iter() - .map(str::to_string) - .zip(datas.into_iter()) - .collect::>() - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_composite_key() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = IndexedMap::new("data", indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = ("1", "5627"); - map.save(&mut store, pk1, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = ("2", "5628"); - map.save(&mut store, pk2, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = ("2", "5629"); - map.save(&mut store, pk3, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = ("3", "5630"); - map.save(&mut store, pk4, &data4).unwrap(); - - // let's prefix and iterate - let result: StdResult> = map - .prefix("2") - .range(&store, None, None, Order::Ascending) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [("5628".to_string(), data2), ("5629".to_string(), data3),] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_triple_key() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = IndexedMap::new("data", indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = ("1", "1", "5627"); - map.save(&mut store, pk1, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = ("1", "2", "5628"); - map.save(&mut store, pk2, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = ("2", "1", "5629"); - map.save(&mut store, pk3, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = ("2", "2", "5630"); - map.save(&mut store, pk4, &data4).unwrap(); - - // let's prefix and iterate - let result: StdResult> = map - .prefix(("1", "2")) - .range(&store, None, None, Order::Ascending) - .collect(); - let result = result.unwrap(); - assert_eq!(result, [("5628".to_string(), data2),]); - } - - #[test] - #[cfg(feature = "iterator")] - fn sub_prefix_triple_key() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = IndexedMap::new("data", indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = ("1", "1", "5627"); - map.save(&mut store, pk1, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = ("1", "2", "5628"); - map.save(&mut store, pk2, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = ("2", "1", "5629"); - map.save(&mut store, pk3, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = ("2", "2", "5630"); - map.save(&mut store, pk4, &data4).unwrap(); - - // let's sub-prefix and iterate - let result: StdResult> = map - .sub_prefix("1") - .range(&store, None, None, Order::Ascending) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [ - (("1".to_string(), "5627".to_string()), data1), - (("2".to_string(), "5628".to_string()), data2), - ] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_range_simple_key() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = IndexedMap::new("data", indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = ("1", "5627"); - map.save(&mut store, pk1, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = ("2", "5628"); - map.save(&mut store, pk2, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = ("2", "5629"); - map.save(&mut store, pk3, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = ("3", "5630"); - map.save(&mut store, pk4, &data4).unwrap(); - - // let's prefix-range and iterate - let result: StdResult> = map - .prefix_range( - &store, - Some(PrefixBound::inclusive("2")), - None, - Order::Ascending, - ) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [ - (("2".to_string(), "5628".to_string()), data2.clone()), - (("2".to_string(), "5629".to_string()), data3.clone()), - (("3".to_string(), "5630".to_string()), data4) - ] - ); - - // let's try to iterate over a more restrictive prefix-range! - let result: StdResult> = map - .prefix_range( - &store, - Some(PrefixBound::inclusive("2")), - Some(PrefixBound::exclusive("3")), - Order::Ascending, - ) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [ - (("2".to_string(), "5628".to_string()), data2), - (("2".to_string(), "5629".to_string()), data3), - ] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_range_triple_key() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = IndexedMap::new("data", indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = ("1", "1", "5627"); - map.save(&mut store, pk1, &data1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = ("1", "2", "5628"); - map.save(&mut store, pk2, &data2).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = ("2", "1", "5629"); - map.save(&mut store, pk3, &data3).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = ("2", "2", "5630"); - map.save(&mut store, pk4, &data4).unwrap(); - - // let's prefix-range and iterate - let result: StdResult> = map - .prefix_range( - &store, - Some(PrefixBound::inclusive(("1", "2"))), - None, - Order::Ascending, - ) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [ - ( - ("1".to_string(), "2".to_string(), "5628".to_string()), - data2.clone() - ), - ( - ("2".to_string(), "1".to_string(), "5629".to_string()), - data3.clone() - ), - ( - ("2".to_string(), "2".to_string(), "5630".to_string()), - data4 - ) - ] - ); - - // let's prefix-range over inclusive bounds on both sides - let result: StdResult> = map - .prefix_range( - &store, - Some(PrefixBound::inclusive(("1", "2"))), - Some(PrefixBound::inclusive(("2", "1"))), - Order::Ascending, - ) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [ - ( - ("1".to_string(), "2".to_string(), "5628".to_string()), - data2 - ), - ( - ("2".to_string(), "1".to_string(), "5629".to_string()), - data3 - ), - ] - ); - } - - mod bounds_unique_index { - use super::*; - - struct Indexes<'a> { - secondary: UniqueIndex<'a, u64, u64>, - } - - impl<'a> IndexList for Indexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.secondary]; - Box::new(v.into_iter()) - } - } - - #[test] - #[cfg(feature = "iterator")] - fn composite_key_query() { - let indexes = Indexes { - secondary: UniqueIndex::new(|secondary| *secondary, "test_map__secondary"), - }; - let map = IndexedMap::<&str, u64, Indexes>::new("test_map", indexes); - let mut store = MockStorage::new(); - - map.save(&mut store, "one", &1).unwrap(); - map.save(&mut store, "two", &2).unwrap(); - map.save(&mut store, "three", &3).unwrap(); - - // Inclusive bound - let items: Vec<_> = map - .idx - .secondary - .range_raw(&store, None, Some(Bound::inclusive(1u64)), Order::Ascending) - .collect::>() - .unwrap(); - - // Strip the index from values (for simpler comparison) - let items: Vec<_> = items.into_iter().map(|(_, v)| v).collect(); - - assert_eq!(items, vec![1]); - - // Exclusive bound - let items: Vec<_> = map - .idx - .secondary - .range(&store, Some(Bound::exclusive(2u64)), None, Order::Ascending) - .collect::>() - .unwrap(); - - assert_eq!(items, vec![((), 3)]); - } - } - - mod bounds_multi_index { - use super::*; - - struct Indexes<'a> { - // The last type param must match the `IndexedMap` primary key type, below - secondary: MultiIndex<'a, u64, u64, &'a str>, - } - - impl<'a> IndexList for Indexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.secondary]; - Box::new(v.into_iter()) - } - } - - #[test] - #[cfg(feature = "iterator")] - fn composite_key_query() { - let indexes = Indexes { - secondary: MultiIndex::new( - |_pk, secondary| *secondary, - "test_map", - "test_map__secondary", - ), - }; - let map = IndexedMap::<&str, u64, Indexes>::new("test_map", indexes); - let mut store = MockStorage::new(); - - map.save(&mut store, "one", &1).unwrap(); - map.save(&mut store, "two", &2).unwrap(); - map.save(&mut store, "two2", &2).unwrap(); - map.save(&mut store, "three", &3).unwrap(); - - // Inclusive prefix-bound - let items: Vec<_> = map - .idx - .secondary - .prefix_range_raw( - &store, - None, - Some(PrefixBound::inclusive(1u64)), - Order::Ascending, - ) - .collect::>() - .unwrap(); - - // Strip the index from values (for simpler comparison) - let items: Vec<_> = items.into_iter().map(|(_, v)| v).collect(); - - assert_eq!(items, vec![1]); - - // Exclusive bound (used for pagination) - // Range over the index specifying a primary key (multi-index key includes the pk) - let items: Vec<_> = map - .idx - .secondary - .range( - &store, - Some(Bound::exclusive((2u64, "two"))), - None, - Order::Ascending, - ) - .collect::>() - .unwrap(); - - assert_eq!( - items, - vec![("two2".to_string(), 2), ("three".to_string(), 3)] - ); - } - } - - mod pk_multi_index { - use super::*; - use cosmwasm_std::{Addr, Uint128}; - - struct Indexes<'a> { - // The last type param must match the `IndexedMap` primary key type below - spender: MultiIndex<'a, Addr, Uint128, (Addr, Addr)>, - } - - impl<'a> IndexList for Indexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.spender]; - Box::new(v.into_iter()) - } - } - - #[test] - #[cfg(feature = "iterator")] - fn pk_based_index() { - fn pk_index(pk: &[u8]) -> Addr { - let (_owner, spender) = <(Addr, Addr)>::from_slice(pk).unwrap(); // mustn't fail - spender - } - - let indexes = Indexes { - spender: MultiIndex::new( - |pk, _allow| pk_index(pk), - "allowances", - "allowances__spender", - ), - }; - let map = IndexedMap::<(&Addr, &Addr), Uint128, Indexes>::new("allowances", indexes); - let mut store = MockStorage::new(); - - map.save( - &mut store, - (&Addr::unchecked("owner1"), &Addr::unchecked("spender1")), - &Uint128::new(11), - ) - .unwrap(); - map.save( - &mut store, - (&Addr::unchecked("owner1"), &Addr::unchecked("spender2")), - &Uint128::new(12), - ) - .unwrap(); - map.save( - &mut store, - (&Addr::unchecked("owner2"), &Addr::unchecked("spender1")), - &Uint128::new(21), - ) - .unwrap(); - - // Iterate over the main values - let items: Vec<_> = map - .range_raw(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - - // Strip the index from values (for simpler comparison) - let items: Vec<_> = items.into_iter().map(|(_, v)| v.u128()).collect(); - - assert_eq!(items, vec![11, 12, 21]); - - // Iterate over the indexed values - let items: Vec<_> = map - .idx - .spender - .range_raw(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - - // Strip the index from values (for simpler comparison) - let items: Vec<_> = items.into_iter().map(|(_, v)| v.u128()).collect(); - - assert_eq!(items, vec![11, 21, 12]); - - // Prefix over the main values - let items: Vec<_> = map - .prefix(&Addr::unchecked("owner1")) - .range_raw(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - - // Strip the index from values (for simpler comparison) - let items: Vec<_> = items.into_iter().map(|(_, v)| v.u128()).collect(); - - assert_eq!(items, vec![11, 12]); - - // Prefix over the indexed values - let items: Vec<_> = map - .idx - .spender - .prefix(Addr::unchecked("spender1")) - .range_raw(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - - // Strip the index from values (for simpler comparison) - let items: Vec<_> = items.into_iter().map(|(_, v)| v.u128()).collect(); - - assert_eq!(items, vec![11, 21]); - - // Prefix over the indexed values, and deserialize primary key as well - let items: Vec<_> = map - .idx - .spender - .prefix(Addr::unchecked("spender2")) - .range(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - - assert_eq!( - items, - vec![( - (Addr::unchecked("owner1"), Addr::unchecked("spender2")), - Uint128::new(12) - )] - ); - } - } - - #[test] - fn clear_works() { - let mut storage = MockStorage::new(); - let map = build_map(); - let (pks, _) = save_data(&mut storage, &map); - - map.clear(&mut storage); - - for key in pks { - assert!(!map.has(&storage, key)); - } - } - - #[test] - fn is_empty_works() { - let mut storage = MockStorage::new(); - let map = build_map(); - - assert!(map.is_empty(&storage)); - - save_data(&mut storage, &map); - - assert!(!map.is_empty(&storage)); - } -} diff --git a/packages/storage-plus/src/indexed_snapshot.rs b/packages/storage-plus/src/indexed_snapshot.rs deleted file mode 100644 index 146fc0d0f..000000000 --- a/packages/storage-plus/src/indexed_snapshot.rs +++ /dev/null @@ -1,1224 +0,0 @@ -// this module requires iterator to be useful at all -#![cfg(feature = "iterator")] - -use cosmwasm_std::{StdError, StdResult, Storage}; -use serde::de::DeserializeOwned; -use serde::Serialize; - -use crate::de::KeyDeserialize; -use crate::iter_helpers::deserialize_kv; -use crate::keys::{Prefixer, PrimaryKey}; -use crate::prefix::{namespaced_prefix_range, Prefix}; -use crate::snapshot::{ChangeSet, SnapshotMap}; -use crate::PrefixBound; -use crate::{Bound, IndexList, Map, Path, Strategy}; - -/// `IndexedSnapshotMap` works like a `SnapshotMap` but has a secondary index -pub struct IndexedSnapshotMap<'a, K, T, I> { - pk_namespace: &'a [u8], - primary: SnapshotMap<'a, K, T>, - /// This is meant to be read directly to get the proper types, like: - /// map.idx.owner.items(...) - pub idx: I, -} - -impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> { - /// Examples: - /// - /// ```rust - /// use cw_storage_plus::{IndexedSnapshotMap, Strategy, UniqueIndex}; - /// - /// #[derive(PartialEq, Debug, Clone)] - /// struct Data { - /// pub name: String, - /// pub age: u32, - /// } - /// - /// let indexes = UniqueIndex::new(|d: &Data| d.age, "data__age"); - /// - /// IndexedSnapshotMap::<&[u8], Data, UniqueIndex>::new( - /// "data", - /// "checkpoints", - /// "changelog", - /// Strategy::EveryBlock, - /// indexes, - /// ); - /// ``` - pub fn new( - pk_namespace: &'a str, - checkpoints: &'a str, - changelog: &'a str, - strategy: Strategy, - indexes: I, - ) -> Self { - IndexedSnapshotMap { - pk_namespace: pk_namespace.as_bytes(), - primary: SnapshotMap::new(pk_namespace, checkpoints, changelog, strategy), - idx: indexes, - } - } - - pub fn changelog(&self) -> &Map<'a, (K, u64), ChangeSet> { - self.primary.changelog() - } -} - -impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, - I: IndexList, -{ - pub fn add_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - self.primary.add_checkpoint(store, height) - } - - pub fn remove_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - self.primary.remove_checkpoint(store, height) - } - - pub fn may_load_at_height( - &self, - store: &dyn Storage, - k: K, - height: u64, - ) -> StdResult> { - self.primary.may_load_at_height(store, k, height) - } - - pub fn assert_checkpointed(&self, store: &dyn Storage, height: u64) -> StdResult<()> { - self.primary.assert_checkpointed(store, height) - } - - pub fn key(&self, k: K) -> Path { - self.primary.key(k) - } -} - -impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> -where - K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, - T: Serialize + DeserializeOwned + Clone, - I: IndexList, -{ - /// save will serialize the model and store, returns an error on serialization issues. - /// this must load the old value to update the indexes properly - /// if you loaded the old value earlier in the same function, use replace to avoid needless db reads - pub fn save(&self, store: &mut dyn Storage, key: K, data: &T, height: u64) -> StdResult<()> { - let old_data = self.may_load(store, key.clone())?; - self.replace(store, key, Some(data), old_data.as_ref(), height) - } - - pub fn remove(&self, store: &mut dyn Storage, key: K, height: u64) -> StdResult<()> { - let old_data = self.may_load(store, key.clone())?; - self.replace(store, key, None, old_data.as_ref(), height) - } - - /// replace writes data to key. old_data must be the current stored value (from a previous load) - /// and is used to properly update the index. This is used by save, replace, and update - /// and can be called directly if you want to optimize - pub fn replace( - &self, - store: &mut dyn Storage, - key: K, - data: Option<&T>, - old_data: Option<&T>, - height: u64, - ) -> StdResult<()> { - // this is the key *relative* to the primary map namespace - let pk = key.joined_key(); - if let Some(old) = old_data { - for index in self.idx.get_indexes() { - index.remove(store, &pk, old)?; - } - } - if let Some(updated) = data { - for index in self.idx.get_indexes() { - index.save(store, &pk, updated)?; - } - self.primary.save(store, key, updated, height)?; - } else { - self.primary.remove(store, key, height)?; - } - Ok(()) - } - - /// 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. - /// - /// If the data exists, `action(Some(value))` is called. Otherwise `action(None)` is called. - pub fn update( - &self, - store: &mut dyn Storage, - key: K, - height: u64, - action: A, - ) -> Result - where - A: FnOnce(Option) -> Result, - E: From, - { - let input = self.may_load(store, key.clone())?; - let old_val = input.clone(); - let output = action(input)?; - self.replace(store, key, Some(&output), old_val.as_ref(), height)?; - Ok(output) - } - - // Everything else, that doesn't touch indexers, is just pass-through from self.core, - // thus can be used from while iterating over indexes - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, store: &dyn Storage, key: K) -> StdResult { - self.primary.load(store, key) - } - - /// 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, store: &dyn Storage, key: K) -> StdResult> { - self.primary.may_load(store, key) - } - - // use no_prefix to scan -> range - pub fn no_prefix_raw(&self) -> Prefix, T, K> { - Prefix::new(self.pk_namespace, &[]) - } -} - -// short-cut for simple keys, rather than .prefix(()).range_raw(...) -impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> -where - K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, - T: Serialize + DeserializeOwned + Clone, - I: IndexList, -{ - // I would prefer not to copy code from Prefix, but no other way - // with lifetimes (create Prefix inside function and return ref = no no) - pub fn range_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box>> + 'c> - where - T: 'c, - { - self.no_prefix_raw().range_raw(store, min, max, order) - } - - pub fn keys_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> { - self.no_prefix_raw().keys_raw(store, min, max, order) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a>, - I: IndexList, -{ - pub fn sub_prefix(&self, p: K::SubPrefix) -> Prefix { - Prefix::new(self.pk_namespace, &p.prefix()) - } - - pub fn prefix(&self, p: K::Prefix) -> Prefix { - Prefix::new(self.pk_namespace, &p.prefix()) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a> + KeyDeserialize, - I: IndexList, -{ - /// While `range` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range` accepts bounds for the lowest and highest elements of the - /// `Prefix` itself, and iterates over those (inclusively or exclusively, depending on - /// `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - 'a: 'c, - K: 'c, - K::Output: 'static, - { - let mapped = namespaced_prefix_range(store, self.pk_namespace, min, max, order) - .map(deserialize_kv::); - Box::new(mapped) - } - - pub fn range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().range(store, min, max, order) - } - - pub fn keys<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().keys(store, min, max, order) - } - - fn no_prefix(&self) -> Prefix { - Prefix::new(self.pk_namespace, &[]) - } -} - -#[cfg(test)] -mod test { - use super::*; - - use crate::indexes::test::{index_string_tuple, index_tuple}; - use crate::{Index, MultiIndex, UniqueIndex}; - use cosmwasm_std::testing::MockStorage; - use cosmwasm_std::{MemoryStorage, Order}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] - struct Data { - pub name: String, - pub last_name: String, - pub age: u32, - } - - struct DataIndexes<'a> { - // Last type parameters are for signaling pk deserialization - pub name: MultiIndex<'a, Vec, Data, String>, - pub age: UniqueIndex<'a, u32, Data, String>, - pub name_lastname: UniqueIndex<'a, (Vec, Vec), Data, String>, - } - - // Future Note: this can likely be macro-derived - impl<'a> IndexList for DataIndexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.name, &self.age, &self.name_lastname]; - Box::new(v.into_iter()) - } - } - - // For composite multi index tests - struct DataCompositeMultiIndex<'a> { - // Last type parameter is for signaling pk deserialization - pub name_age: MultiIndex<'a, (Vec, u32), Data, String>, - } - - // Future Note: this can likely be macro-derived - impl<'a> IndexList for DataCompositeMultiIndex<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.name_age]; - Box::new(v.into_iter()) - } - } - - // Can we make it easier to define this? (less wordy generic) - fn build_snapshot_map<'a>() -> IndexedSnapshotMap<'a, &'a str, Data, DataIndexes<'a>> { - let indexes = DataIndexes { - name: MultiIndex::new(|_pk, d| d.name.as_bytes().to_vec(), "data", "data__name"), - age: UniqueIndex::new(|d| d.age, "data__age"), - name_lastname: UniqueIndex::new( - |d| index_string_tuple(&d.name, &d.last_name), - "data__name_lastname", - ), - }; - IndexedSnapshotMap::new( - "data", - "checkpoints", - "changelog", - Strategy::EveryBlock, - indexes, - ) - } - - fn save_data<'a>( - store: &mut MockStorage, - map: &IndexedSnapshotMap<'a, &'a str, Data, DataIndexes<'a>>, - ) -> (Vec<&'a str>, Vec) { - let mut pks = vec![]; - let mut datas = vec![]; - let mut height = 0; - let data = Data { - name: "Maria".to_string(), - last_name: "Doe".to_string(), - age: 42, - }; - let pk = "1"; - map.save(store, pk, &data, height).unwrap(); - height += 1; - pks.push(pk); - datas.push(data); - - // same name (multi-index), different last name, different age => ok - let data = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 23, - }; - let pk = "2"; - map.save(store, pk, &data, height).unwrap(); - height += 1; - pks.push(pk); - datas.push(data); - - // different name, different last name, different age => ok - let data = Data { - name: "John".to_string(), - last_name: "Wayne".to_string(), - age: 32, - }; - let pk = "3"; - map.save(store, pk, &data, height).unwrap(); - height += 1; - pks.push(pk); - datas.push(data); - - let data = Data { - name: "Maria Luisa".to_string(), - last_name: "Rodriguez".to_string(), - age: 12, - }; - let pk = "4"; - map.save(store, pk, &data, height).unwrap(); - pks.push(pk); - datas.push(data); - - (pks, datas) - } - - #[test] - fn store_and_load_by_index() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - let pk = pks[0]; - let data = &datas[0]; - - // load it properly - let loaded = map.load(&store, pk).unwrap(); - assert_eq!(*data, loaded); - - let count = map - .idx - .name - .prefix(b"Maria".to_vec()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(2, count); - - // load it by secondary index - let marias: Vec<_> = map - .idx - .name - .prefix(b"Maria".to_vec()) - .range_raw(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - assert_eq!(2, marias.len()); - let (k, v) = &marias[0]; - assert_eq!(pk.as_bytes(), k); - assert_eq!(data, v); - - // other index doesn't match (1 byte after) - let count = map - .idx - .name - .prefix(b"Marib".to_vec()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(0, count); - - // other index doesn't match (1 byte before) - let count = map - .idx - .name - .prefix(b"Mari`".to_vec()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(0, count); - - // other index doesn't match (longer) - let count = map - .idx - .name - .prefix(b"Maria5".to_vec()) - .range_raw(&store, None, None, Order::Ascending) - .count(); - assert_eq!(0, count); - - // match on proper age - let proper = 42u32; - let aged = map.idx.age.item(&store, proper).unwrap().unwrap(); - assert_eq!(pk.as_bytes(), aged.0); - assert_eq!(*data, aged.1); - - // no match on wrong age - let too_old = 43u32; - let aged = map.idx.age.item(&store, too_old).unwrap(); - assert_eq!(None, aged); - } - - #[test] - fn range_raw_simple_key_by_multi_index() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - let mut height = 1; - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk = "5627"; - map.save(&mut store, pk, &data1, height).unwrap(); - height += 1; - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk = "5628"; - map.save(&mut store, pk, &data2, height).unwrap(); - height += 1; - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 24, - }; - let pk = "5629"; - map.save(&mut store, pk, &data3, height).unwrap(); - height += 1; - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 12, - }; - let pk = "5630"; - map.save(&mut store, pk, &data4, height).unwrap(); - - let marias: Vec<_> = map - .idx - .name - .prefix(b"Maria".to_vec()) - .range_raw(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Sorted by (descending) pk - assert_eq!(marias[0].0, b"5629"); - assert_eq!(marias[1].0, b"5627"); - // Data is correct - assert_eq!(marias[0].1, data3); - assert_eq!(marias[1].1, data1); - } - - #[test] - fn range_simple_key_by_multi_index() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - let mut height = 1; - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk = "5627"; - map.save(&mut store, pk, &data1, height).unwrap(); - height += 1; - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk = "5628"; - map.save(&mut store, pk, &data2, height).unwrap(); - height += 1; - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 24, - }; - let pk = "5629"; - map.save(&mut store, pk, &data3, height).unwrap(); - height += 1; - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 12, - }; - let pk = "5630"; - map.save(&mut store, pk, &data4, height).unwrap(); - - let marias: Vec<_> = map - .idx - .name - .prefix(b"Maria".to_vec()) - .range(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Sorted by (descending) pk - assert_eq!(marias[0].0, "5629"); - assert_eq!(marias[1].0, "5627"); - // Data is correct - assert_eq!(marias[0].1, data3); - assert_eq!(marias[1].1, data1); - } - - #[test] - fn changelog_range_works() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - let mut height = 1; - - // simple data for testing - // EVERY.remove(&mut store, "B", 4).unwrap(); - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk_a = "A"; - map.save(&mut store, pk_a, &data1, height).unwrap(); - height += 1; - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk_b = "B"; - map.save(&mut store, pk_b, &data2, height).unwrap(); - height += 1; - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 24, - }; - map.update(&mut store, pk_a, height, |_| -> StdResult { - Ok(data3) - }) - .unwrap(); - - height += 1; - map.remove(&mut store, pk_b, height).unwrap(); - - let changes: Vec<_> = map - .changelog() - .range(&store, None, None, Order::Ascending) - .collect::>() - .unwrap(); - let count = changes.len(); - assert_eq!(4, count); - - // sorted by ascending key, height - assert_eq!( - changes, - vec![ - (("A".into(), 1), ChangeSet { old: None }), - (("A".into(), 3), ChangeSet { old: Some(data1) }), - (("B".into(), 2), ChangeSet { old: None }), - (("B".into(), 4), ChangeSet { old: Some(data2) }) - ] - ); - } - - #[test] - fn range_raw_composite_key_by_multi_index() { - let mut store = MockStorage::new(); - let mut height = 2; - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = - IndexedSnapshotMap::new("data", "checks", "changes", Strategy::EveryBlock, indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = "5627"; - map.save(&mut store, pk1, &data1, height).unwrap(); - height += 1; - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = "5628"; - map.save(&mut store, pk2, &data2, height).unwrap(); - height += 1; - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = "5629"; - map.save(&mut store, pk3, &data3, height).unwrap(); - height += 1; - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = "5630"; - map.save(&mut store, pk4, &data4, height).unwrap(); - - let marias: Vec<_> = map - .idx - .name_age - .sub_prefix(b"Maria".to_vec()) - .range_raw(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Pks (sorted by age descending) - assert_eq!(pk1.as_bytes(), marias[0].0); - assert_eq!(pk3.as_bytes(), marias[1].0); - - // Data - assert_eq!(data1, marias[0].1); - assert_eq!(data3, marias[1].1); - } - - #[test] - fn range_composite_key_by_multi_index() { - let mut store = MockStorage::new(); - let mut height = 2; - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = - IndexedSnapshotMap::new("data", "checks", "changes", Strategy::EveryBlock, indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1 = "5627"; - map.save(&mut store, pk1, &data1, height).unwrap(); - height += 1; - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2 = "5628"; - map.save(&mut store, pk2, &data2, height).unwrap(); - height += 1; - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3 = "5629"; - map.save(&mut store, pk3, &data3, height).unwrap(); - height += 1; - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4 = "5630"; - map.save(&mut store, pk4, &data4, height).unwrap(); - - let marias: Vec<_> = map - .idx - .name_age - .sub_prefix(b"Maria".to_vec()) - .range(&store, None, None, Order::Descending) - .collect::>() - .unwrap(); - let count = marias.len(); - assert_eq!(2, count); - - // Pks (sorted by age descending) - assert_eq!(pk1.to_string(), marias[0].0); - assert_eq!(pk3.to_string(), marias[1].0); - - // Data - assert_eq!(data1, marias[0].1); - assert_eq!(data3, marias[1].1); - } - - #[test] - fn unique_index_enforced() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - let mut height = 3; - - // save data - let (pks, datas) = save_data(&mut store, &map); - - // different name, different last name, same age => error - let data5 = Data { - name: "Marta".to_string(), - last_name: "Laurens".to_string(), - age: 42, - }; - let pk5 = "4"; - - // enforce this returns some error - map.save(&mut store, pk5, &data5, height).unwrap_err(); - height += 1; - - // query by unique key - // match on proper age - let age42 = 42u32; - let (k, v) = map.idx.age.item(&store, age42).unwrap().unwrap(); - assert_eq!(k, pks[0].as_bytes()); - assert_eq!(v.name, datas[0].name); - assert_eq!(v.age, datas[0].age); - - // match on other age - let age23 = 23u32; - let (k, v) = map.idx.age.item(&store, age23).unwrap().unwrap(); - assert_eq!(k, pks[1].as_bytes()); - assert_eq!(v.name, datas[1].name); - assert_eq!(v.age, datas[1].age); - - // if we delete the first one, we can add the blocked one - map.remove(&mut store, pks[0], height).unwrap(); - height += 1; - map.save(&mut store, pk5, &data5, height).unwrap(); - // now 42 is the new owner - let (k, v) = map.idx.age.item(&store, age42).unwrap().unwrap(); - assert_eq!(k, pk5.as_bytes()); - assert_eq!(v.name, data5.name); - assert_eq!(v.age, data5.age); - } - - #[test] - fn unique_index_enforced_composite_key() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - let height = 4; - - // save data - save_data(&mut store, &map); - - // same name, same lastname => error - let data5 = Data { - name: "Maria".to_string(), - last_name: "Doe".to_string(), - age: 24, - }; - let pk5 = "5"; - // enforce this returns some error - map.save(&mut store, pk5, &data5, height).unwrap_err(); - } - - #[test] - fn remove_and_update_reflected_on_indexes() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - let mut height = 5; - - let name_count = |map: &IndexedSnapshotMap<&str, Data, DataIndexes>, - store: &MemoryStorage, - name: &str| - -> usize { - map.idx - .name - .prefix(name.as_bytes().to_vec()) - .keys_raw(store, None, None, Order::Ascending) - .count() - }; - - // save data - let (pks, _) = save_data(&mut store, &map); - - // find 2 Marias, 1 John, and no Mary - assert_eq!(name_count(&map, &store, "Maria"), 2); - assert_eq!(name_count(&map, &store, "John"), 1); - assert_eq!(name_count(&map, &store, "Maria Luisa"), 1); - assert_eq!(name_count(&map, &store, "Mary"), 0); - - // remove maria 2 - map.remove(&mut store, pks[1], height).unwrap(); - height += 1; - - // change john to mary - map.update(&mut store, pks[2], height, |d| -> StdResult<_> { - let mut x = d.unwrap(); - assert_eq!(&x.name, "John"); - x.name = "Mary".to_string(); - Ok(x) - }) - .unwrap(); - - // find 1 maria, 1 maria luisa, no john, and 1 mary - assert_eq!(name_count(&map, &store, "Maria"), 1); - assert_eq!(name_count(&map, &store, "Maria Luisa"), 1); - assert_eq!(name_count(&map, &store, "John"), 0); - assert_eq!(name_count(&map, &store, "Mary"), 1); - } - - #[test] - fn range_raw_simple_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .age - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let ages = res.unwrap(); - - let count = ages.len(); - assert_eq!(4, count); - - // The pks, sorted by age ascending - assert_eq!(pks[3].as_bytes(), ages[0].0); - assert_eq!(pks[1].as_bytes(), ages[1].0); - assert_eq!(pks[2].as_bytes(), ages[2].0); - assert_eq!(pks[0].as_bytes(), ages[3].0); - - // The associated data - assert_eq!(datas[3], ages[0].1); - assert_eq!(datas[1], ages[1].1); - assert_eq!(datas[2], ages[2].1); - assert_eq!(datas[0], ages[3].1); - } - - #[test] - fn range_simple_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .age - .range(&store, None, None, Order::Ascending) - .collect(); - let ages = res.unwrap(); - - let count = ages.len(); - assert_eq!(4, count); - - // The pks, sorted by age ascending - assert_eq!(pks[3], ages[0].0); - assert_eq!(pks[1], ages[1].0); - assert_eq!(pks[2], ages[2].0); - assert_eq!(pks[0], ages[3].0); - - // The associated data - assert_eq!(datas[3], ages[0].1); - assert_eq!(datas[1], ages[1].1); - assert_eq!(datas[2], ages[2].1); - assert_eq!(datas[0], ages[3].1); - } - - #[test] - fn range_raw_composite_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .name_lastname - .prefix(b"Maria".to_vec()) - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let marias = res.unwrap(); - - // Only two people are called "Maria" - let count = marias.len(); - assert_eq!(2, count); - - // The pks - assert_eq!(pks[0].as_bytes(), marias[0].0); - assert_eq!(pks[1].as_bytes(), marias[1].0); - - // The associated data - assert_eq!(datas[0], marias[0].1); - assert_eq!(datas[1], marias[1].1); - } - - #[test] - fn range_composite_key_by_unique_index() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - let res: StdResult> = map - .idx - .name_lastname - .prefix(b"Maria".to_vec()) - .range(&store, None, None, Order::Ascending) - .collect(); - let marias = res.unwrap(); - - // Only two people are called "Maria" - let count = marias.len(); - assert_eq!(2, count); - - // The pks - assert_eq!(pks[0], marias[0].0); - assert_eq!(pks[1], marias[1].0); - - // The associated data - assert_eq!(datas[0], marias[0].1); - assert_eq!(datas[1], marias[1].1); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_string_key() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - // let's try to iterate! - let all: StdResult> = map.range(&store, None, None, Order::Ascending).collect(); - let all = all.unwrap(); - assert_eq!( - all, - pks.clone() - .into_iter() - .map(str::to_string) - .zip(datas.clone().into_iter()) - .collect::>() - ); - - // let's try to iterate over a range - let all: StdResult> = map - .range(&store, Some(Bound::inclusive("3")), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!( - all, - pks.into_iter() - .map(str::to_string) - .zip(datas.into_iter()) - .rev() - .take(2) - .rev() - .collect::>() - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_simple_string_key() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - // Let's prefix and iterate. - // This is similar to calling range() directly, but added here for completeness / prefix - // type checks - let all: StdResult> = map - .prefix(()) - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!( - all, - pks.clone() - .into_iter() - .map(str::to_string) - .zip(datas.into_iter()) - .collect::>() - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn sub_prefix_simple_string_key() { - let mut store = MockStorage::new(); - let map = build_snapshot_map(); - - // save data - let (pks, datas) = save_data(&mut store, &map); - - // Let's sub-prefix and iterate. - // This is similar to calling range() directly, but added here for completeness / sub_prefix - // type checks - let all: StdResult> = map - .sub_prefix(()) - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!( - all, - pks.clone() - .into_iter() - .map(str::to_string) - .zip(datas.into_iter()) - .collect::>() - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_range_simple_key() { - let mut store = MockStorage::new(); - - let indexes = DataCompositeMultiIndex { - name_age: MultiIndex::new( - |_pk, d| index_tuple(&d.name, d.age), - "data", - "data__name_age", - ), - }; - let map = - IndexedSnapshotMap::new("data", "checks", "changes", Strategy::EveryBlock, indexes); - - // save data - let data1 = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk1: (&str, &str) = ("1", "5627"); - map.save(&mut store, pk1, &data1, 1).unwrap(); - - let data2 = Data { - name: "Juan".to_string(), - last_name: "Perez".to_string(), - age: 13, - }; - let pk2: (&str, &str) = ("2", "5628"); - map.save(&mut store, pk2, &data2, 1).unwrap(); - - let data3 = Data { - name: "Maria".to_string(), - last_name: "Young".to_string(), - age: 24, - }; - let pk3: (&str, &str) = ("2", "5629"); - map.save(&mut store, pk3, &data3, 1).unwrap(); - - let data4 = Data { - name: "Maria Luisa".to_string(), - last_name: "Bemberg".to_string(), - age: 43, - }; - let pk4: (&str, &str) = ("3", "5630"); - map.save(&mut store, pk4, &data4, 1).unwrap(); - - // let's prefix-range and iterate - let result: StdResult> = map - .prefix_range( - &store, - Some(PrefixBound::inclusive("2")), - None, - Order::Ascending, - ) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [ - (("2".to_string(), "5628".to_string()), data2.clone()), - (("2".to_string(), "5629".to_string()), data3.clone()), - (("3".to_string(), "5630".to_string()), data4) - ] - ); - - // let's try to iterate over a more restrictive prefix-range! - let result: StdResult> = map - .prefix_range( - &store, - Some(PrefixBound::inclusive("2")), - Some(PrefixBound::exclusive("3")), - Order::Ascending, - ) - .collect(); - let result = result.unwrap(); - assert_eq!( - result, - [ - (("2".to_string(), "5628".to_string()), data2), - (("2".to_string(), "5629".to_string()), data3), - ] - ); - } -} diff --git a/packages/storage-plus/src/indexes/mod.rs b/packages/storage-plus/src/indexes/mod.rs deleted file mode 100644 index e2639ac83..000000000 --- a/packages/storage-plus/src/indexes/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -// this module requires iterator to be useful at all -#![cfg(feature = "iterator")] -mod multi; -mod unique; - -pub use multi::MultiIndex; -pub use unique::UniqueIndex; - -use serde::de::DeserializeOwned; -use serde::Serialize; - -use cosmwasm_std::{StdResult, Storage}; - -// Note: we cannot store traits with generic functions inside `Box`, -// so I pull S: Storage to a top-level -pub trait Index -where - T: Serialize + DeserializeOwned + Clone, -{ - fn save(&self, store: &mut dyn Storage, pk: &[u8], data: &T) -> StdResult<()>; - fn remove(&self, store: &mut dyn Storage, pk: &[u8], old_data: &T) -> StdResult<()>; -} - -#[cfg(test)] -pub mod test { - - pub fn index_string(data: &str) -> Vec { - data.as_bytes().to_vec() - } - - pub fn index_tuple(name: &str, age: u32) -> (Vec, u32) { - (index_string(name), age) - } - - pub fn index_string_tuple(data1: &str, data2: &str) -> (Vec, Vec) { - (index_string(data1), index_string(data2)) - } -} diff --git a/packages/storage-plus/src/indexes/multi.rs b/packages/storage-plus/src/indexes/multi.rs deleted file mode 100644 index b40ee202e..000000000 --- a/packages/storage-plus/src/indexes/multi.rs +++ /dev/null @@ -1,346 +0,0 @@ -// this module requires iterator to be useful at all -#![cfg(feature = "iterator")] - -use serde::de::DeserializeOwned; -use serde::Serialize; - -use cosmwasm_std::{from_slice, Order, Record, StdError, StdResult, Storage}; - -use crate::bound::PrefixBound; -use crate::de::KeyDeserialize; -use crate::helpers::namespaces_with_key; -use crate::iter_helpers::deserialize_kv; -use crate::map::Map; -use crate::prefix::namespaced_prefix_range; -use crate::{Bound, Index, Prefix, Prefixer, PrimaryKey}; -use std::marker::PhantomData; - -/// MultiIndex stores (namespace, index_name, idx_value, pk) -> b"pk_len". -/// Allows many values per index, and references pk. -/// The associated primary key value is stored in the main (pk_namespace) map, -/// which stores (namespace, pk_namespace, pk) -> value. -/// -/// The stored pk_len is used to recover the pk from the index namespace, and perform -/// the secondary load of the associated value from the main map. -/// -/// The PK type defines the type of Primary Key, both for deserialization, and -/// more important, as the type-safe bound key type. -/// This type must match the encompassing `IndexedMap` primary key type, -/// or its owned variant. -pub struct MultiIndex<'a, IK, T, PK> { - index: fn(&[u8], &T) -> IK, - idx_namespace: &'a [u8], - // note, we collapse the ik - combining everything under the namespace - and concatenating the pk - idx_map: Map<'a, Vec, u32>, - pk_namespace: &'a [u8], - phantom: PhantomData, -} - -impl<'a, IK, T, PK> MultiIndex<'a, IK, T, PK> -where - T: Serialize + DeserializeOwned + Clone, -{ - // TODO: make this a const fn - /// Create a new MultiIndex - /// - /// idx_fn - lambda creating index key from value - /// pk_namespace - prefix for the primary key - /// idx_namespace - prefix for the index value - /// - /// ## Example: - /// - /// ```rust - /// use cw_storage_plus::MultiIndex; - /// use serde::{Deserialize, Serialize}; - /// - /// #[derive(Deserialize, Serialize, Clone)] - /// struct Data { - /// pub name: String, - /// pub age: u32, - /// } - /// - /// let index: MultiIndex<_, _, String> = MultiIndex::new( - /// |_pk: &[u8], d: &Data| d.age, - /// "age", - /// "age__owner", - /// ); - /// ``` - pub fn new(idx_fn: fn(&[u8], &T) -> IK, pk_namespace: &'a str, idx_namespace: &'a str) -> Self { - MultiIndex { - index: idx_fn, - idx_namespace: idx_namespace.as_bytes(), - idx_map: Map::new(idx_namespace), - pk_namespace: pk_namespace.as_bytes(), - phantom: PhantomData, - } - } -} - -fn deserialize_multi_v( - store: &dyn Storage, - pk_namespace: &[u8], - kv: Record, -) -> StdResult> { - let (key, pk_len) = kv; - - // Deserialize pk_len - let pk_len = from_slice::(pk_len.as_slice())?; - - // Recover pk from last part of k - let offset = key.len() - pk_len as usize; - let pk = &key[offset..]; - - let full_key = namespaces_with_key(&[pk_namespace], pk); - - let v = store - .get(&full_key) - .ok_or_else(|| StdError::generic_err("pk not found"))?; - let v = from_slice::(&v)?; - - Ok((pk.to_vec(), v)) -} - -fn deserialize_multi_kv( - store: &dyn Storage, - pk_namespace: &[u8], - kv: Record, -) -> StdResult<(K::Output, T)> { - let (key, pk_len) = kv; - - // Deserialize pk_len - let pk_len = from_slice::(pk_len.as_slice())?; - - // Recover pk from last part of k - let offset = key.len() - pk_len as usize; - let pk = &key[offset..]; - - let full_key = namespaces_with_key(&[pk_namespace], pk); - - let v = store - .get(&full_key) - .ok_or_else(|| StdError::generic_err("pk not found"))?; - let v = from_slice::(&v)?; - - // We return deserialized `pk` here for consistency - Ok((K::from_slice(pk)?, v)) -} - -impl<'a, IK, T, PK> Index for MultiIndex<'a, IK, T, PK> -where - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a>, -{ - fn save(&self, store: &mut dyn Storage, pk: &[u8], data: &T) -> StdResult<()> { - let idx = (self.index)(pk, data).joined_extra_key(pk); - self.idx_map.save(store, idx, &(pk.len() as u32)) - } - - fn remove(&self, store: &mut dyn Storage, pk: &[u8], old_data: &T) -> StdResult<()> { - let idx = (self.index)(pk, old_data).joined_extra_key(pk); - self.idx_map.remove(store, idx); - Ok(()) - } -} - -impl<'a, IK, T, PK> MultiIndex<'a, IK, T, PK> -where - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a> + Prefixer<'a>, -{ - fn no_prefix_raw(&self) -> Prefix, T, (IK, PK)> { - Prefix::with_deserialization_functions( - self.idx_namespace, - &[], - self.pk_namespace, - deserialize_multi_v, - deserialize_multi_v, - ) - } -} - -impl<'a, IK, T, PK> MultiIndex<'a, IK, T, PK> -where - PK: PrimaryKey<'a> + KeyDeserialize, - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a> + Prefixer<'a>, -{ - pub fn index_key(&self, k: IK) -> Vec { - k.joined_extra_key(b"") - } - - #[cfg(test)] - pub fn count(&self, store: &dyn Storage, p: IK) -> usize { - let prefix = self.prefix(p); - prefix.keys_raw(store, None, None, Order::Ascending).count() - } - - #[cfg(test)] - pub fn all_pks(&self, store: &dyn Storage, p: IK) -> Vec> { - let prefix = self.prefix(p); - prefix - .keys_raw(store, None, None, Order::Ascending) - .collect::>>() - } - - #[cfg(test)] - pub fn all_items(&self, store: &dyn Storage, p: IK) -> StdResult>> { - let prefix = self.prefix(p); - prefix - .range_raw(store, None, None, Order::Ascending) - .collect() - } -} - -// short-cut for simple keys, rather than .prefix(()).range_raw(...) -impl<'a, IK, T, PK> MultiIndex<'a, IK, T, PK> -where - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, - PK: PrimaryKey<'a> + KeyDeserialize, -{ - // I would prefer not to copy code from Prefix, but no other way - // with lifetimes (create Prefix inside function and return ref = no no) - pub fn range_raw<'c>( - &'c self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box>> + 'c> - where - T: 'c, - { - self.no_prefix_raw().range_raw(store, min, max, order) - } - - pub fn keys_raw<'c>( - &'c self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box> + 'c> { - self.no_prefix_raw().keys_raw(store, min, max, order) - } - - /// While `range_raw` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range_raw` accepts bounds for the lowest and highest elements of the - /// `Prefix` itself, and iterates over those (inclusively or exclusively, depending on - /// `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range_raw<'c>( - &'c self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box>> + 'c> - where - T: 'c, - 'a: 'c, - { - let mapped = namespaced_prefix_range(store, self.idx_namespace, min, max, order) - .map(move |kv| (deserialize_multi_v)(store, self.pk_namespace, kv)); - Box::new(mapped) - } -} - -#[cfg(feature = "iterator")] -impl<'a, IK, T, PK> MultiIndex<'a, IK, T, PK> -where - PK: PrimaryKey<'a> + KeyDeserialize, - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a> + Prefixer<'a>, -{ - pub fn prefix(&self, p: IK) -> Prefix { - Prefix::with_deserialization_functions( - self.idx_namespace, - &p.prefix(), - self.pk_namespace, - deserialize_multi_kv::, - deserialize_multi_v, - ) - } - - pub fn sub_prefix(&self, p: IK::Prefix) -> Prefix { - Prefix::with_deserialization_functions( - self.idx_namespace, - &p.prefix(), - self.pk_namespace, - deserialize_multi_kv::, - deserialize_multi_v, - ) - } -} - -#[cfg(feature = "iterator")] -impl<'a, IK, T, PK> MultiIndex<'a, IK, T, PK> -where - PK: PrimaryKey<'a> + KeyDeserialize, - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a> + KeyDeserialize + Prefixer<'a>, -{ - /// While `range` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range` accepts bounds for the lowest and highest elements of the - /// `Prefix` itself, and iterates over those (inclusively or exclusively, depending on - /// `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - 'a: 'c, - IK: 'c, - PK: 'c, - PK::Output: 'static, - { - let mapped = namespaced_prefix_range(store, self.idx_namespace, min, max, order) - .map(deserialize_kv::); - Box::new(mapped) - } - - pub fn range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - PK::Output: 'static, - { - self.no_prefix().range(store, min, max, order) - } - - pub fn keys<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - PK::Output: 'static, - { - self.no_prefix().keys(store, min, max, order) - } - - fn no_prefix(&self) -> Prefix { - Prefix::with_deserialization_functions( - self.idx_namespace, - &[], - self.pk_namespace, - deserialize_multi_kv::, - deserialize_multi_v, - ) - } -} diff --git a/packages/storage-plus/src/indexes/unique.rs b/packages/storage-plus/src/indexes/unique.rs deleted file mode 100644 index 3143e5cff..000000000 --- a/packages/storage-plus/src/indexes/unique.rs +++ /dev/null @@ -1,257 +0,0 @@ -// this module requires iterator to be useful at all -#![cfg(feature = "iterator")] - -use std::marker::PhantomData; - -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{from_slice, Binary, Order, Record, StdError, StdResult, Storage}; - -use crate::bound::PrefixBound; -use crate::de::KeyDeserialize; -use crate::iter_helpers::deserialize_kv; -use crate::map::Map; -use crate::prefix::namespaced_prefix_range; -use crate::{Bound, Index, Prefix, Prefixer, PrimaryKey}; - -/// UniqueRef stores Binary(Vec[u8]) representation of private key and index value -#[derive(Deserialize, Serialize)] -pub(crate) struct UniqueRef { - // note, we collapse the pk - combining everything under the namespace - even if it is composite - pk: Binary, - value: T, -} - -/// UniqueIndex stores (namespace, index_name, idx_value) -> {key, value} -/// Allows one value per index (i.e. unique) and copies pk and data -/// The optional PK type defines the type of Primary Key deserialization. -pub struct UniqueIndex<'a, IK, T, PK = ()> { - index: fn(&T) -> IK, - idx_map: Map<'a, IK, UniqueRef>, - idx_namespace: &'a [u8], - phantom: PhantomData, -} - -impl<'a, IK, T, PK> UniqueIndex<'a, IK, T, PK> { - // TODO: make this a const fn - /// Create a new UniqueIndex - /// - /// idx_fn - lambda creating index key from index value - /// idx_namespace - prefix for the index value - /// - /// ## Example: - /// - /// ```rust - /// use cw_storage_plus::UniqueIndex; - /// - /// struct Data { - /// pub name: String, - /// pub age: u32, - /// } - /// - /// UniqueIndex::<_, _, ()>::new(|d: &Data| d.age, "data__age"); - /// ``` - pub fn new(idx_fn: fn(&T) -> IK, idx_namespace: &'a str) -> Self { - UniqueIndex { - index: idx_fn, - idx_map: Map::new(idx_namespace), - idx_namespace: idx_namespace.as_bytes(), - phantom: PhantomData, - } - } -} - -impl<'a, IK, T, PK> Index for UniqueIndex<'a, IK, T, PK> -where - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a>, -{ - fn save(&self, store: &mut dyn Storage, pk: &[u8], data: &T) -> StdResult<()> { - let idx = (self.index)(data); - // error if this is already set - self.idx_map - .update(store, idx, |existing| -> StdResult<_> { - match existing { - Some(_) => Err(StdError::generic_err("Violates unique constraint on index")), - None => Ok(UniqueRef:: { - pk: pk.into(), - value: data.clone(), - }), - } - })?; - Ok(()) - } - - fn remove(&self, store: &mut dyn Storage, _pk: &[u8], old_data: &T) -> StdResult<()> { - let idx = (self.index)(old_data); - self.idx_map.remove(store, idx); - Ok(()) - } -} - -fn deserialize_unique_v(kv: Record) -> StdResult> { - let (_, v) = kv; - let t = from_slice::>(&v)?; - Ok((t.pk.0, t.value)) -} - -fn deserialize_unique_kv( - kv: Record, -) -> StdResult<(K::Output, T)> { - let (_, v) = kv; - let t = from_slice::>(&v)?; - Ok((K::from_vec(t.pk.0)?, t.value)) -} - -impl<'a, IK, T, PK> UniqueIndex<'a, IK, T, PK> -where - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a>, -{ - pub fn index_key(&self, k: IK) -> Vec { - k.joined_key() - } - - fn no_prefix_raw(&self) -> Prefix, T, IK> { - Prefix::with_deserialization_functions( - self.idx_namespace, - &[], - &[], - |_, _, kv| deserialize_unique_v(kv), - |_, _, kv| deserialize_unique_v(kv), - ) - } - - /// returns all items that match this secondary index, always by pk Ascending - pub fn item(&self, store: &dyn Storage, idx: IK) -> StdResult>> { - let data = self - .idx_map - .may_load(store, idx)? - .map(|i| (i.pk.into(), i.value)); - Ok(data) - } -} - -// short-cut for simple keys, rather than .prefix(()).range_raw(...) -impl<'a, IK, T, PK> UniqueIndex<'a, IK, T, PK> -where - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a>, -{ - // I would prefer not to copy code from Prefix, but no other way - // with lifetimes (create Prefix inside function and return ref = no no) - pub fn range_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box>> + 'c> - where - T: 'c, - { - self.no_prefix_raw().range_raw(store, min, max, order) - } - - pub fn keys_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box> + 'c> { - self.no_prefix_raw().keys_raw(store, min, max, order) - } -} - -#[cfg(feature = "iterator")] -impl<'a, IK, T, PK> UniqueIndex<'a, IK, T, PK> -where - PK: PrimaryKey<'a> + KeyDeserialize, - T: Serialize + DeserializeOwned + Clone, - IK: PrimaryKey<'a>, -{ - /// While `range` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range` accepts bounds for the lowest and highest elements of the - /// `Prefix` itself, and iterates over those (inclusively or exclusively, depending on - /// `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - 'a: 'c, - IK: 'c, - PK: 'c, - PK::Output: 'static, - { - let mapped = namespaced_prefix_range(store, self.idx_namespace, min, max, order) - .map(deserialize_kv::); - Box::new(mapped) - } - - pub fn range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - PK::Output: 'static, - { - self.no_prefix().range(store, min, max, order) - } - - pub fn keys<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - PK::Output: 'static, - { - self.no_prefix().keys(store, min, max, order) - } - - pub fn prefix(&self, p: IK::Prefix) -> Prefix { - Prefix::with_deserialization_functions( - self.idx_namespace, - &p.prefix(), - &[], - |_, _, kv| deserialize_unique_kv::(kv), - |_, _, kv| deserialize_unique_v(kv), - ) - } - - pub fn sub_prefix(&self, p: IK::SubPrefix) -> Prefix { - Prefix::with_deserialization_functions( - self.idx_namespace, - &p.prefix(), - &[], - |_, _, kv| deserialize_unique_kv::(kv), - |_, _, kv| deserialize_unique_v(kv), - ) - } - - fn no_prefix(&self) -> Prefix { - Prefix::with_deserialization_functions( - self.idx_namespace, - &[], - &[], - |_, _, kv| deserialize_unique_kv::(kv), - |_, _, kv| deserialize_unique_v(kv), - ) - } -} diff --git a/packages/storage-plus/src/int_key.rs b/packages/storage-plus/src/int_key.rs deleted file mode 100644 index 0292674a2..000000000 --- a/packages/storage-plus/src/int_key.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::mem; - -/// Our int keys are simply the big-endian representation bytes for unsigned ints, -/// but "sign-flipped" (xored msb) big-endian bytes for signed ints. -/// -/// So that the representation of signed integers is in the right lexicographical order. -pub trait IntKey: Sized + Copy { - type Buf: AsRef<[u8]> + AsMut<[u8]> + Into> + Default; - - fn to_cw_bytes(&self) -> Self::Buf; - fn from_cw_bytes(bytes: Self::Buf) -> Self; -} - -macro_rules! cw_uint_keys { - (for $($t:ty),+) => { - $(impl IntKey for $t { - type Buf = [u8; mem::size_of::<$t>()]; - - #[inline] - fn to_cw_bytes(&self) -> Self::Buf { - self.to_be_bytes() - } - - #[inline] - fn from_cw_bytes(bytes: Self::Buf) -> Self { - Self::from_be_bytes(bytes) - } - })* - } -} - -cw_uint_keys!(for u8, u16, u32, u64, u128); - -macro_rules! cw_int_keys { - (for $($t:ty, $ut:ty),+) => { - $(impl IntKey for $t { - type Buf = [u8; mem::size_of::<$t>()]; - - #[inline] - fn to_cw_bytes(&self) -> Self::Buf { - (*self as $ut ^ <$t>::MIN as $ut).to_be_bytes() - } - - #[inline] - fn from_cw_bytes(bytes: Self::Buf) -> Self { - (Self::from_be_bytes(bytes) as $ut ^ <$t>::MIN as $ut) as _ - } - })* - } -} - -cw_int_keys!(for i8, u8, i16, u16, i32, u32, i64, u64, i128, u128); - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn x8_int_key_works() { - assert_eq!(0x42u8.to_cw_bytes(), [0x42]); - assert_eq!(0x42i8.to_cw_bytes(), [0xc2]); - assert_eq!((-0x3ei8).to_cw_bytes(), [0x42]); - } - - #[test] - fn x16_int_key_works() { - assert_eq!(0x4243u16.to_cw_bytes(), [0x42, 0x43]); - assert_eq!(0x4243i16.to_cw_bytes(), [0xc2, 0x43]); - assert_eq!((-0x3dbdi16).to_cw_bytes(), [0x42, 0x43]); - } - - #[test] - fn x32_int_key_works() { - assert_eq!(0x424344u32.to_cw_bytes(), [0x00, 0x42, 0x43, 0x44]); - assert_eq!(0x424344i32.to_cw_bytes(), [0x80, 0x42, 0x43, 0x44]); - assert_eq!((-0x7fbdbcbci32).to_cw_bytes(), [0x00, 0x42, 0x43, 0x44]); - } - - #[test] - fn x64_int_key_works() { - assert_eq!( - 0x42434445u64.to_cw_bytes(), - [0x00, 0x00, 0x00, 0x00, 0x42, 0x43, 0x44, 0x45] - ); - assert_eq!( - 0x42434445i64.to_cw_bytes(), - [0x80, 0x00, 0x00, 0x00, 0x42, 0x43, 0x44, 0x45] - ); - assert_eq!( - (-0x7fffffffbdbcbbbbi64).to_cw_bytes(), - [0x00, 0x00, 0x00, 0x00, 0x42, 0x43, 0x44, 0x45] - ); - } - - #[test] - fn x128_int_key_works() { - assert_eq!( - 0x4243444546u128.to_cw_bytes(), - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x43, 0x44, - 0x45, 0x46 - ] - ); - assert_eq!( - 0x4243444546i128.to_cw_bytes(), - [ - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x43, 0x44, - 0x45, 0x46 - ] - ); - assert_eq!( - (-0x7fffffffffffffffffffffbdbcbbbabai128).to_cw_bytes(), - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x43, 0x44, - 0x45, 0x46 - ] - ); - } - - #[test] - fn unsigned_int_key_order() { - assert!(0u32.to_cw_bytes() < 652u32.to_cw_bytes()); - } - - #[test] - fn signed_int_key_order() { - assert!((-321i32).to_cw_bytes() < 0i32.to_cw_bytes()); - assert!(0i32.to_cw_bytes() < 652i32.to_cw_bytes()); - } -} diff --git a/packages/storage-plus/src/item.rs b/packages/storage-plus/src/item.rs deleted file mode 100644 index 865fd534e..000000000 --- a/packages/storage-plus/src/item.rs +++ /dev/null @@ -1,319 +0,0 @@ -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::marker::PhantomData; - -use cosmwasm_std::{ - to_vec, Addr, CustomQuery, QuerierWrapper, StdError, StdResult, Storage, WasmQuery, -}; - -use crate::helpers::{may_deserialize, must_deserialize}; - -/// Item stores one typed item at the given key. -/// This is an analog of Singleton. -/// It functions the same way as Path does but doesn't use a Vec and thus has a const fn constructor. -pub struct Item<'a, T> { - // this is full key - no need to length-prefix it, we only store one item - storage_key: &'a [u8], - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data_type: PhantomData, -} - -impl<'a, T> Item<'a, T> { - pub const fn new(storage_key: &'a str) -> Self { - Item { - storage_key: storage_key.as_bytes(), - data_type: PhantomData, - } - } -} - -impl<'a, T> Item<'a, T> -where - T: Serialize + DeserializeOwned, -{ - // this gets the path of the data to use elsewhere - pub fn as_slice(&self) -> &[u8] { - self.storage_key - } - - /// save will serialize the model and store, returns an error on serialization issues - pub fn save(&self, store: &mut dyn Storage, data: &T) -> StdResult<()> { - store.set(self.storage_key, &to_vec(data)?); - Ok(()) - } - - pub fn remove(&self, store: &mut dyn Storage) { - store.remove(self.storage_key); - } - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, store: &dyn Storage) -> StdResult { - let value = store.get(self.storage_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, store: &dyn Storage) -> StdResult> { - let value = store.get(self.storage_key); - may_deserialize(&value) - } - - /// 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. - /// - /// It assumes, that data was initialized before, and if it doesn't exist, `Err(StdError::NotFound)` - /// is returned. - pub fn update(&self, store: &mut dyn Storage, action: A) -> Result - where - A: FnOnce(T) -> Result, - E: From, - { - let input = self.load(store)?; - let output = action(input)?; - self.save(store, &output)?; - Ok(output) - } - - /// If you import the proper Item from the remote contract, this will let you read the data - /// from a remote contract in a type-safe way using WasmQuery::RawQuery. - /// - /// Note that we expect an Item to be set, and error if there is no data there - pub fn query( - &self, - querier: &QuerierWrapper, - remote_contract: Addr, - ) -> StdResult { - let request = WasmQuery::Raw { - contract_addr: remote_contract.into(), - key: self.storage_key.into(), - }; - querier.query(&request.into()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::testing::MockStorage; - use serde::{Deserialize, Serialize}; - - use cosmwasm_std::{OverflowError, OverflowOperation, StdError}; - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Config { - pub owner: String, - pub max_tokens: i32, - } - - // note const constructor rather than 2 funcs with Singleton - const CONFIG: Item = Item::new("config"); - - #[test] - fn save_and_load() { - let mut store = MockStorage::new(); - - assert!(CONFIG.load(&store).is_err()); - assert_eq!(CONFIG.may_load(&store).unwrap(), None); - - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg).unwrap(); - - assert_eq!(cfg, CONFIG.load(&store).unwrap()); - } - - #[test] - fn remove_works() { - let mut store = MockStorage::new(); - - // store data - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg).unwrap(); - assert_eq!(cfg, CONFIG.load(&store).unwrap()); - - // remove it and loads None - CONFIG.remove(&mut store); - assert_eq!(None, CONFIG.may_load(&store).unwrap()); - - // safe to remove 2 times - CONFIG.remove(&mut store); - assert_eq!(None, CONFIG.may_load(&store).unwrap()); - } - - #[test] - fn isolated_reads() { - let mut store = MockStorage::new(); - - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg).unwrap(); - - let reader = Item::::new("config"); - assert_eq!(cfg, reader.load(&store).unwrap()); - - let other_reader = Item::::new("config2"); - assert_eq!(other_reader.may_load(&store).unwrap(), None); - } - - #[test] - fn update_success() { - let mut store = MockStorage::new(); - - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg).unwrap(); - - let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> { - c.max_tokens *= 2; - Ok(c) - }); - let expected = Config { - owner: "admin".to_string(), - max_tokens: 2468, - }; - assert_eq!(output.unwrap(), expected); - assert_eq!(CONFIG.load(&store).unwrap(), expected); - } - - #[test] - fn update_can_change_variable_from_outer_scope() { - let mut store = MockStorage::new(); - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg).unwrap(); - - let mut old_max_tokens = 0i32; - CONFIG - .update(&mut store, |mut c| -> StdResult<_> { - old_max_tokens = c.max_tokens; - c.max_tokens *= 2; - Ok(c) - }) - .unwrap(); - assert_eq!(old_max_tokens, 1234); - } - - #[test] - fn update_does_not_change_data_on_error() { - let mut store = MockStorage::new(); - - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg).unwrap(); - - let output = CONFIG.update(&mut store, &|_c| { - Err(StdError::overflow(OverflowError::new( - OverflowOperation::Sub, - 4, - 7, - ))) - }); - match output.unwrap_err() { - StdError::Overflow { .. } => {} - err => panic!("Unexpected error: {:?}", err), - } - assert_eq!(CONFIG.load(&store).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 cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg).unwrap(); - - let res = CONFIG.update(&mut store, |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!(CONFIG.load(&store).unwrap(), cfg); - } - - #[test] - fn readme_works() -> StdResult<()> { - let mut store = MockStorage::new(); - - // may_load returns Option, so None if data is missing - // load returns T and Err(StdError::NotFound{}) if data is missing - let empty = CONFIG.may_load(&store)?; - assert_eq!(None, empty); - let cfg = Config { - owner: "admin".to_string(), - max_tokens: 1234, - }; - CONFIG.save(&mut store, &cfg)?; - let loaded = CONFIG.load(&store)?; - assert_eq!(cfg, loaded); - - // update an item with a closure (includes read and write) - // returns the newly saved value - let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> { - c.max_tokens *= 2; - Ok(c) - })?; - assert_eq!(2468, output.max_tokens); - - // you can error in an update and nothing is saved - let failed = CONFIG.update(&mut store, |_| -> StdResult<_> { - Err(StdError::generic_err("failure mode")) - }); - assert!(failed.is_err()); - - // loading data will show the first update was saved - let loaded = CONFIG.load(&store)?; - let expected = Config { - owner: "admin".to_string(), - max_tokens: 2468, - }; - assert_eq!(expected, loaded); - - // we can remove data as well - CONFIG.remove(&mut store); - let empty = CONFIG.may_load(&store)?; - assert_eq!(None, empty); - - Ok(()) - } -} diff --git a/packages/storage-plus/src/iter_helpers.rs b/packages/storage-plus/src/iter_helpers.rs deleted file mode 100644 index fa9ad6641..000000000 --- a/packages/storage-plus/src/iter_helpers.rs +++ /dev/null @@ -1,222 +0,0 @@ -#![cfg(feature = "iterator")] - -use serde::de::DeserializeOwned; - -use cosmwasm_std::Record; -use cosmwasm_std::{from_slice, StdResult}; - -use crate::de::KeyDeserialize; -use crate::helpers::encode_length; - -#[allow(dead_code)] -pub(crate) fn deserialize_v(kv: Record) -> StdResult> { - let (k, v) = kv; - let t = from_slice::(&v)?; - Ok((k, t)) -} - -pub(crate) fn deserialize_kv( - kv: Record, -) -> StdResult<(K::Output, T)> { - let (k, v) = kv; - let kt = K::from_vec(k)?; - let vt = from_slice::(&v)?; - Ok((kt, vt)) -} - -/// Calculates the raw key prefix for a given namespace as documented -/// in https://github.com/webmaster128/key-namespacing#length-prefixed-keys -#[allow(dead_code)] -pub(crate) fn to_length_prefixed(namespace: &[u8]) -> Vec { - let mut out = Vec::with_capacity(namespace.len() + 2); - out.extend_from_slice(&encode_length(namespace)); - out.extend_from_slice(namespace); - out -} - -// TODO: add a check here that it is the real prefix? -#[inline] -pub(crate) fn trim(namespace: &[u8], key: &[u8]) -> Vec { - key[namespace.len()..].to_vec() -} - -#[inline] -pub(crate) fn concat(namespace: &[u8], key: &[u8]) -> Vec { - let mut k = namespace.to_vec(); - k.extend_from_slice(key); - k -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn to_length_prefixed_works() { - assert_eq!(to_length_prefixed(b""), b"\x00\x00"); - assert_eq!(to_length_prefixed(b"a"), b"\x00\x01a"); - assert_eq!(to_length_prefixed(b"ab"), b"\x00\x02ab"); - assert_eq!(to_length_prefixed(b"abc"), b"\x00\x03abc"); - } - - #[test] - fn to_length_prefixed_works_for_long_prefix() { - let long_namespace1 = vec![0; 256]; - let prefix1 = to_length_prefixed(&long_namespace1); - assert_eq!(prefix1.len(), 256 + 2); - assert_eq!(&prefix1[0..2], b"\x01\x00"); - - let long_namespace2 = vec![0; 30000]; - let prefix2 = to_length_prefixed(&long_namespace2); - assert_eq!(prefix2.len(), 30000 + 2); - assert_eq!(&prefix2[0..2], b"\x75\x30"); - - let long_namespace3 = vec![0; 0xFFFF]; - let prefix3 = to_length_prefixed(&long_namespace3); - assert_eq!(prefix3.len(), 0xFFFF + 2); - assert_eq!(&prefix3[0..2], b"\xFF\xFF"); - } - - #[test] - #[should_panic(expected = "only supports namespaces up to length 0xFFFF")] - fn to_length_prefixed_panics_for_too_long_prefix() { - let limit = 0xFFFF; - let long_namespace = vec![0; limit + 1]; - to_length_prefixed(&long_namespace); - } - - #[test] - fn to_length_prefixed_calculates_capacity_correctly() { - // Those tests cannot guarantee the required capacity was calculated correctly before - // the vector allocation but increase the likelyhood of a proper implementation. - - let key = to_length_prefixed(b""); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed(b"h"); - assert_eq!(key.capacity(), key.len()); - - let key = to_length_prefixed(b"hij"); - assert_eq!(key.capacity(), key.len()); - } -} - -// currently disabled tests as they require a bunch of legacy non-sense -// TODO: enable -#[cfg(test)] -#[cfg(not(feature = "iterator"))] -mod namespace_test { - use super::*; - use cosmwasm_std::testing::MockStorage; - - #[test] - fn test_range() { - let mut storage = MockStorage::new(); - let prefix = to_length_prefixed(b"foo"); - let other_prefix = to_length_prefixed(b"food"); - - // set some values in this range - set_with_prefix(&mut storage, &prefix, b"bar", b"none"); - set_with_prefix(&mut storage, &prefix, b"snowy", b"day"); - - // set some values outside this range - set_with_prefix(&mut storage, &other_prefix, b"moon", b"buggy"); - - // ensure we get proper result from prefixed_range iterator - let mut iter = range_with_prefix(&storage, &prefix, None, None, Order::Descending); - let first = iter.next().unwrap(); - assert_eq!(first, (b"snowy".to_vec(), b"day".to_vec())); - let second = iter.next().unwrap(); - assert_eq!(second, (b"bar".to_vec(), b"none".to_vec())); - assert!(iter.next().is_none()); - - // ensure we get raw result from base range - let iter = storage.range(None, None, Order::Ascending); - assert_eq!(3, iter.count()); - - // foo comes first - let mut iter = storage.range(None, None, Order::Ascending); - let first = iter.next().unwrap(); - let expected_key = concat(&prefix, b"bar"); - assert_eq!(first, (expected_key, b"none".to_vec())); - } - - #[test] - fn test_range_with_prefix_wrapover() { - let mut storage = MockStorage::new(); - // if we don't properly wrap over there will be issues here (note 255+1 is used to calculate end) - let prefix = to_length_prefixed(b"f\xff\xff"); - let other_prefix = to_length_prefixed(b"f\xff\x44"); - - // set some values in this range - set_with_prefix(&mut storage, &prefix, b"bar", b"none"); - set_with_prefix(&mut storage, &prefix, b"snowy", b"day"); - - // set some values outside this range - set_with_prefix(&mut storage, &other_prefix, b"moon", b"buggy"); - - // ensure we get proper result from prefixed_range iterator - let iter = range_with_prefix(&storage, &prefix, None, None, Order::Descending); - let elements: Vec = iter.collect(); - assert_eq!( - elements, - vec![ - (b"snowy".to_vec(), b"day".to_vec()), - (b"bar".to_vec(), b"none".to_vec()), - ] - ); - } - - #[test] - fn test_range_with_start_end_set() { - let mut storage = MockStorage::new(); - // if we don't properly wrap over there will be issues here (note 255+1 is used to calculate end) - let prefix = to_length_prefixed(b"f\xff\xff"); - let other_prefix = to_length_prefixed(b"f\xff\x44"); - - // set some values in this range - set_with_prefix(&mut storage, &prefix, b"bar", b"none"); - set_with_prefix(&mut storage, &prefix, b"snowy", b"day"); - - // set some values outside this range - set_with_prefix(&mut storage, &other_prefix, b"moon", b"buggy"); - - // make sure start and end are applied properly - let res: Vec = - range_with_prefix(&storage, &prefix, Some(b"b"), Some(b"c"), Order::Ascending) - .collect(); - assert_eq!(res.len(), 1); - assert_eq!(res[0], (b"bar".to_vec(), b"none".to_vec())); - - // make sure start and end are applied properly - let res: Vec = range_with_prefix( - &storage, - &prefix, - Some(b"bas"), - Some(b"sno"), - Order::Ascending, - ) - .collect(); - assert_eq!(res.len(), 0); - - let res: Vec = - range_with_prefix(&storage, &prefix, Some(b"ant"), None, Order::Ascending).collect(); - assert_eq!(res.len(), 2); - assert_eq!(res[0], (b"bar".to_vec(), b"none".to_vec())); - assert_eq!(res[1], (b"snowy".to_vec(), b"day".to_vec())); - } - - #[test] - fn test_namespace_upper_bound() { - assert_eq!(namespace_upper_bound(b"bob"), b"boc".to_vec()); - assert_eq!(namespace_upper_bound(b"fo\xfe"), b"fo\xff".to_vec()); - assert_eq!(namespace_upper_bound(b"fo\xff"), b"fp\x00".to_vec()); - // multiple \xff roll over - assert_eq!( - namespace_upper_bound(b"fo\xff\xff\xff"), - b"fp\x00\x00\x00".to_vec() - ); - // \xff not at the end are ignored - assert_eq!(namespace_upper_bound(b"\xffabc"), b"\xffabd".to_vec()); - } -} diff --git a/packages/storage-plus/src/keys.rs b/packages/storage-plus/src/keys.rs deleted file mode 100644 index cf7eff867..000000000 --- a/packages/storage-plus/src/keys.rs +++ /dev/null @@ -1,512 +0,0 @@ -use cosmwasm_std::Addr; - -use crate::de::KeyDeserialize; -use crate::helpers::namespaces_with_key; -use crate::int_key::IntKey; - -#[derive(Debug)] -pub enum Key<'a> { - Ref(&'a [u8]), - Val8([u8; 1]), - Val16([u8; 2]), - Val32([u8; 4]), - Val64([u8; 8]), - Val128([u8; 16]), -} - -impl<'a> AsRef<[u8]> for Key<'a> { - fn as_ref(&self) -> &[u8] { - match self { - Key::Ref(r) => r, - Key::Val8(v) => v, - Key::Val16(v) => v, - Key::Val32(v) => v, - Key::Val64(v) => v, - Key::Val128(v) => v, - } - } -} - -impl<'a> PartialEq<&[u8]> for Key<'a> { - fn eq(&self, other: &&[u8]) -> bool { - self.as_ref() == *other - } -} - -/// `PrimaryKey` needs to be implemented for types that want to be a `Map` (or `Map`-like) key, -/// or part of a key. -/// -/// In particular, it defines a series of types that help iterating over parts of a (composite) key: -/// -/// `Prefix`: Prefix is eager. That is, except for empty keys, it's always "one less" than the full key. -/// `Suffix`: Suffix is the complement of prefix. -/// `SubPrefix`: Sub-prefix is "one less" than prefix. -/// `SuperSuffix`: Super-suffix is "one more" than suffix. The complement of sub-prefix. -/// -/// By example, for a 2-tuple `(T, U)`: -/// -/// `T`: Prefix. -/// `U`: Suffix. -/// `()`: Sub-prefix. -/// `(T, U)`: Super-suffix. -/// -/// `SubPrefix` and `SuperSuffix` only make real sense in the case of triples. Still, they need to be -/// consistently defined for all types. -pub trait PrimaryKey<'a>: Clone { - /// These associated types need to implement `Prefixer`, so that they can be useful arguments - /// for `prefix()`, `sub_prefix()`, and their key-deserializable variants. - type Prefix: Prefixer<'a>; - type SubPrefix: Prefixer<'a>; - - /// These associated types need to implement `KeyDeserialize`, so that they can be returned from - /// `range_de()` and friends. - type Suffix: KeyDeserialize; - type SuperSuffix: KeyDeserialize; - - /// returns a slice of key steps, which can be optionally combined - fn key(&self) -> Vec; - - fn joined_key(&self) -> Vec { - let keys = self.key(); - let l = keys.len(); - namespaces_with_key( - &keys[0..l - 1].iter().map(Key::as_ref).collect::>(), - keys[l - 1].as_ref(), - ) - } - - fn joined_extra_key(&self, key: &[u8]) -> Vec { - let keys = self.key(); - namespaces_with_key(&keys.iter().map(Key::as_ref).collect::>(), key) - } -} - -// Empty / no primary key -impl<'a> PrimaryKey<'a> for () { - type Prefix = Self; - type SubPrefix = Self; - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - vec![] - } -} - -impl<'a> PrimaryKey<'a> for &'a [u8] { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - // this is simple, we don't add more prefixes - vec![Key::Ref(self)] - } -} - -// Provide a string version of this to raw encode strings -impl<'a> PrimaryKey<'a> for &'a str { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - // this is simple, we don't add more prefixes - vec![Key::Ref(self.as_bytes())] - } -} - -// use generics for combining there - so we can use &[u8], Vec, or IntKey -impl<'a, T: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, U: PrimaryKey<'a> + KeyDeserialize> - PrimaryKey<'a> for (T, U) -{ - type Prefix = T; - type SubPrefix = (); - type Suffix = U; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - let mut keys = self.0.key(); - keys.extend(self.1.key()); - keys - } -} - -// use generics for combining there - so we can use &[u8], Vec, or IntKey -impl< - 'a, - T: PrimaryKey<'a> + Prefixer<'a>, - U: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, - V: PrimaryKey<'a> + KeyDeserialize, - > PrimaryKey<'a> for (T, U, V) -{ - type Prefix = (T, U); - type SubPrefix = T; - type Suffix = V; - type SuperSuffix = (U, V); - - fn key(&self) -> Vec { - let mut keys = self.0.key(); - keys.extend(self.1.key()); - keys.extend(self.2.key()); - keys - } -} - -pub trait Prefixer<'a> { - /// returns 0 or more namespaces that should be length-prefixed and concatenated for range searches - fn prefix(&self) -> Vec; - - fn joined_prefix(&self) -> Vec { - let prefixes = self.prefix(); - namespaces_with_key(&prefixes.iter().map(Key::as_ref).collect::>(), &[]) - } -} - -impl<'a> Prefixer<'a> for () { - fn prefix(&self) -> Vec { - vec![] - } -} - -impl<'a> Prefixer<'a> for &'a [u8] { - fn prefix(&self) -> Vec { - vec![Key::Ref(self)] - } -} - -impl<'a, T: Prefixer<'a>, U: Prefixer<'a>> Prefixer<'a> for (T, U) { - fn prefix(&self) -> Vec { - let mut res = self.0.prefix(); - res.extend(self.1.prefix().into_iter()); - res - } -} - -impl<'a, T: Prefixer<'a>, U: Prefixer<'a>, V: Prefixer<'a>> Prefixer<'a> for (T, U, V) { - fn prefix(&self) -> Vec { - let mut res = self.0.prefix(); - res.extend(self.1.prefix().into_iter()); - res.extend(self.2.prefix().into_iter()); - res - } -} - -// Provide a string version of this to raw encode strings -impl<'a> Prefixer<'a> for &'a str { - fn prefix(&self) -> Vec { - vec![Key::Ref(self.as_bytes())] - } -} - -impl<'a> PrimaryKey<'a> for Vec { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - vec![Key::Ref(self)] - } -} - -impl<'a> Prefixer<'a> for Vec { - fn prefix(&self) -> Vec { - vec![Key::Ref(self.as_ref())] - } -} - -impl<'a> PrimaryKey<'a> for String { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - vec![Key::Ref(self.as_bytes())] - } -} - -impl<'a> Prefixer<'a> for String { - fn prefix(&self) -> Vec { - vec![Key::Ref(self.as_bytes())] - } -} - -/// type safe version to ensure address was validated before use. -impl<'a> PrimaryKey<'a> for &'a Addr { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - // this is simple, we don't add more prefixes - vec![Key::Ref(self.as_ref().as_bytes())] - } -} - -impl<'a> Prefixer<'a> for &'a Addr { - fn prefix(&self) -> Vec { - vec![Key::Ref(self.as_bytes())] - } -} - -/// owned variant. -impl<'a> PrimaryKey<'a> for Addr { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - // this is simple, we don't add more prefixes - vec![Key::Ref(self.as_bytes())] - } -} - -impl<'a> Prefixer<'a> for Addr { - fn prefix(&self) -> Vec { - vec![Key::Ref(self.as_bytes())] - } -} - -macro_rules! integer_key { - (for $($t:ty, $v:tt),+) => { - $(impl<'a> PrimaryKey<'a> for $t { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - vec![Key::$v(self.to_cw_bytes())] - } - })* - } -} - -integer_key!(for i8, Val8, u8, Val8, i16, Val16, u16, Val16, i32, Val32, u32, Val32, i64, Val64, u64, Val64, i128, Val128, u128, Val128); - -macro_rules! integer_prefix { - (for $($t:ty, $v:tt),+) => { - $(impl<'a> Prefixer<'a> for $t { - fn prefix(&self) -> Vec { - vec![Key::$v(self.to_cw_bytes())] - } - })* - } -} - -integer_prefix!(for i8, Val8, u8, Val8, i16, Val16, u16, Val16, i32, Val32, u32, Val32, i64, Val64, u64, Val64, i128, Val128, u128, Val128); - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn naked_8key_works() { - let k: u8 = 42u8; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(42u8.to_cw_bytes(), path[0].as_ref()); - - let k: i8 = 42i8; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(42i8.to_cw_bytes(), path[0].as_ref()); - } - - #[test] - fn naked_16key_works() { - let k: u16 = 4242u16; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242u16.to_cw_bytes(), path[0].as_ref()); - - let k: i16 = 4242i16; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242i16.to_cw_bytes(), path[0].as_ref()); - } - - #[test] - fn naked_32key_works() { - let k: u32 = 4242u32; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242u32.to_cw_bytes(), path[0].as_ref()); - - let k: i32 = 4242i32; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242i32.to_cw_bytes(), path[0].as_ref()); - } - - #[test] - fn naked_64key_works() { - let k: u64 = 4242u64; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242u64.to_cw_bytes(), path[0].as_ref()); - - let k: i64 = 4242i64; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242i64.to_cw_bytes(), path[0].as_ref()); - } - - #[test] - fn naked_128key_works() { - let k: u128 = 4242u128; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242u128.to_cw_bytes(), path[0].as_ref()); - - let k: i128 = 4242i128; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(4242i128.to_cw_bytes(), path[0].as_ref()); - } - - #[test] - fn str_key_works() { - type K<'a> = &'a str; - - let k: K = "hello"; - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(b"hello", path[0].as_ref()); - - let joined = k.joined_key(); - assert_eq!(joined, b"hello") - } - - #[test] - fn string_key_works() { - type K = String; - - let k: K = "hello".to_string(); - let path = k.key(); - assert_eq!(1, path.len()); - assert_eq!(b"hello", path[0].as_ref()); - - let joined = k.joined_key(); - assert_eq!(joined, b"hello") - } - - #[test] - fn nested_str_key_works() { - type K<'a> = (&'a str, &'a [u8]); - - let k: K = ("hello", b"world"); - let path = k.key(); - assert_eq!(2, path.len()); - assert_eq!(b"hello", path[0].as_ref()); - assert_eq!(b"world", path[1].as_ref()); - } - - #[test] - fn composite_byte_key() { - let k: (&[u8], &[u8]) = ("foo".as_bytes(), b"bar"); - let path = k.key(); - assert_eq!(2, path.len()); - assert_eq!(path, vec!["foo".as_bytes(), b"bar"],); - } - - #[test] - fn naked_composite_int_key() { - let k: (u32, u64) = (123, 87654); - let path = k.key(); - assert_eq!(2, path.len()); - assert_eq!(4, path[0].as_ref().len()); - assert_eq!(8, path[1].as_ref().len()); - assert_eq!(path[0].as_ref(), 123u32.to_cw_bytes()); - assert_eq!(path[1].as_ref(), 87654u64.to_cw_bytes()); - } - - #[test] - fn nested_composite_keys() { - // use this to ensure proper type-casts below - let first: &[u8] = b"foo"; - // this function tests how well the generics extend to "edge cases" - let k: ((&[u8], &[u8]), &[u8]) = ((first, b"bar"), b"zoom"); - let path = k.key(); - assert_eq!(3, path.len()); - assert_eq!(path, vec![first, b"bar", b"zoom"]); - - // ensure prefix also works - let dir = k.0.prefix(); - assert_eq!(2, dir.len()); - assert_eq!(dir, vec![first, b"bar"]); - } - - #[test] - fn naked_8bit_prefixes() { - let pair: (u8, &[u8]) = (123, b"random"); - let one: Vec = vec![123]; - let two: Vec = b"random".to_vec(); - assert_eq!(pair.prefix(), vec![one.as_slice(), two.as_slice()]); - - let pair: (i8, &[u8]) = (123, b"random"); - // Signed int keys are "sign-flipped" - let one: Vec = vec![123 ^ 0x80]; - let two: Vec = b"random".to_vec(); - assert_eq!(pair.prefix(), vec![one.as_slice(), two.as_slice()]); - } - - #[test] - fn naked_16bit_prefixes() { - let pair: (u16, &[u8]) = (12345, b"random"); - let one: Vec = vec![48, 57]; - let two: Vec = b"random".to_vec(); - assert_eq!(pair.prefix(), vec![one.as_slice(), two.as_slice()]); - - let pair: (i16, &[u8]) = (12345, b"random"); - // Signed int keys are "sign-flipped" - let one: Vec = vec![48 ^ 0x80, 57]; - let two: Vec = b"random".to_vec(); - assert_eq!(pair.prefix(), vec![one.as_slice(), two.as_slice()]); - } - - #[test] - fn naked_64bit_prefixes() { - let pair: (u64, &[u8]) = (12345, b"random"); - let one: Vec = vec![0, 0, 0, 0, 0, 0, 48, 57]; - let two: Vec = b"random".to_vec(); - assert_eq!(pair.prefix(), vec![one.as_slice(), two.as_slice()]); - - let pair: (i64, &[u8]) = (12345, b"random"); - // Signed int keys are "sign-flipped" - #[allow(clippy::identity_op)] - let one: Vec = vec![0 ^ 0x80, 0, 0, 0, 0, 0, 48, 57]; - let two: Vec = b"random".to_vec(); - assert_eq!(pair.prefix(), vec![one.as_slice(), two.as_slice()]); - } - - #[test] - fn naked_proper_prefixes() { - let pair: (u32, &[u8]) = (12345, b"random"); - let one: Vec = vec![0, 0, 48, 57]; - let two: Vec = b"random".to_vec(); - assert_eq!(pair.prefix(), vec![one.as_slice(), two.as_slice()]); - - let triple: (&str, u32, &[u8]) = ("begin", 12345, b"end"); - let one: Vec = b"begin".to_vec(); - let two: Vec = vec![0, 0, 48, 57]; - let three: Vec = b"end".to_vec(); - assert_eq!( - triple.prefix(), - vec![one.as_slice(), two.as_slice(), three.as_slice()] - ); - - // same works with owned variants (&str -> String, &[u8] -> Vec) - let owned_triple: (String, u32, Vec) = ("begin".to_string(), 12345, b"end".to_vec()); - assert_eq!( - owned_triple.prefix(), - vec![one.as_slice(), two.as_slice(), three.as_slice()] - ); - } -} diff --git a/packages/storage-plus/src/lib.rs b/packages/storage-plus/src/lib.rs deleted file mode 100644 index a91f096a4..000000000 --- a/packages/storage-plus/src/lib.rs +++ /dev/null @@ -1,87 +0,0 @@ -/*! -After building `cosmwasm-storage`, we realized many of the design decisions were -limiting us and producing a lot of needless boilerplate. The decision was made to leave -those APIs stable for anyone wanting a very basic abstraction on the KV-store and to -build a much more powerful and complex ORM layer that can provide powerful accessors -using complex key types, which are transparently turned into bytes. - -This led to a number of breaking API changes in this package of the course of several -releases as we updated this with lots of experience, user feedback, and deep dives to harness -the full power of generics. - -For more information on this package, please check out the -[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/storage-plus/README.md). -*/ - -mod bound; -mod de; -mod deque; -mod endian; -mod helpers; -mod indexed_map; -mod indexed_snapshot; -mod indexes; -mod int_key; -mod item; -mod iter_helpers; -mod keys; -mod map; -mod path; -mod prefix; -mod snapshot; - -#[cfg(feature = "iterator")] -pub use bound::{Bound, Bounder, PrefixBound, RawBound}; -pub use de::KeyDeserialize; -pub use deque::Deque; -pub use deque::DequeIter; -pub use endian::Endian; -#[cfg(feature = "iterator")] -pub use indexed_map::{IndexList, IndexedMap}; -#[cfg(feature = "iterator")] -pub use indexed_snapshot::IndexedSnapshotMap; -#[cfg(feature = "iterator")] -pub use indexes::Index; -#[cfg(feature = "iterator")] -pub use indexes::MultiIndex; -#[cfg(feature = "iterator")] -pub use indexes::UniqueIndex; -pub use int_key::IntKey; -pub use item::Item; -pub use keys::{Key, Prefixer, PrimaryKey}; -pub use map::Map; -pub use path::Path; -#[cfg(feature = "iterator")] -pub use prefix::{range_with_prefix, Prefix}; -#[cfg(feature = "iterator")] -pub use snapshot::{SnapshotItem, SnapshotMap, Strategy}; - -// cw_storage_macro reexports -#[cfg(all(feature = "iterator", feature = "macro"))] -#[macro_use] -extern crate cw_storage_macro; -#[cfg(all(feature = "iterator", feature = "macro"))] -/// Auto generate an `IndexList` impl for your indexes struct. -/// -/// # Example -/// -/// ```rust -/// use cosmwasm_std::Addr; -/// use cw_storage_plus::{MultiIndex, UniqueIndex, index_list}; -/// use serde::{Serialize, Deserialize}; -/// -/// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -/// struct TestStruct { -/// id: u64, -/// id2: u32, -/// addr: Addr, -/// } -/// -/// #[index_list(TestStruct)] // <- Add this line right here. -/// struct TestIndexes<'a> { -/// id: MultiIndex<'a, u32, TestStruct, u64>, -/// addr: UniqueIndex<'a, Addr, TestStruct>, -/// } -/// ``` -/// -pub use cw_storage_macro::index_list; diff --git a/packages/storage-plus/src/map.rs b/packages/storage-plus/src/map.rs deleted file mode 100644 index 322cdeec7..000000000 --- a/packages/storage-plus/src/map.rs +++ /dev/null @@ -1,1580 +0,0 @@ -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::marker::PhantomData; - -#[cfg(feature = "iterator")] -use crate::bound::{Bound, PrefixBound}; -#[cfg(feature = "iterator")] -use crate::de::KeyDeserialize; -use crate::helpers::query_raw; -#[cfg(feature = "iterator")] -use crate::iter_helpers::{deserialize_kv, deserialize_v}; -#[cfg(feature = "iterator")] -use crate::keys::Prefixer; -use crate::keys::{Key, PrimaryKey}; -use crate::path::Path; -#[cfg(feature = "iterator")] -use crate::prefix::{namespaced_prefix_range, Prefix}; -use cosmwasm_std::{from_slice, Addr, CustomQuery, QuerierWrapper, StdError, StdResult, Storage}; - -#[derive(Debug, Clone)] -pub struct Map<'a, K, T> { - namespace: &'a [u8], - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - key_type: PhantomData, - data_type: PhantomData, -} - -impl<'a, K, T> Map<'a, K, T> { - pub const fn new(namespace: &'a str) -> Self { - Map { - namespace: namespace.as_bytes(), - data_type: PhantomData, - key_type: PhantomData, - } - } - - pub fn namespace(&self) -> &'a [u8] { - self.namespace - } -} - -impl<'a, K, T> Map<'a, K, T> -where - T: Serialize + DeserializeOwned, - K: PrimaryKey<'a>, -{ - pub fn key(&self, k: K) -> Path { - Path::new( - self.namespace, - &k.key().iter().map(Key::as_ref).collect::>(), - ) - } - - #[cfg(feature = "iterator")] - pub(crate) fn no_prefix_raw(&self) -> Prefix, T, K> { - Prefix::new(self.namespace, &[]) - } - - pub fn save(&self, store: &mut dyn Storage, k: K, data: &T) -> StdResult<()> { - self.key(k).save(store, data) - } - - pub fn remove(&self, store: &mut dyn Storage, k: K) { - self.key(k).remove(store) - } - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, store: &dyn Storage, k: K) -> StdResult { - self.key(k).load(store) - } - - /// 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, store: &dyn Storage, k: K) -> StdResult> { - self.key(k).may_load(store) - } - - /// has returns true or false if any data is at this key, without parsing or interpreting the - /// contents. - pub fn has(&self, store: &dyn Storage, k: K) -> bool { - self.key(k).has(store) - } - - /// 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. - /// - /// If the data exists, `action(Some(value))` is called. Otherwise `action(None)` is called. - pub fn update(&self, store: &mut dyn Storage, k: K, action: A) -> Result - where - A: FnOnce(Option) -> Result, - E: From, - { - self.key(k).update(store, action) - } - - /// If you import the proper Map from the remote contract, this will let you read the data - /// from a remote contract in a type-safe way using WasmQuery::RawQuery - pub fn query( - &self, - querier: &QuerierWrapper, - remote_contract: Addr, - k: K, - ) -> StdResult> { - let key = self.key(k).storage_key.into(); - let result = query_raw(querier, remote_contract, key)?; - if result.is_empty() { - Ok(None) - } else { - from_slice(&result).map(Some) - } - } - - /// Clears the map, removing all elements. - #[cfg(feature = "iterator")] - pub fn clear(&self, store: &mut dyn Storage) { - const TAKE: usize = 10; - let mut cleared = false; - - while !cleared { - let paths = self - .no_prefix_raw() - .keys_raw(store, None, None, cosmwasm_std::Order::Ascending) - .map(|raw_key| Path::::new(self.namespace, &[raw_key.as_slice()])) - // Take just TAKE elements to prevent possible heap overflow if the Map is big. - .take(TAKE) - .collect::>(); - - paths.iter().for_each(|path| store.remove(path)); - - cleared = paths.len() < TAKE; - } - } - - /// Returns `true` if the map is empty. - #[cfg(feature = "iterator")] - pub fn is_empty(&self, store: &dyn Storage) -> bool { - self.no_prefix_raw() - .keys_raw(store, None, None, cosmwasm_std::Order::Ascending) - .next() - .is_none() - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T> Map<'a, K, T> -where - T: Serialize + DeserializeOwned, - K: PrimaryKey<'a>, -{ - pub fn sub_prefix(&self, p: K::SubPrefix) -> Prefix { - Prefix::new(self.namespace, &p.prefix()) - } - - pub fn prefix(&self, p: K::Prefix) -> Prefix { - Prefix::new(self.namespace, &p.prefix()) - } -} - -// short-cut for simple keys, rather than .prefix(()).range_raw(...) -#[cfg(feature = "iterator")] -impl<'a, K, T> Map<'a, K, T> -where - T: Serialize + DeserializeOwned, - // TODO: this should only be when K::Prefix == () - // Other cases need to call prefix() first - K: PrimaryKey<'a>, -{ - /// While `range_raw` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range_raw` accepts bounds for the lowest and highest elements of the `Prefix` - /// itself, and iterates over those (inclusively or exclusively, depending on `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box>> + 'c> - where - T: 'c, - 'a: 'c, - { - let mapped = - namespaced_prefix_range(store, self.namespace, min, max, order).map(deserialize_v); - Box::new(mapped) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T> Map<'a, K, T> -where - T: Serialize + DeserializeOwned, - K: PrimaryKey<'a> + KeyDeserialize, -{ - /// While `range` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range` accepts bounds for the lowest and highest elements of the - /// `Prefix` itself, and iterates over those (inclusively or exclusively, depending on - /// `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - 'a: 'c, - K: 'c, - K::Output: 'static, - { - let mapped = namespaced_prefix_range(store, self.namespace, min, max, order) - .map(deserialize_kv::); - Box::new(mapped) - } - - fn no_prefix(&self) -> Prefix { - Prefix::new(self.namespace, &[]) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T> Map<'a, K, T> -where - T: Serialize + DeserializeOwned, - K: PrimaryKey<'a>, -{ - pub fn range_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box>> + 'c> - where - T: 'c, - { - self.no_prefix_raw().range_raw(store, min, max, order) - } - - pub fn keys_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - { - self.no_prefix_raw().keys_raw(store, min, max, order) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T> Map<'a, K, T> -where - T: Serialize + DeserializeOwned, - K: PrimaryKey<'a> + KeyDeserialize, -{ - pub fn range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().range(store, min, max, order) - } - - pub fn keys<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().keys(store, min, max, order) - } -} - -#[cfg(test)] -mod test { - use super::*; - use serde::{Deserialize, Serialize}; - use std::ops::Deref; - - use cosmwasm_std::testing::MockStorage; - use cosmwasm_std::to_binary; - use cosmwasm_std::StdError::InvalidUtf8; - #[cfg(feature = "iterator")] - use cosmwasm_std::{Order, StdResult}; - - #[cfg(feature = "iterator")] - use crate::bound::Bounder; - - use crate::int_key::IntKey; - - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] - struct Data { - pub name: String, - pub age: i32, - } - - const PEOPLE: Map<&[u8], Data> = Map::new("people"); - #[cfg(feature = "iterator")] - const PEOPLE_STR_KEY: &str = "people2"; - #[cfg(feature = "iterator")] - const PEOPLE_STR: Map<&str, Data> = Map::new(PEOPLE_STR_KEY); - #[cfg(feature = "iterator")] - const PEOPLE_ID: Map = Map::new("people_id"); - #[cfg(feature = "iterator")] - const SIGNED_ID: Map = Map::new("signed_id"); - - const ALLOWANCE: Map<(&[u8], &[u8]), u64> = Map::new("allow"); - - const TRIPLE: Map<(&[u8], u8, &str), u64> = Map::new("triple"); - - #[test] - fn create_path() { - let path = PEOPLE.key(b"john"); - let key = path.deref(); - // this should be prefixed(people) || john - assert_eq!("people".len() + "john".len() + 2, key.len()); - assert_eq!(b"people".to_vec().as_slice(), &key[2..8]); - assert_eq!(b"john".to_vec().as_slice(), &key[8..]); - - let path = ALLOWANCE.key((b"john", b"maria")); - let key = path.deref(); - // this should be prefixed(allow) || prefixed(john) || maria - assert_eq!( - "allow".len() + "john".len() + "maria".len() + 2 * 2, - key.len() - ); - assert_eq!(b"allow".to_vec().as_slice(), &key[2..7]); - assert_eq!(b"john".to_vec().as_slice(), &key[9..13]); - assert_eq!(b"maria".to_vec().as_slice(), &key[13..]); - - let path = TRIPLE.key((b"john", 8u8, "pedro")); - let key = path.deref(); - // this should be prefixed(allow) || prefixed(john) || maria - assert_eq!( - "triple".len() + "john".len() + 1 + "pedro".len() + 2 * 3, - key.len() - ); - assert_eq!(b"triple".to_vec().as_slice(), &key[2..8]); - assert_eq!(b"john".to_vec().as_slice(), &key[10..14]); - assert_eq!(8u8.to_cw_bytes(), &key[16..17]); - assert_eq!(b"pedro".to_vec().as_slice(), &key[17..]); - } - - #[test] - fn save_and_load() { - let mut store = MockStorage::new(); - - // save and load on one key - let john = PEOPLE.key(b"john"); - let data = Data { - name: "John".to_string(), - age: 32, - }; - assert_eq!(None, john.may_load(&store).unwrap()); - john.save(&mut store, &data).unwrap(); - assert_eq!(data, john.load(&store).unwrap()); - - // nothing on another key - assert_eq!(None, PEOPLE.may_load(&store, b"jack").unwrap()); - - // same named path gets the data - assert_eq!(data, PEOPLE.load(&store, b"john").unwrap()); - - // removing leaves us empty - john.remove(&mut store); - assert_eq!(None, john.may_load(&store).unwrap()); - } - - #[test] - fn existence() { - let mut store = MockStorage::new(); - - // set data in proper format - let data = Data { - name: "John".to_string(), - age: 32, - }; - PEOPLE.save(&mut store, b"john", &data).unwrap(); - - // set and remove it - PEOPLE.save(&mut store, b"removed", &data).unwrap(); - PEOPLE.remove(&mut store, b"removed"); - - // invalid, but non-empty data - store.set(&PEOPLE.key(b"random"), b"random-data"); - - // any data, including invalid or empty is returned as "has" - assert!(PEOPLE.has(&store, b"john")); - assert!(PEOPLE.has(&store, b"random")); - - // if nothing was written, it is false - assert!(!PEOPLE.has(&store, b"never-writen")); - assert!(!PEOPLE.has(&store, b"removed")); - } - - #[test] - fn composite_keys() { - let mut store = MockStorage::new(); - - // save and load on a composite key - let allow = ALLOWANCE.key((b"owner", b"spender")); - assert_eq!(None, allow.may_load(&store).unwrap()); - allow.save(&mut store, &1234).unwrap(); - assert_eq!(1234, allow.load(&store).unwrap()); - - // not under other key - let different = ALLOWANCE.may_load(&store, (b"owners", b"pender")).unwrap(); - assert_eq!(None, different); - - // matches under a proper copy - let same = ALLOWANCE.load(&store, (b"owner", b"spender")).unwrap(); - assert_eq!(1234, same); - } - - #[test] - fn triple_keys() { - let mut store = MockStorage::new(); - - // save and load on a triple composite key - let triple = TRIPLE.key((b"owner", 10u8, "recipient")); - assert_eq!(None, triple.may_load(&store).unwrap()); - triple.save(&mut store, &1234).unwrap(); - assert_eq!(1234, triple.load(&store).unwrap()); - - // not under other key - let different = TRIPLE - .may_load(&store, (b"owners", 10u8, "ecipient")) - .unwrap(); - assert_eq!(None, different); - - // matches under a proper copy - let same = TRIPLE.load(&store, (b"owner", 10u8, "recipient")).unwrap(); - assert_eq!(1234, same); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_raw_simple_key() { - let mut store = MockStorage::new(); - - // save and load on two keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - PEOPLE.save(&mut store, b"john", &data).unwrap(); - - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - PEOPLE.save(&mut store, b"jim", &data2).unwrap(); - - // let's try to iterate! - let all: StdResult> = PEOPLE - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![ - (b"jim".to_vec(), data2.clone()), - (b"john".to_vec(), data.clone()) - ] - ); - - // let's try to iterate over a range - let all: StdResult> = PEOPLE - .range_raw( - &store, - Some(Bound::inclusive(b"j" as &[u8])), - None, - Order::Ascending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![(b"jim".to_vec(), data2), (b"john".to_vec(), data.clone())] - ); - - // let's try to iterate over a more restrictive range - let all: StdResult> = PEOPLE - .range_raw( - &store, - Some(Bound::inclusive(b"jo" as &[u8])), - None, - Order::Ascending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(b"john".to_vec(), data)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_string_key() { - let mut store = MockStorage::new(); - - // save and load on three keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - PEOPLE.save(&mut store, b"john", &data).unwrap(); - - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - PEOPLE.save(&mut store, b"jim", &data2).unwrap(); - - let data3 = Data { - name: "Ada".to_string(), - age: 23, - }; - PEOPLE.save(&mut store, b"ada", &data3).unwrap(); - - // let's try to iterate! - let all: StdResult> = PEOPLE.range(&store, None, None, Order::Ascending).collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - assert_eq!( - all, - vec![ - (b"ada".to_vec(), data3), - (b"jim".to_vec(), data2.clone()), - (b"john".to_vec(), data.clone()) - ] - ); - - // let's try to iterate over a range - let all: StdResult> = PEOPLE - .range(&store, b"j".inclusive_bound(), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![(b"jim".to_vec(), data2), (b"john".to_vec(), data.clone())] - ); - - // let's try to iterate over a more restrictive range - let all: StdResult> = PEOPLE - .range(&store, b"jo".inclusive_bound(), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(b"john".to_vec(), data)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_key_broken_deserialization_errors() { - let mut store = MockStorage::new(); - - // save and load on three keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - PEOPLE_STR.save(&mut store, "john", &data).unwrap(); - - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - PEOPLE_STR.save(&mut store, "jim", &data2).unwrap(); - - let data3 = Data { - name: "Ada".to_string(), - age: 23, - }; - PEOPLE_STR.save(&mut store, "ada", &data3).unwrap(); - - // let's iterate! - let all: StdResult> = PEOPLE_STR - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - assert_eq!( - all, - vec![ - ("ada".to_string(), data3.clone()), - ("jim".to_string(), data2.clone()), - ("john".to_string(), data.clone()) - ] - ); - - // Manually add a broken key (invalid utf-8) - store.set( - &[ - [0u8, PEOPLE_STR_KEY.len() as u8].as_slice(), - PEOPLE_STR_KEY.as_bytes(), - b"\xddim", - ] - .concat(), - &to_binary(&data2).unwrap(), - ); - - // Let's try to iterate again! - let all: StdResult> = PEOPLE_STR - .range(&store, None, None, Order::Ascending) - .collect(); - assert!(all.is_err()); - assert!(matches!(all.unwrap_err(), InvalidUtf8 { .. })); - - // And the same with keys() - let all: StdResult> = PEOPLE_STR - .keys(&store, None, None, Order::Ascending) - .collect(); - assert!(all.is_err()); - assert!(matches!(all.unwrap_err(), InvalidUtf8 { .. })); - - // But range_raw still works - let all: StdResult> = PEOPLE_STR - .range_raw(&store, None, None, Order::Ascending) - .collect(); - - let all = all.unwrap(); - assert_eq!(4, all.len()); - assert_eq!( - all, - vec![ - (b"ada".to_vec(), data3.clone()), - (b"jim".to_vec(), data2.clone()), - (b"john".to_vec(), data.clone()), - (b"\xddim".to_vec(), data2.clone()), - ] - ); - - // And the same with keys_raw - let all: Vec<_> = PEOPLE_STR - .keys_raw(&store, None, None, Order::Ascending) - .collect(); - - assert_eq!(4, all.len()); - assert_eq!( - all, - vec![ - b"ada".to_vec(), - b"jim".to_vec(), - b"john".to_vec(), - b"\xddim".to_vec(), - ] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_integer_key() { - let mut store = MockStorage::new(); - - // save and load on two keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - PEOPLE_ID.save(&mut store, 1234, &data).unwrap(); - - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - PEOPLE_ID.save(&mut store, 56, &data2).unwrap(); - - // let's try to iterate! - let all: StdResult> = PEOPLE_ID - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![(56, data2.clone()), (1234, data.clone())]); - - // let's try to iterate over a range - let all: StdResult> = PEOPLE_ID - .range( - &store, - Some(Bound::inclusive(56u32)), - None, - Order::Ascending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![(56, data2), (1234, data.clone())]); - - // let's try to iterate over a more restrictive range - let all: StdResult> = PEOPLE_ID - .range( - &store, - Some(Bound::inclusive(57u32)), - None, - Order::Ascending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(1234, data)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_integer_key_with_bounder_trait() { - let mut store = MockStorage::new(); - - // save and load on two keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - PEOPLE_ID.save(&mut store, 1234, &data).unwrap(); - - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - PEOPLE_ID.save(&mut store, 56, &data2).unwrap(); - - // let's try to iterate! - let all: StdResult> = PEOPLE_ID - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![(56, data2.clone()), (1234, data.clone())]); - - // let's try to iterate over a range - let all: StdResult> = PEOPLE_ID - .range(&store, 56u32.inclusive_bound(), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![(56, data2), (1234, data.clone())]); - - // let's try to iterate over a more restrictive range - let all: StdResult> = PEOPLE_ID - .range(&store, 57u32.inclusive_bound(), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(1234, data)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_signed_integer_key() { - let mut store = MockStorage::new(); - - // save and load on three keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - SIGNED_ID.save(&mut store, -1234, &data).unwrap(); - - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - SIGNED_ID.save(&mut store, -56, &data2).unwrap(); - - let data3 = Data { - name: "Jules".to_string(), - age: 55, - }; - SIGNED_ID.save(&mut store, 50, &data3).unwrap(); - - // let's try to iterate! - let all: StdResult> = SIGNED_ID - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - // order is correct - assert_eq!( - all, - vec![(-1234, data), (-56, data2.clone()), (50, data3.clone())] - ); - - // let's try to iterate over a range - let all: StdResult> = SIGNED_ID - .range( - &store, - Some(Bound::inclusive(-56i32)), - None, - Order::Ascending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![(-56, data2), (50, data3.clone())]); - - // let's try to iterate over a more restrictive range - let all: StdResult> = SIGNED_ID - .range( - &store, - Some(Bound::inclusive(-55i32)), - Some(Bound::inclusive(50i32)), - Order::Descending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(50, data3)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_signed_integer_key_with_bounder_trait() { - let mut store = MockStorage::new(); - - // save and load on three keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - SIGNED_ID.save(&mut store, -1234, &data).unwrap(); - - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - SIGNED_ID.save(&mut store, -56, &data2).unwrap(); - - let data3 = Data { - name: "Jules".to_string(), - age: 55, - }; - SIGNED_ID.save(&mut store, 50, &data3).unwrap(); - - // let's try to iterate! - let all: StdResult> = SIGNED_ID - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - // order is correct - assert_eq!( - all, - vec![(-1234, data), (-56, data2.clone()), (50, data3.clone())] - ); - - // let's try to iterate over a range - let all: StdResult> = SIGNED_ID - .range(&store, (-56i32).inclusive_bound(), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![(-56, data2), (50, data3.clone())]); - - // let's try to iterate over a more restrictive range - let all: StdResult> = SIGNED_ID - .range( - &store, - (-55i32).inclusive_bound(), - 50i32.inclusive_bound(), - Order::Descending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(50, data3)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_raw_composite_key() { - let mut store = MockStorage::new(); - - // save and load on three keys, one under different owner - ALLOWANCE - .save(&mut store, (b"owner", b"spender"), &1000) - .unwrap(); - ALLOWANCE - .save(&mut store, (b"owner", b"spender2"), &3000) - .unwrap(); - ALLOWANCE - .save(&mut store, (b"owner2", b"spender"), &5000) - .unwrap(); - - // let's try to iterate! - let all: StdResult> = ALLOWANCE - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - assert_eq!( - all, - vec![ - ((b"owner".to_vec(), b"spender".to_vec()).joined_key(), 1000), - ((b"owner".to_vec(), b"spender2".to_vec()).joined_key(), 3000), - ((b"owner2".to_vec(), b"spender".to_vec()).joined_key(), 5000), - ] - ); - - // let's try to iterate over a prefix - let all: StdResult> = ALLOWANCE - .prefix(b"owner") - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![(b"spender".to_vec(), 1000), (b"spender2".to_vec(), 3000)] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_composite_key() { - let mut store = MockStorage::new(); - - // save and load on three keys, one under different owner - ALLOWANCE - .save(&mut store, (b"owner", b"spender"), &1000) - .unwrap(); - ALLOWANCE - .save(&mut store, (b"owner", b"spender2"), &3000) - .unwrap(); - ALLOWANCE - .save(&mut store, (b"owner2", b"spender"), &5000) - .unwrap(); - - // let's try to iterate! - let all: StdResult> = ALLOWANCE - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - assert_eq!( - all, - vec![ - ((b"owner".to_vec(), b"spender".to_vec()), 1000), - ((b"owner".to_vec(), b"spender2".to_vec()), 3000), - ((b"owner2".to_vec(), b"spender".to_vec()), 5000) - ] - ); - - // let's try to iterate over a prefix - let all: StdResult> = ALLOWANCE - .prefix(b"owner") - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![(b"spender".to_vec(), 1000), (b"spender2".to_vec(), 3000),] - ); - - // let's try to iterate over a prefixed restricted inclusive range - let all: StdResult> = ALLOWANCE - .prefix(b"owner") - .range(&store, b"spender".inclusive_bound(), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![(b"spender".to_vec(), 1000), (b"spender2".to_vec(), 3000),] - ); - - // let's try to iterate over a prefixed restricted exclusive range - let all: StdResult> = ALLOWANCE - .prefix(b"owner") - .range(&store, b"spender".exclusive_bound(), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(b"spender2".to_vec(), 3000),]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_raw_triple_key() { - let mut store = MockStorage::new(); - - // save and load on three keys, one under different owner - TRIPLE - .save(&mut store, (b"owner", 9, "recipient"), &1000) - .unwrap(); - TRIPLE - .save(&mut store, (b"owner", 9, "recipient2"), &3000) - .unwrap(); - TRIPLE - .save(&mut store, (b"owner", 10, "recipient3"), &3000) - .unwrap(); - TRIPLE - .save(&mut store, (b"owner2", 9, "recipient"), &5000) - .unwrap(); - - // let's try to iterate! - let all: StdResult> = TRIPLE - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(4, all.len()); - assert_eq!( - all, - vec![ - ( - (b"owner".to_vec(), 9u8, b"recipient".to_vec()).joined_key(), - 1000 - ), - ( - (b"owner".to_vec(), 9u8, b"recipient2".to_vec()).joined_key(), - 3000 - ), - ( - (b"owner".to_vec(), 10u8, b"recipient3".to_vec()).joined_key(), - 3000 - ), - ( - (b"owner2".to_vec(), 9u8, b"recipient".to_vec()).joined_key(), - 5000 - ) - ] - ); - - // let's iterate over a prefix - let all: StdResult> = TRIPLE - .prefix((b"owner", 9)) - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![ - (b"recipient".to_vec(), 1000), - (b"recipient2".to_vec(), 3000) - ] - ); - - // let's iterate over a sub prefix - let all: StdResult> = TRIPLE - .sub_prefix(b"owner") - .range_raw(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - // Use range() if you want key deserialization - assert_eq!( - all, - vec![ - ((9u8, b"recipient".to_vec()).joined_key(), 1000), - ((9u8, b"recipient2".to_vec()).joined_key(), 3000), - ((10u8, b"recipient3".to_vec()).joined_key(), 3000) - ] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_triple_key() { - let mut store = MockStorage::new(); - - // save and load on three keys, one under different owner - TRIPLE - .save(&mut store, (b"owner", 9u8, "recipient"), &1000) - .unwrap(); - TRIPLE - .save(&mut store, (b"owner", 9u8, "recipient2"), &3000) - .unwrap(); - TRIPLE - .save(&mut store, (b"owner", 10u8, "recipient3"), &3000) - .unwrap(); - TRIPLE - .save(&mut store, (b"owner2", 9u8, "recipient"), &5000) - .unwrap(); - - // let's try to iterate! - let all: StdResult> = TRIPLE.range(&store, None, None, Order::Ascending).collect(); - let all = all.unwrap(); - assert_eq!(4, all.len()); - assert_eq!( - all, - vec![ - ((b"owner".to_vec(), 9, "recipient".to_string()), 1000), - ((b"owner".to_vec(), 9, "recipient2".to_string()), 3000), - ((b"owner".to_vec(), 10, "recipient3".to_string()), 3000), - ((b"owner2".to_vec(), 9, "recipient".to_string()), 5000) - ] - ); - - // let's iterate over a sub_prefix - let all: StdResult> = TRIPLE - .sub_prefix(b"owner") - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(3, all.len()); - assert_eq!( - all, - vec![ - ((9, "recipient".to_string()), 1000), - ((9, "recipient2".to_string()), 3000), - ((10, "recipient3".to_string()), 3000), - ] - ); - - // let's iterate over a prefix - let all: StdResult> = TRIPLE - .prefix((b"owner", 9)) - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![ - ("recipient".to_string(), 1000), - ("recipient2".to_string(), 3000), - ] - ); - - // let's try to iterate over a prefixed restricted inclusive range - let all: StdResult> = TRIPLE - .prefix((b"owner", 9)) - .range( - &store, - "recipient".inclusive_bound(), - None, - Order::Ascending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![ - ("recipient".to_string(), 1000), - ("recipient2".to_string(), 3000), - ] - ); - - // let's try to iterate over a prefixed restricted exclusive range - let all: StdResult> = TRIPLE - .prefix((b"owner", 9)) - .range( - &store, - "recipient".exclusive_bound(), - None, - Order::Ascending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![("recipient2".to_string(), 3000),]); - } - - #[test] - fn basic_update() { - let mut store = MockStorage::new(); - - let add_ten = |a: Option| -> StdResult<_> { Ok(a.unwrap_or_default() + 10) }; - - // save and load on three keys, one under different owner - let key: (&[u8], &[u8]) = (b"owner", b"spender"); - ALLOWANCE.update(&mut store, key, add_ten).unwrap(); - let twenty = ALLOWANCE.update(&mut store, key, add_ten).unwrap(); - assert_eq!(20, twenty); - let loaded = ALLOWANCE.load(&store, key).unwrap(); - assert_eq!(20, loaded); - } - - #[test] - fn readme_works() -> StdResult<()> { - let mut store = MockStorage::new(); - let data = Data { - name: "John".to_string(), - age: 32, - }; - - // load and save with extra key argument - let empty = PEOPLE.may_load(&store, b"john")?; - assert_eq!(None, empty); - PEOPLE.save(&mut store, b"john", &data)?; - let loaded = PEOPLE.load(&store, b"john")?; - assert_eq!(data, loaded); - - // nothing on another key - let missing = PEOPLE.may_load(&store, b"jack")?; - assert_eq!(None, missing); - - // update function for new or existing keys - let birthday = |d: Option| -> StdResult { - match d { - Some(one) => Ok(Data { - name: one.name, - age: one.age + 1, - }), - None => Ok(Data { - name: "Newborn".to_string(), - age: 0, - }), - } - }; - - let old_john = PEOPLE.update(&mut store, b"john", birthday)?; - assert_eq!(33, old_john.age); - assert_eq!("John", old_john.name.as_str()); - - let new_jack = PEOPLE.update(&mut store, b"jack", birthday)?; - assert_eq!(0, new_jack.age); - assert_eq!("Newborn", new_jack.name.as_str()); - - // update also changes the store - assert_eq!(old_john, PEOPLE.load(&store, b"john")?); - assert_eq!(new_jack, PEOPLE.load(&store, b"jack")?); - - // removing leaves us empty - PEOPLE.remove(&mut store, b"john"); - let empty = PEOPLE.may_load(&store, b"john")?; - assert_eq!(None, empty); - - Ok(()) - } - - #[test] - fn readme_works_composite_keys() -> StdResult<()> { - let mut store = MockStorage::new(); - - // save and load on a composite key - let empty = ALLOWANCE.may_load(&store, (b"owner", b"spender"))?; - assert_eq!(None, empty); - ALLOWANCE.save(&mut store, (b"owner", b"spender"), &777)?; - let loaded = ALLOWANCE.load(&store, (b"owner", b"spender"))?; - assert_eq!(777, loaded); - - // doesn't appear under other key (even if a concat would be the same) - let different = ALLOWANCE.may_load(&store, (b"owners", b"pender")).unwrap(); - assert_eq!(None, different); - - // simple update - ALLOWANCE.update(&mut store, (b"owner", b"spender"), |v| -> StdResult { - Ok(v.unwrap_or_default() + 222) - })?; - let loaded = ALLOWANCE.load(&store, (b"owner", b"spender"))?; - assert_eq!(999, loaded); - - Ok(()) - } - - #[test] - fn readme_works_with_path() -> StdResult<()> { - let mut store = MockStorage::new(); - let data = Data { - name: "John".to_string(), - age: 32, - }; - - // create a Path one time to use below - let john = PEOPLE.key(b"john"); - - // Use this just like an Item above - let empty = john.may_load(&store)?; - assert_eq!(None, empty); - john.save(&mut store, &data)?; - let loaded = john.load(&store)?; - assert_eq!(data, loaded); - john.remove(&mut store); - let empty = john.may_load(&store)?; - assert_eq!(None, empty); - - // same for composite keys, just use both parts in key() - let allow = ALLOWANCE.key((b"owner", b"spender")); - allow.save(&mut store, &1234)?; - let loaded = allow.load(&store)?; - assert_eq!(1234, loaded); - allow.update(&mut store, |x| -> StdResult { - Ok(x.unwrap_or_default() * 2) - })?; - let loaded = allow.load(&store)?; - assert_eq!(2468, loaded); - - Ok(()) - } - - #[test] - #[cfg(feature = "iterator")] - fn readme_with_range_raw() -> StdResult<()> { - let mut store = MockStorage::new(); - - // save and load on two keys - let data = Data { - name: "John".to_string(), - age: 32, - }; - PEOPLE.save(&mut store, b"john", &data)?; - let data2 = Data { - name: "Jim".to_string(), - age: 44, - }; - PEOPLE.save(&mut store, b"jim", &data2)?; - - // iterate over them all - let all: StdResult> = PEOPLE - .range_raw(&store, None, None, Order::Ascending) - .collect(); - assert_eq!( - all?, - vec![(b"jim".to_vec(), data2), (b"john".to_vec(), data.clone())] - ); - - // or just show what is after jim - let all: StdResult> = PEOPLE - .range_raw( - &store, - Some(Bound::exclusive(b"jim" as &[u8])), - None, - Order::Ascending, - ) - .collect(); - assert_eq!(all?, vec![(b"john".to_vec(), data)]); - - // save and load on three keys, one under different owner - ALLOWANCE.save(&mut store, (b"owner", b"spender"), &1000)?; - ALLOWANCE.save(&mut store, (b"owner", b"spender2"), &3000)?; - ALLOWANCE.save(&mut store, (b"owner2", b"spender"), &5000)?; - - // get all under one key - let all: StdResult> = ALLOWANCE - .prefix(b"owner") - .range_raw(&store, None, None, Order::Ascending) - .collect(); - assert_eq!( - all?, - vec![(b"spender".to_vec(), 1000), (b"spender2".to_vec(), 3000)] - ); - - // Or ranges between two items (even reverse) - let all: StdResult> = ALLOWANCE - .prefix(b"owner") - .range_raw( - &store, - Some(Bound::exclusive(b"spender1" as &[u8])), - Some(Bound::inclusive(b"spender2" as &[u8])), - Order::Descending, - ) - .collect(); - assert_eq!(all?, vec![(b"spender2".to_vec(), 3000)]); - - Ok(()) - } - - #[test] - #[cfg(feature = "iterator")] - fn prefixed_range_raw_works() { - // this is designed to look as much like a secondary index as possible - // we want to query over a range of u32 for the first key and all subkeys - const AGES: Map<(u32, Vec), u64> = Map::new("ages"); - - let mut store = MockStorage::new(); - AGES.save(&mut store, (2, vec![1, 2, 3]), &123).unwrap(); - AGES.save(&mut store, (3, vec![4, 5, 6]), &456).unwrap(); - AGES.save(&mut store, (5, vec![7, 8, 9]), &789).unwrap(); - AGES.save(&mut store, (5, vec![9, 8, 7]), &987).unwrap(); - AGES.save(&mut store, (7, vec![20, 21, 22]), &2002).unwrap(); - AGES.save(&mut store, (8, vec![23, 24, 25]), &2332).unwrap(); - - // typical range under one prefix as a control - let fives = AGES - .prefix(5) - .range_raw(&store, None, None, Order::Ascending) - .collect::>>() - .unwrap(); - assert_eq!(fives.len(), 2); - assert_eq!(fives, vec![(vec![7, 8, 9], 789), (vec![9, 8, 7], 987)]); - - let keys: Vec<_> = AGES - .keys_raw(&store, None, None, Order::Ascending) - .collect(); - println!("keys: {:?}", keys); - - // using inclusive bounds both sides - let include = AGES - .prefix_range_raw( - &store, - Some(PrefixBound::inclusive(3u32)), - Some(PrefixBound::inclusive(7u32)), - Order::Ascending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(include.len(), 4); - assert_eq!(include, vec![456, 789, 987, 2002]); - - // using exclusive bounds both sides - let exclude = AGES - .prefix_range_raw( - &store, - Some(PrefixBound::exclusive(3u32)), - Some(PrefixBound::exclusive(7u32)), - Order::Ascending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(exclude.len(), 2); - assert_eq!(exclude, vec![789, 987]); - - // using inclusive in descending - let include = AGES - .prefix_range_raw( - &store, - Some(PrefixBound::inclusive(3u32)), - Some(PrefixBound::inclusive(5u32)), - Order::Descending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(include.len(), 3); - assert_eq!(include, vec![987, 789, 456]); - - // using exclusive in descending - let include = AGES - .prefix_range_raw( - &store, - Some(PrefixBound::exclusive(2u32)), - Some(PrefixBound::exclusive(5u32)), - Order::Descending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(include.len(), 1); - assert_eq!(include, vec![456]); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefixed_range_works() { - // this is designed to look as much like a secondary index as possible - // we want to query over a range of u32 for the first key and all subkeys - const AGES: Map<(u32, &str), u64> = Map::new("ages"); - - let mut store = MockStorage::new(); - AGES.save(&mut store, (2, "123"), &123).unwrap(); - AGES.save(&mut store, (3, "456"), &456).unwrap(); - AGES.save(&mut store, (5, "789"), &789).unwrap(); - AGES.save(&mut store, (5, "987"), &987).unwrap(); - AGES.save(&mut store, (7, "202122"), &2002).unwrap(); - AGES.save(&mut store, (8, "232425"), &2332).unwrap(); - - // typical range under one prefix as a control - let fives = AGES - .prefix(5) - .range(&store, None, None, Order::Ascending) - .collect::>>() - .unwrap(); - assert_eq!(fives.len(), 2); - assert_eq!( - fives, - vec![("789".to_string(), 789), ("987".to_string(), 987)] - ); - - let keys: Vec<_> = AGES.keys(&store, None, None, Order::Ascending).collect(); - println!("keys: {:?}", keys); - - // using inclusive bounds both sides - let include = AGES - .prefix_range( - &store, - Some(PrefixBound::inclusive(3u32)), - Some(PrefixBound::inclusive(7u32)), - Order::Ascending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(include.len(), 4); - assert_eq!(include, vec![456, 789, 987, 2002]); - - // using exclusive bounds both sides - let exclude = AGES - .prefix_range( - &store, - Some(PrefixBound::exclusive(3u32)), - Some(PrefixBound::exclusive(7u32)), - Order::Ascending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(exclude.len(), 2); - assert_eq!(exclude, vec![789, 987]); - - // using inclusive in descending - let include = AGES - .prefix_range( - &store, - Some(PrefixBound::inclusive(3u32)), - Some(PrefixBound::inclusive(5u32)), - Order::Descending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(include.len(), 3); - assert_eq!(include, vec![987, 789, 456]); - - // using exclusive in descending - let include = AGES - .prefix_range( - &store, - Some(PrefixBound::exclusive(2u32)), - Some(PrefixBound::exclusive(5u32)), - Order::Descending, - ) - .map(|r| r.map(|(_, v)| v)) - .collect::>>() - .unwrap(); - assert_eq!(include.len(), 1); - assert_eq!(include, vec![456]); - } - - #[test] - #[cfg(feature = "iterator")] - fn clear_works() { - const TEST_MAP: Map<&str, u32> = Map::new("test_map"); - - let mut storage = MockStorage::new(); - TEST_MAP.save(&mut storage, "key0", &0u32).unwrap(); - TEST_MAP.save(&mut storage, "key1", &1u32).unwrap(); - TEST_MAP.save(&mut storage, "key2", &2u32).unwrap(); - TEST_MAP.save(&mut storage, "key3", &3u32).unwrap(); - TEST_MAP.save(&mut storage, "key4", &4u32).unwrap(); - - TEST_MAP.clear(&mut storage); - - assert!(!TEST_MAP.has(&storage, "key0")); - assert!(!TEST_MAP.has(&storage, "key1")); - assert!(!TEST_MAP.has(&storage, "key2")); - assert!(!TEST_MAP.has(&storage, "key3")); - assert!(!TEST_MAP.has(&storage, "key4")); - } - - #[test] - #[cfg(feature = "iterator")] - fn is_empty_works() { - const TEST_MAP: Map<&str, u32> = Map::new("test_map"); - - let mut storage = MockStorage::new(); - - assert!(TEST_MAP.is_empty(&storage)); - - TEST_MAP.save(&mut storage, "key1", &1u32).unwrap(); - TEST_MAP.save(&mut storage, "key2", &2u32).unwrap(); - - assert!(!TEST_MAP.is_empty(&storage)); - } -} diff --git a/packages/storage-plus/src/path.rs b/packages/storage-plus/src/path.rs deleted file mode 100644 index e6a3cb526..000000000 --- a/packages/storage-plus/src/path.rs +++ /dev/null @@ -1,96 +0,0 @@ -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::marker::PhantomData; - -use crate::helpers::{may_deserialize, must_deserialize, nested_namespaces_with_key}; -use crate::keys::Key; -use cosmwasm_std::{to_vec, StdError, StdResult, Storage}; -use std::ops::Deref; - -#[derive(Debug, Clone)] -pub struct Path -where - T: Serialize + DeserializeOwned, -{ - /// all namespaces prefixes and concatenated with the key - pub(crate) storage_key: Vec, - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData, -} - -impl Deref for Path -where - T: Serialize + DeserializeOwned, -{ - type Target = [u8]; - - fn deref(&self) -> &[u8] { - &self.storage_key - } -} - -impl Path -where - T: Serialize + DeserializeOwned, -{ - pub fn new(namespace: &[u8], keys: &[&[u8]]) -> Self { - let l = keys.len(); - // FIXME: make this more efficient - let storage_key = nested_namespaces_with_key( - &[namespace], - &keys[0..l - 1] - .iter() - .map(|k| Key::Ref(k)) - .collect::>(), - keys[l - 1], - ); - Path { - storage_key, - data: PhantomData, - } - } - - /// save will serialize the model and store, returns an error on serialization issues - pub fn save(&self, store: &mut dyn Storage, data: &T) -> StdResult<()> { - store.set(&self.storage_key, &to_vec(data)?); - Ok(()) - } - - pub fn remove(&self, store: &mut dyn Storage) { - store.remove(&self.storage_key); - } - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, store: &dyn Storage) -> StdResult { - let value = store.get(&self.storage_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, store: &dyn Storage) -> StdResult> { - let value = store.get(&self.storage_key); - may_deserialize(&value) - } - - /// has returns true or false if any data is at this key, without parsing or interpreting the - /// contents. It will returns true for an length-0 byte array (Some(b"")), if you somehow manage to set that. - pub fn has(&self, store: &dyn Storage) -> bool { - store.get(&self.storage_key).is_some() - } - - /// 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. - /// - /// If the data exists, `action(Some(value))` is called. Otherwise `action(None)` is called. - pub fn update(&self, store: &mut dyn Storage, action: A) -> Result - where - A: FnOnce(Option) -> Result, - E: From, - { - let input = self.may_load(store)?; - let output = action(input)?; - self.save(store, &output)?; - Ok(output) - } -} diff --git a/packages/storage-plus/src/prefix.rs b/packages/storage-plus/src/prefix.rs deleted file mode 100644 index 6922226e8..000000000 --- a/packages/storage-plus/src/prefix.rs +++ /dev/null @@ -1,441 +0,0 @@ -#![cfg(feature = "iterator")] -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::marker::PhantomData; - -use cosmwasm_std::{Order, Record, StdResult, Storage}; -use std::ops::Deref; - -use crate::bound::{PrefixBound, RawBound}; -use crate::de::KeyDeserialize; -use crate::helpers::{namespaces_with_key, nested_namespaces_with_key}; -use crate::iter_helpers::{concat, deserialize_kv, deserialize_v, trim}; -use crate::keys::Key; -use crate::{Bound, Prefixer, PrimaryKey}; - -type DeserializeVFn = fn(&dyn Storage, &[u8], Record) -> StdResult>; - -type DeserializeKvFn = - fn(&dyn Storage, &[u8], Record) -> StdResult<(::Output, T)>; - -pub fn default_deserializer_v( - _: &dyn Storage, - _: &[u8], - raw: Record, -) -> StdResult> { - deserialize_v(raw) -} - -pub fn default_deserializer_kv( - _: &dyn Storage, - _: &[u8], - raw: Record, -) -> StdResult<(K::Output, T)> { - deserialize_kv::(raw) -} - -#[derive(Clone)] -pub struct Prefix> -where - K: KeyDeserialize, - T: Serialize + DeserializeOwned, -{ - /// all namespaces prefixes and concatenated with the key - storage_prefix: Vec, - // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed - data: PhantomData<(T, B)>, - pk_name: Vec, - de_fn_kv: DeserializeKvFn, - de_fn_v: DeserializeVFn, -} - -impl Deref for Prefix -where - K: KeyDeserialize, - T: Serialize + DeserializeOwned, -{ - type Target = [u8]; - - fn deref(&self) -> &[u8] { - &self.storage_prefix - } -} - -impl Prefix -where - K: KeyDeserialize, - T: Serialize + DeserializeOwned, -{ - pub fn new(top_name: &[u8], sub_names: &[Key]) -> Self { - Prefix::with_deserialization_functions( - top_name, - sub_names, - &[], - default_deserializer_kv::, - default_deserializer_v, - ) - } - - pub fn with_deserialization_functions( - top_name: &[u8], - sub_names: &[Key], - pk_name: &[u8], - de_fn_kv: DeserializeKvFn, - de_fn_v: DeserializeVFn, - ) -> Self { - let storage_prefix = nested_namespaces_with_key(&[top_name], sub_names, b""); - Prefix { - storage_prefix, - data: PhantomData, - pk_name: pk_name.to_vec(), - de_fn_kv, - de_fn_v, - } - } -} - -impl<'b, K, T, B> Prefix -where - B: PrimaryKey<'b>, - K: KeyDeserialize, - T: Serialize + DeserializeOwned, -{ - pub fn range_raw<'a>( - &self, - store: &'a dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box>> + 'a> - where - T: 'a, - { - let de_fn = self.de_fn_v; - let pk_name = self.pk_name.clone(); - let mapped = range_with_prefix( - store, - &self.storage_prefix, - min.map(|b| b.to_raw_bound()), - max.map(|b| b.to_raw_bound()), - order, - ) - .map(move |kv| (de_fn)(store, &pk_name, kv)); - Box::new(mapped) - } - - pub fn keys_raw<'a>( - &self, - store: &'a dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box> + 'a> { - let mapped = range_with_prefix( - store, - &self.storage_prefix, - min.map(|b| b.to_raw_bound()), - max.map(|b| b.to_raw_bound()), - order, - ) - .map(|(k, _)| k); - Box::new(mapped) - } - - pub fn range<'a>( - &self, - store: &'a dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box> + 'a> - where - T: 'a, - K::Output: 'static, - { - let de_fn = self.de_fn_kv; - let pk_name = self.pk_name.clone(); - let mapped = range_with_prefix( - store, - &self.storage_prefix, - min.map(|b| b.to_raw_bound()), - max.map(|b| b.to_raw_bound()), - order, - ) - .map(move |kv| (de_fn)(store, &pk_name, kv)); - Box::new(mapped) - } - - pub fn keys<'a>( - &self, - store: &'a dyn Storage, - min: Option>, - max: Option>, - order: Order, - ) -> Box> + 'a> - where - T: 'a, - K::Output: 'static, - { - let de_fn = self.de_fn_kv; - let pk_name = self.pk_name.clone(); - let mapped = range_with_prefix( - store, - &self.storage_prefix, - min.map(|b| b.to_raw_bound()), - max.map(|b| b.to_raw_bound()), - order, - ) - .map(move |kv| (de_fn)(store, &pk_name, kv).map(|(k, _)| k)); - Box::new(mapped) - } -} - -pub fn range_with_prefix<'a>( - storage: &'a dyn Storage, - namespace: &[u8], - start: Option, - end: Option, - order: Order, -) -> Box + 'a> { - let start = calc_start_bound(namespace, start); - let end = calc_end_bound(namespace, end); - - // get iterator from storage - let base_iterator = storage.range(Some(&start), Some(&end), order); - - // make a copy for the closure to handle lifetimes safely - let prefix = namespace.to_vec(); - let mapped = base_iterator.map(move |(k, v)| (trim(&prefix, &k), v)); - Box::new(mapped) -} - -fn calc_start_bound(namespace: &[u8], bound: Option) -> Vec { - match bound { - None => namespace.to_vec(), - // this is the natural limits of the underlying Storage - Some(RawBound::Inclusive(limit)) => concat(namespace, &limit), - Some(RawBound::Exclusive(limit)) => concat(namespace, &extend_one_byte(&limit)), - } -} - -fn calc_end_bound(namespace: &[u8], bound: Option) -> Vec { - match bound { - None => increment_last_byte(namespace), - // this is the natural limits of the underlying Storage - Some(RawBound::Exclusive(limit)) => concat(namespace, &limit), - Some(RawBound::Inclusive(limit)) => concat(namespace, &extend_one_byte(&limit)), - } -} - -pub fn namespaced_prefix_range<'a, 'c, K: Prefixer<'a>>( - storage: &'c dyn Storage, - namespace: &[u8], - start: Option>, - end: Option>, - order: Order, -) -> Box + 'c> { - let prefix = namespaces_with_key(&[namespace], &[]); - let start = calc_prefix_start_bound(&prefix, start); - let end = calc_prefix_end_bound(&prefix, end); - - // get iterator from storage - let base_iterator = storage.range(Some(&start), Some(&end), order); - - // make a copy for the closure to handle lifetimes safely - let mapped = base_iterator.map(move |(k, v)| (trim(&prefix, &k), v)); - Box::new(mapped) -} - -fn calc_prefix_start_bound<'a, K: Prefixer<'a>>( - namespace: &[u8], - bound: Option>, -) -> Vec { - match bound.map(|b| b.to_raw_bound()) { - None => namespace.to_vec(), - // this is the natural limits of the underlying Storage - Some(RawBound::Inclusive(limit)) => concat(namespace, &limit), - Some(RawBound::Exclusive(limit)) => concat(namespace, &increment_last_byte(&limit)), - } -} - -fn calc_prefix_end_bound<'a, K: Prefixer<'a>>( - namespace: &[u8], - bound: Option>, -) -> Vec { - match bound.map(|b| b.to_raw_bound()) { - None => increment_last_byte(namespace), - // this is the natural limits of the underlying Storage - Some(RawBound::Exclusive(limit)) => concat(namespace, &limit), - Some(RawBound::Inclusive(limit)) => concat(namespace, &increment_last_byte(&limit)), - } -} - -fn extend_one_byte(limit: &[u8]) -> Vec { - let mut v = limit.to_vec(); - v.push(0); - v -} - -/// Returns a new vec of same length and last byte incremented by one -/// If last bytes are 255, we handle overflow up the chain. -/// If all bytes are 255, this returns wrong data - but that is never possible as a namespace -fn increment_last_byte(input: &[u8]) -> Vec { - let mut copy = input.to_vec(); - // zero out all trailing 255, increment first that is not such - for i in (0..input.len()).rev() { - if copy[i] == 255 { - copy[i] = 0; - } else { - copy[i] += 1; - break; - } - } - copy -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::testing::MockStorage; - - #[test] - fn ensure_proper_range_bounds() { - let mut store = MockStorage::new(); - // manually create this - not testing nested prefixes here - let prefix: Prefix, u64> = Prefix { - storage_prefix: b"foo".to_vec(), - data: PhantomData::<(u64, _)>, - pk_name: vec![], - de_fn_kv: |_, _, kv| deserialize_kv::, u64>(kv), - de_fn_v: |_, _, kv| deserialize_v(kv), - }; - - // set some data, we care about "foo" prefix - store.set(b"foobar", b"1"); - store.set(b"foora", b"2"); - store.set(b"foozi", b"3"); - // these shouldn't match - store.set(b"foply", b"100"); - store.set(b"font", b"200"); - - let expected = vec![ - (b"bar".to_vec(), 1u64), - (b"ra".to_vec(), 2u64), - (b"zi".to_vec(), 3u64), - ]; - let expected_reversed: Vec<(Vec, u64)> = expected.iter().rev().cloned().collect(); - - // let's do the basic sanity check - let res: StdResult> = prefix - .range_raw(&store, None, None, Order::Ascending) - .collect(); - assert_eq!(&expected, &res.unwrap()); - let res: StdResult> = prefix - .range_raw(&store, None, None, Order::Descending) - .collect(); - assert_eq!(&expected_reversed, &res.unwrap()); - - // now let's check some ascending ranges - let res: StdResult> = prefix - .range_raw( - &store, - Some(Bound::inclusive(b"ra".to_vec())), - None, - Order::Ascending, - ) - .collect(); - assert_eq!(&expected[1..], res.unwrap().as_slice()); - // skip excluded - let res: StdResult> = prefix - .range_raw( - &store, - Some(Bound::exclusive(b"ra".to_vec())), - None, - Order::Ascending, - ) - .collect(); - assert_eq!(&expected[2..], res.unwrap().as_slice()); - // if we exclude something a little lower, we get matched - let res: StdResult> = prefix - .range_raw( - &store, - Some(Bound::exclusive(b"r".to_vec())), - None, - Order::Ascending, - ) - .collect(); - assert_eq!(&expected[1..], res.unwrap().as_slice()); - - // now let's check some descending ranges - let res: StdResult> = prefix - .range_raw( - &store, - None, - Some(Bound::inclusive(b"ra".to_vec())), - Order::Descending, - ) - .collect(); - assert_eq!(&expected_reversed[1..], res.unwrap().as_slice()); - // skip excluded - let res: StdResult> = prefix - .range_raw( - &store, - None, - Some(Bound::exclusive(b"ra".to_vec())), - Order::Descending, - ) - .collect(); - assert_eq!(&expected_reversed[2..], res.unwrap().as_slice()); - // if we exclude something a little higher, we get matched - let res: StdResult> = prefix - .range_raw( - &store, - None, - Some(Bound::exclusive(b"rb".to_vec())), - Order::Descending, - ) - .collect(); - assert_eq!(&expected_reversed[1..], res.unwrap().as_slice()); - - // now test when both sides are set - let res: StdResult> = prefix - .range_raw( - &store, - Some(Bound::inclusive(b"ra".to_vec())), - Some(Bound::exclusive(b"zi".to_vec())), - Order::Ascending, - ) - .collect(); - assert_eq!(&expected[1..2], res.unwrap().as_slice()); - // and descending - let res: StdResult> = prefix - .range_raw( - &store, - Some(Bound::inclusive(b"ra".to_vec())), - Some(Bound::exclusive(b"zi".to_vec())), - Order::Descending, - ) - .collect(); - assert_eq!(&expected[1..2], res.unwrap().as_slice()); - // Include both sides - let res: StdResult> = prefix - .range_raw( - &store, - Some(Bound::inclusive(b"ra".to_vec())), - Some(Bound::inclusive(b"zi".to_vec())), - Order::Descending, - ) - .collect(); - assert_eq!(&expected_reversed[..2], res.unwrap().as_slice()); - // Exclude both sides - let res: StdResult> = prefix - .range_raw( - &store, - Some(Bound::exclusive(b"ra".to_vec())), - Some(Bound::exclusive(b"zi".to_vec())), - Order::Ascending, - ) - .collect(); - assert_eq!(res.unwrap().as_slice(), &[]); - } -} diff --git a/packages/storage-plus/src/snapshot/item.rs b/packages/storage-plus/src/snapshot/item.rs deleted file mode 100644 index 633f073ff..000000000 --- a/packages/storage-plus/src/snapshot/item.rs +++ /dev/null @@ -1,325 +0,0 @@ -use serde::de::DeserializeOwned; -use serde::Serialize; - -use cosmwasm_std::{StdError, StdResult, Storage}; - -use crate::snapshot::{ChangeSet, Snapshot}; -use crate::{Item, Map, Strategy}; - -/// Item that maintains a snapshot of one or more checkpoints. -/// We can query historical data as well as current state. -/// What data is snapshotted depends on the Strategy. -pub struct SnapshotItem<'a, T> { - primary: Item<'a, T>, - changelog_namespace: &'a str, - snapshots: Snapshot<'a, (), T>, -} - -impl<'a, T> SnapshotItem<'a, T> { - /// Example: - /// - /// ```rust - /// use cw_storage_plus::{SnapshotItem, Strategy}; - /// - /// SnapshotItem::<'static, u64>::new( - /// "every", - /// "every__check", - /// "every__change", - /// Strategy::EveryBlock); - /// ``` - pub const fn new( - storage_key: &'a str, - checkpoints: &'a str, - changelog: &'a str, - strategy: Strategy, - ) -> Self { - SnapshotItem { - primary: Item::new(storage_key), - changelog_namespace: changelog, - snapshots: Snapshot::new(checkpoints, changelog, strategy), - } - } - - pub fn add_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - self.snapshots.add_checkpoint(store, height) - } - - pub fn remove_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - self.snapshots.remove_checkpoint(store, height) - } - - pub fn changelog(&self) -> Map> { - // Build and return a compatible Map with the proper key type - Map::new(self.changelog_namespace) - } -} - -impl<'a, T> SnapshotItem<'a, T> -where - T: Serialize + DeserializeOwned + Clone, -{ - /// load old value and store changelog - fn write_change(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - // if there is already data in the changelog for this block, do not write more - if self.snapshots.has_changelog(store, (), height)? { - return Ok(()); - } - // otherwise, store the previous value - let old = self.primary.may_load(store)?; - self.snapshots.write_changelog(store, (), height, old) - } - - pub fn save(&self, store: &mut dyn Storage, data: &T, height: u64) -> StdResult<()> { - if self.snapshots.should_checkpoint(store, &())? { - self.write_change(store, height)?; - } - self.primary.save(store, data) - } - - pub fn remove(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - if self.snapshots.should_checkpoint(store, &())? { - self.write_change(store, height)?; - } - self.primary.remove(store); - Ok(()) - } - - /// load will return an error if no data is set, or on parse error - pub fn load(&self, store: &dyn Storage) -> StdResult { - self.primary.load(store) - } - - /// may_load will parse the data stored if present, returns Ok(None) if no data there. - /// returns an error on parsing issues - pub fn may_load(&self, store: &dyn Storage) -> StdResult> { - self.primary.may_load(store) - } - - pub fn may_load_at_height(&self, store: &dyn Storage, height: u64) -> StdResult> { - let snapshot = self.snapshots.may_load_at_height(store, (), height)?; - - if let Some(r) = snapshot { - Ok(r) - } else { - // otherwise, return current value - self.may_load(store) - } - } - - // If there is no checkpoint for that height, then we return StdError::NotFound - pub fn assert_checkpointed(&self, store: &dyn Storage, height: u64) -> StdResult<()> { - self.snapshots.assert_checkpointed(store, height) - } - - /// Loads the data, perform the specified action, and store the result in the database. - /// This is a shorthand for some common sequences, which may be useful. - /// - /// If the data exists, `action(Some(value))` is called. Otherwise `action(None)` is called. - /// - /// This is a bit more customized than needed to only read "old" value 1 time, not 2 per naive approach - pub fn update(&self, store: &mut dyn Storage, height: u64, action: A) -> Result - where - A: FnOnce(Option) -> Result, - E: From, - { - let input = self.may_load(store)?; - let output = action(input)?; - self.save(store, &output, height)?; - Ok(output) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::bound::Bound; - use cosmwasm_std::testing::MockStorage; - - type TestItem = SnapshotItem<'static, u64>; - - const NEVER: TestItem = - SnapshotItem::new("never", "never__check", "never__change", Strategy::Never); - const EVERY: TestItem = SnapshotItem::new( - "every", - "every__check", - "every__change", - Strategy::EveryBlock, - ); - const SELECT: TestItem = SnapshotItem::new( - "select", - "select__check", - "select__change", - Strategy::Selected, - ); - - // Fills an item (u64) with the following writes: - // 1: 5 - // 2: 7 - // 3: 8 - // 4: 1 - // 5: None - // 6: 13 - // 7: None - // 8: 22 - // Final value: 22 - // Value at beginning of 3 -> 7 - // Value at beginning of 5 -> 1 - fn init_data(item: &TestItem, storage: &mut dyn Storage) { - item.save(storage, &5, 1).unwrap(); - item.save(storage, &7, 2).unwrap(); - - // checkpoint 3 - item.add_checkpoint(storage, 3).unwrap(); - - // also use update to set - to ensure this works - item.save(storage, &1, 3).unwrap(); - item.update(storage, 3, |_| -> StdResult { Ok(8) }) - .unwrap(); - - item.remove(storage, 4).unwrap(); - item.save(storage, &13, 4).unwrap(); - - // checkpoint 5 - item.add_checkpoint(storage, 5).unwrap(); - item.remove(storage, 5).unwrap(); - item.update(storage, 5, |_| -> StdResult { Ok(22) }) - .unwrap(); - // and delete it later (unknown if all data present) - item.remove_checkpoint(storage, 5).unwrap(); - } - - const FINAL_VALUE: Option = Some(22); - - const VALUE_START_3: Option = Some(7); - - const VALUE_START_5: Option = Some(13); - - fn assert_final_value(item: &TestItem, storage: &dyn Storage) { - assert_eq!(FINAL_VALUE, item.may_load(storage).unwrap()); - } - - #[track_caller] - fn assert_value_at_height( - item: &TestItem, - storage: &dyn Storage, - height: u64, - value: Option, - ) { - assert_eq!(value, item.may_load_at_height(storage, height).unwrap()); - } - - fn assert_missing_checkpoint(item: &TestItem, storage: &dyn Storage, height: u64) { - assert!(item.may_load_at_height(storage, height).is_err()); - } - - #[test] - fn never_works_like_normal_item() { - let mut storage = MockStorage::new(); - init_data(&NEVER, &mut storage); - assert_final_value(&NEVER, &storage); - - // historical queries return error - assert_missing_checkpoint(&NEVER, &storage, 3); - assert_missing_checkpoint(&NEVER, &storage, 5); - } - - #[test] - fn every_blocks_stores_present_and_past() { - let mut storage = MockStorage::new(); - init_data(&EVERY, &mut storage); - assert_final_value(&EVERY, &storage); - - // historical queries return historical values - assert_value_at_height(&EVERY, &storage, 3, VALUE_START_3); - assert_value_at_height(&EVERY, &storage, 5, VALUE_START_5); - } - - #[test] - fn selected_shows_3_not_5() { - let mut storage = MockStorage::new(); - init_data(&SELECT, &mut storage); - assert_final_value(&SELECT, &storage); - - // historical queries return historical values - assert_value_at_height(&SELECT, &storage, 3, VALUE_START_3); - // never checkpointed - assert_missing_checkpoint(&NEVER, &storage, 1); - // deleted checkpoint - assert_missing_checkpoint(&NEVER, &storage, 5); - } - - #[test] - fn handle_multiple_writes_in_one_block() { - let mut storage = MockStorage::new(); - - println!("SETUP"); - EVERY.save(&mut storage, &5, 1).unwrap(); - EVERY.save(&mut storage, &7, 2).unwrap(); - EVERY.save(&mut storage, &2, 2).unwrap(); - - // update and save - query at 3 => 2, at 4 => 12 - EVERY - .update(&mut storage, 3, |_| -> StdResult { Ok(9) }) - .unwrap(); - EVERY.save(&mut storage, &12, 3).unwrap(); - assert_eq!(Some(5), EVERY.may_load_at_height(&storage, 2).unwrap()); - assert_eq!(Some(2), EVERY.may_load_at_height(&storage, 3).unwrap()); - assert_eq!(Some(12), EVERY.may_load_at_height(&storage, 4).unwrap()); - - // save and remove - query at 4 => 1, at 5 => None - EVERY.save(&mut storage, &17, 4).unwrap(); - EVERY.remove(&mut storage, 4).unwrap(); - assert_eq!(Some(12), EVERY.may_load_at_height(&storage, 4).unwrap()); - assert_eq!(None, EVERY.may_load_at_height(&storage, 5).unwrap()); - - // remove and update - query at 5 => 2, at 6 => 13 - EVERY.remove(&mut storage, 5).unwrap(); - EVERY - .update(&mut storage, 5, |_| -> StdResult { Ok(2) }) - .unwrap(); - assert_eq!(None, EVERY.may_load_at_height(&storage, 5).unwrap()); - assert_eq!(Some(2), EVERY.may_load_at_height(&storage, 6).unwrap()); - } - - #[test] - #[cfg(feature = "iterator")] - fn changelog_range_works() { - use cosmwasm_std::Order; - - let mut store = MockStorage::new(); - - // simple data for testing - EVERY.save(&mut store, &5, 1u64).unwrap(); - EVERY.save(&mut store, &7, 2u64).unwrap(); - EVERY - .update(&mut store, 3u64, |_| -> StdResult { Ok(8) }) - .unwrap(); - EVERY.remove(&mut store, 4u64).unwrap(); - - // let's try to iterate over the changelog - let all: StdResult> = EVERY - .changelog() - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(4, all.len()); - assert_eq!( - all, - vec![ - (1, ChangeSet { old: None }), - (2, ChangeSet { old: Some(5) }), - (3, ChangeSet { old: Some(7) }), - (4, ChangeSet { old: Some(8) }) - ] - ); - - // let's try to iterate over a changelog range - let all: StdResult> = EVERY - .changelog() - .range(&store, Some(Bound::exclusive(3u64)), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(4, ChangeSet { old: Some(8) }),]); - } -} diff --git a/packages/storage-plus/src/snapshot/map.rs b/packages/storage-plus/src/snapshot/map.rs deleted file mode 100644 index 3d7e6b0e4..000000000 --- a/packages/storage-plus/src/snapshot/map.rs +++ /dev/null @@ -1,644 +0,0 @@ -use serde::de::DeserializeOwned; -use serde::Serialize; - -use cosmwasm_std::{StdError, StdResult, Storage}; - -use crate::bound::PrefixBound; -use crate::de::KeyDeserialize; -use crate::iter_helpers::deserialize_kv; -use crate::keys::PrimaryKey; -use crate::map::Map; -use crate::path::Path; -use crate::prefix::{namespaced_prefix_range, Prefix}; -use crate::snapshot::{ChangeSet, Snapshot}; -use crate::{Bound, Prefixer, Strategy}; - -/// Map that maintains a snapshots of one or more checkpoints. -/// We can query historical data as well as current state. -/// What data is snapshotted depends on the Strategy. -pub struct SnapshotMap<'a, K, T> { - primary: Map<'a, K, T>, - snapshots: Snapshot<'a, K, T>, -} - -impl<'a, K, T> SnapshotMap<'a, K, T> { - /// Example: - /// - /// ```rust - /// use cw_storage_plus::{SnapshotMap, Strategy}; - /// - /// SnapshotMap::<&[u8], &str>::new( - /// "never", - /// "never__check", - /// "never__change", - /// Strategy::EveryBlock - /// ); - /// ``` - pub const fn new( - pk: &'a str, - checkpoints: &'a str, - changelog: &'a str, - strategy: Strategy, - ) -> Self { - SnapshotMap { - primary: Map::new(pk), - snapshots: Snapshot::new(checkpoints, changelog, strategy), - } - } - - pub fn changelog(&self) -> &Map<'a, (K, u64), ChangeSet> { - &self.snapshots.changelog - } -} - -impl<'a, K, T> SnapshotMap<'a, K, T> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a> + Prefixer<'a>, -{ - pub fn add_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - self.snapshots.add_checkpoint(store, height) - } - - pub fn remove_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - self.snapshots.remove_checkpoint(store, height) - } -} - -impl<'a, K, T> SnapshotMap<'a, K, T> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, -{ - pub fn key(&self, k: K) -> Path { - self.primary.key(k) - } - - fn no_prefix_raw(&self) -> Prefix, T, K> { - self.primary.no_prefix_raw() - } - - /// load old value and store changelog - fn write_change(&self, store: &mut dyn Storage, k: K, height: u64) -> StdResult<()> { - // if there is already data in the changelog for this key and block, do not write more - if self.snapshots.has_changelog(store, k.clone(), height)? { - return Ok(()); - } - // otherwise, store the previous value - let old = self.primary.may_load(store, k.clone())?; - self.snapshots.write_changelog(store, k, height, old) - } - - pub fn save(&self, store: &mut dyn Storage, k: K, data: &T, height: u64) -> StdResult<()> { - if self.snapshots.should_checkpoint(store, &k)? { - self.write_change(store, k.clone(), height)?; - } - self.primary.save(store, k, data) - } - - pub fn remove(&self, store: &mut dyn Storage, k: K, height: u64) -> StdResult<()> { - if self.snapshots.should_checkpoint(store, &k)? { - self.write_change(store, k.clone(), height)?; - } - self.primary.remove(store, k); - Ok(()) - } - - /// load will return an error if no data is set at the given key, or on parse error - pub fn load(&self, store: &dyn Storage, k: K) -> StdResult { - self.primary.load(store, k) - } - - /// 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, store: &dyn Storage, k: K) -> StdResult> { - self.primary.may_load(store, k) - } - - pub fn may_load_at_height( - &self, - store: &dyn Storage, - k: K, - height: u64, - ) -> StdResult> { - let snapshot = self - .snapshots - .may_load_at_height(store, k.clone(), height)?; - - if let Some(r) = snapshot { - Ok(r) - } else { - // otherwise, return current value - self.may_load(store, k) - } - } - - pub fn assert_checkpointed(&self, store: &dyn Storage, height: u64) -> StdResult<()> { - self.snapshots.assert_checkpointed(store, height) - } - - /// 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. - /// - /// If the data exists, `action(Some(value))` is called. Otherwise `action(None)` is called. - /// - /// This is a bit more customized than needed to only read "old" value 1 time, not 2 per naive approach - pub fn update( - &self, - store: &mut dyn Storage, - k: K, - height: u64, - action: A, - ) -> Result - where - A: FnOnce(Option) -> Result, - E: From, - { - let input = self.may_load(store, k.clone())?; - let output = action(input)?; - self.save(store, k, &output, height)?; - Ok(output) - } -} - -// short-cut for simple keys, rather than .prefix(()).range_raw(...) -impl<'a, K, T> SnapshotMap<'a, K, T> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, -{ - // I would prefer not to copy code from Prefix, but no other way - // with lifetimes (create Prefix inside function and return ref = no no) - pub fn range_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box>> + 'c> - where - T: 'c, - { - self.no_prefix_raw().range_raw(store, min, max, order) - } - - pub fn keys_raw<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - { - self.no_prefix_raw().keys_raw(store, min, max, order) - } -} - -#[cfg(feature = "iterator")] -impl<'a, K, T> SnapshotMap<'a, K, T> -where - T: Serialize + DeserializeOwned, - K: PrimaryKey<'a> + KeyDeserialize, -{ - /// While `range` over a `prefix` fixes the prefix to one element and iterates over the - /// remaining, `prefix_range` accepts bounds for the lowest and highest elements of the - /// `Prefix` itself, and iterates over those (inclusively or exclusively, depending on - /// `PrefixBound`). - /// There are some issues that distinguish these two, and blindly casting to `Vec` doesn't - /// solve them. - pub fn prefix_range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - 'a: 'c, - K: 'c, - K::Output: 'static, - { - let mapped = namespaced_prefix_range(store, self.primary.namespace(), min, max, order) - .map(deserialize_kv::); - Box::new(mapped) - } - - pub fn range<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().range(store, min, max, order) - } - - pub fn keys<'c>( - &self, - store: &'c dyn Storage, - min: Option>, - max: Option>, - order: cosmwasm_std::Order, - ) -> Box> + 'c> - where - T: 'c, - K::Output: 'static, - { - self.no_prefix().keys(store, min, max, order) - } - - pub fn prefix(&self, p: K::Prefix) -> Prefix { - Prefix::new(self.primary.namespace(), &p.prefix()) - } - - pub fn sub_prefix(&self, p: K::SubPrefix) -> Prefix { - Prefix::new(self.primary.namespace(), &p.prefix()) - } - - fn no_prefix(&self) -> Prefix { - Prefix::new(self.primary.namespace(), &[]) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::MockStorage; - - type TestMap = SnapshotMap<'static, &'static str, u64>; - type TestMapCompositeKey = SnapshotMap<'static, (&'static str, &'static str), u64>; - - const NEVER: TestMap = - SnapshotMap::new("never", "never__check", "never__change", Strategy::Never); - const EVERY: TestMap = SnapshotMap::new( - "every", - "every__check", - "every__change", - Strategy::EveryBlock, - ); - const EVERY_COMPOSITE_KEY: TestMapCompositeKey = SnapshotMap::new( - "every", - "every__check", - "every__change", - Strategy::EveryBlock, - ); - const SELECT: TestMap = SnapshotMap::new( - "select", - "select__check", - "select__change", - Strategy::Selected, - ); - - // Fills a map &[u8] -> u64 with the following writes: - // 1: A = 5 - // 2: B = 7 - // 3: C = 1, A = 8 - // 4: B = None, C = 13 - // 5: A = None, D = 22 - // Final values -> C = 13, D = 22 - // Values at beginning of 3 -> A = 5, B = 7 - // Values at beginning of 5 -> A = 8, C = 13 - fn init_data(map: &TestMap, storage: &mut dyn Storage) { - map.save(storage, "A", &5, 1).unwrap(); - map.save(storage, "B", &7, 2).unwrap(); - - // checkpoint 3 - map.add_checkpoint(storage, 3).unwrap(); - - // also use update to set - to ensure this works - map.save(storage, "C", &1, 3).unwrap(); - map.update(storage, "A", 3, |_| -> StdResult { Ok(8) }) - .unwrap(); - - map.remove(storage, "B", 4).unwrap(); - map.save(storage, "C", &13, 4).unwrap(); - - // checkpoint 5 - map.add_checkpoint(storage, 5).unwrap(); - map.remove(storage, "A", 5).unwrap(); - map.update(storage, "D", 5, |_| -> StdResult { Ok(22) }) - .unwrap(); - // and delete it later (unknown if all data present) - map.remove_checkpoint(storage, 5).unwrap(); - } - - const FINAL_VALUES: &[(&str, Option)] = - &[("A", None), ("B", None), ("C", Some(13)), ("D", Some(22))]; - - const VALUES_START_3: &[(&str, Option)] = - &[("A", Some(5)), ("B", Some(7)), ("C", None), ("D", None)]; - - const VALUES_START_5: &[(&str, Option)] = - &[("A", Some(8)), ("B", None), ("C", Some(13)), ("D", None)]; - - // Same as `init_data`, but we have a composite key for testing range. - fn init_data_composite_key(map: &TestMapCompositeKey, storage: &mut dyn Storage) { - map.save(storage, ("A", "B"), &5, 1).unwrap(); - map.save(storage, ("B", "A"), &7, 2).unwrap(); - - // checkpoint 3 - map.add_checkpoint(storage, 3).unwrap(); - - // also use update to set - to ensure this works - map.save(storage, ("B", "B"), &1, 3).unwrap(); - map.update(storage, ("A", "B"), 3, |_| -> StdResult { Ok(8) }) - .unwrap(); - - map.remove(storage, ("B", "A"), 4).unwrap(); - map.save(storage, ("B", "B"), &13, 4).unwrap(); - - // checkpoint 5 - map.add_checkpoint(storage, 5).unwrap(); - map.remove(storage, ("A", "B"), 5).unwrap(); - map.update(storage, ("C", "A"), 5, |_| -> StdResult { Ok(22) }) - .unwrap(); - // and delete it later (unknown if all data present) - map.remove_checkpoint(storage, 5).unwrap(); - } - - fn assert_final_values(map: &TestMap, storage: &dyn Storage) { - for (k, v) in FINAL_VALUES.iter().cloned() { - assert_eq!(v, map.may_load(storage, k).unwrap()); - } - } - - fn assert_values_at_height( - map: &TestMap, - storage: &dyn Storage, - height: u64, - values: &[(&str, Option)], - ) { - for (k, v) in values.iter().cloned() { - assert_eq!(v, map.may_load_at_height(storage, k, height).unwrap()); - } - } - - fn assert_missing_checkpoint(map: &TestMap, storage: &dyn Storage, height: u64) { - for k in &["A", "B", "C", "D"] { - assert!(map.may_load_at_height(storage, *k, height).is_err()); - } - } - - #[test] - fn never_works_like_normal_map() { - let mut storage = MockStorage::new(); - init_data(&NEVER, &mut storage); - assert_final_values(&NEVER, &storage); - - // historical queries return error - assert_missing_checkpoint(&NEVER, &storage, 3); - assert_missing_checkpoint(&NEVER, &storage, 5); - } - - #[test] - fn every_blocks_stores_present_and_past() { - let mut storage = MockStorage::new(); - init_data(&EVERY, &mut storage); - assert_final_values(&EVERY, &storage); - - // historical queries return historical values - assert_values_at_height(&EVERY, &storage, 3, VALUES_START_3); - assert_values_at_height(&EVERY, &storage, 5, VALUES_START_5); - } - - #[test] - fn selected_shows_3_not_5() { - let mut storage = MockStorage::new(); - init_data(&SELECT, &mut storage); - assert_final_values(&SELECT, &storage); - - // historical queries return historical values - assert_values_at_height(&SELECT, &storage, 3, VALUES_START_3); - // never checkpointed - assert_missing_checkpoint(&NEVER, &storage, 1); - // deleted checkpoint - assert_missing_checkpoint(&NEVER, &storage, 5); - } - - #[test] - fn handle_multiple_writes_in_one_block() { - let mut storage = MockStorage::new(); - - println!("SETUP"); - EVERY.save(&mut storage, "A", &5, 1).unwrap(); - EVERY.save(&mut storage, "B", &7, 2).unwrap(); - EVERY.save(&mut storage, "C", &2, 2).unwrap(); - - // update and save - A query at 3 => 5, at 4 => 12 - EVERY - .update(&mut storage, "A", 3, |_| -> StdResult { Ok(9) }) - .unwrap(); - EVERY.save(&mut storage, "A", &12, 3).unwrap(); - assert_eq!(Some(5), EVERY.may_load_at_height(&storage, "A", 2).unwrap()); - assert_eq!(Some(5), EVERY.may_load_at_height(&storage, "A", 3).unwrap()); - assert_eq!( - Some(12), - EVERY.may_load_at_height(&storage, "A", 4).unwrap() - ); - - // save and remove - B query at 4 => 7, at 5 => None - EVERY.save(&mut storage, "B", &17, 4).unwrap(); - EVERY.remove(&mut storage, "B", 4).unwrap(); - assert_eq!(Some(7), EVERY.may_load_at_height(&storage, "B", 3).unwrap()); - assert_eq!(Some(7), EVERY.may_load_at_height(&storage, "B", 4).unwrap()); - assert_eq!(None, EVERY.may_load_at_height(&storage, "B", 5).unwrap()); - - // remove and update - C query at 5 => 2, at 6 => 16 - EVERY.remove(&mut storage, "C", 5).unwrap(); - EVERY - .update(&mut storage, "C", 5, |_| -> StdResult { Ok(16) }) - .unwrap(); - assert_eq!(Some(2), EVERY.may_load_at_height(&storage, "C", 4).unwrap()); - assert_eq!(Some(2), EVERY.may_load_at_height(&storage, "C", 5).unwrap()); - assert_eq!( - Some(16), - EVERY.may_load_at_height(&storage, "C", 6).unwrap() - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn changelog_range_works() { - use cosmwasm_std::Order; - - let mut store = MockStorage::new(); - - // simple data for testing - EVERY.save(&mut store, "A", &5, 1).unwrap(); - EVERY.save(&mut store, "B", &7, 2).unwrap(); - EVERY - .update(&mut store, "A", 3, |_| -> StdResult { Ok(8) }) - .unwrap(); - EVERY.remove(&mut store, "B", 4).unwrap(); - - // let's try to iterate over the changelog - let all: StdResult> = EVERY - .changelog() - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(4, all.len()); - assert_eq!( - all, - vec![ - (("A".into(), 1), ChangeSet { old: None }), - (("A".into(), 3), ChangeSet { old: Some(5) }), - (("B".into(), 2), ChangeSet { old: None }), - (("B".into(), 4), ChangeSet { old: Some(7) }) - ] - ); - - // let's try to iterate over a changelog key/prefix - let all: StdResult> = EVERY - .changelog() - .prefix("B") - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![ - (2, ChangeSet { old: None }), - (4, ChangeSet { old: Some(7) }) - ] - ); - - // let's try to iterate over a changelog prefixed range - let all: StdResult> = EVERY - .changelog() - .prefix("A") - .range(&store, Some(Bound::inclusive(3u64)), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(3, ChangeSet { old: Some(5) }),]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_simple_string_key() { - use cosmwasm_std::Order; - - let mut store = MockStorage::new(); - init_data(&EVERY, &mut store); - - // let's try to iterate! - let all: StdResult> = EVERY.range(&store, None, None, Order::Ascending).collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![("C".into(), 13), ("D".into(), 22)]); - - // let's try to iterate over a range - let all: StdResult> = EVERY - .range(&store, Some(Bound::inclusive("C")), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!(all, vec![("C".into(), 13), ("D".into(), 22)]); - - // let's try to iterate over a more restrictive range - let all: StdResult> = EVERY - .range(&store, Some(Bound::inclusive("D")), None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![("D".into(), 22)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn range_composite_key() { - use cosmwasm_std::Order; - - let mut store = MockStorage::new(); - init_data_composite_key(&EVERY_COMPOSITE_KEY, &mut store); - - // let's try to iterate! - let all: StdResult> = EVERY_COMPOSITE_KEY - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![ - (("B".into(), "B".into()), 13), - (("C".into(), "A".into()), 22) - ] - ); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_range_composite_key() { - use cosmwasm_std::Order; - - let mut store = MockStorage::new(); - init_data_composite_key(&EVERY_COMPOSITE_KEY, &mut store); - - // let's prefix-range and iterate - let all: StdResult> = EVERY_COMPOSITE_KEY - .prefix_range( - &store, - None, - Some(PrefixBound::exclusive("C")), - Order::Descending, - ) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![(("B".into(), "B".into()), 13)]); - } - - #[test] - #[cfg(feature = "iterator")] - fn prefix_composite_key() { - use cosmwasm_std::Order; - - let mut store = MockStorage::new(); - init_data_composite_key(&EVERY_COMPOSITE_KEY, &mut store); - - // let's prefix and iterate - let all: StdResult> = EVERY_COMPOSITE_KEY - .prefix("C") - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(1, all.len()); - assert_eq!(all, vec![("A".into(), 22),]); - } - - #[test] - #[cfg(feature = "iterator")] - fn sub_prefix_composite_key() { - use cosmwasm_std::Order; - - let mut store = MockStorage::new(); - init_data_composite_key(&EVERY_COMPOSITE_KEY, &mut store); - - // Let's sub-prefix and iterate. - // This is similar to calling range() directly, but added here for completeness / - // sub_prefix type checks - let all: StdResult> = EVERY_COMPOSITE_KEY - .sub_prefix(()) - .range(&store, None, None, Order::Ascending) - .collect(); - let all = all.unwrap(); - assert_eq!(2, all.len()); - assert_eq!( - all, - vec![ - (("B".into(), "B".into()), 13), - (("C".into(), "A".into()), 22) - ] - ); - } -} diff --git a/packages/storage-plus/src/snapshot/mod.rs b/packages/storage-plus/src/snapshot/mod.rs deleted file mode 100644 index 7fccfea7d..000000000 --- a/packages/storage-plus/src/snapshot/mod.rs +++ /dev/null @@ -1,392 +0,0 @@ -#![cfg(feature = "iterator")] -mod item; -mod map; - -pub use item::SnapshotItem; -pub use map::SnapshotMap; - -use crate::bound::Bound; -use crate::de::KeyDeserialize; -use crate::{Map, Prefixer, PrimaryKey}; -use cosmwasm_std::{Order, StdError, StdResult, Storage}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; - -/// Structure holding a map of checkpoints composited from -/// height (as u64) and counter of how many times it has -/// been checkpointed (as u32). -/// Stores all changes in changelog. -#[derive(Debug, Clone)] -pub(crate) struct Snapshot<'a, K, T> { - checkpoints: Map<'a, u64, u32>, - - // this stores all changes (key, height). Must differentiate between no data written, - // and explicit None (just inserted) - pub changelog: Map<'a, (K, u64), ChangeSet>, - - // How aggressive we are about checkpointing all data - strategy: Strategy, -} - -impl<'a, K, T> Snapshot<'a, K, T> { - pub const fn new( - checkpoints: &'a str, - changelog: &'a str, - strategy: Strategy, - ) -> Snapshot<'a, K, T> { - Snapshot { - checkpoints: Map::new(checkpoints), - changelog: Map::new(changelog), - strategy, - } - } - - pub fn add_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - self.checkpoints - .update::<_, StdError>(store, height, |count| Ok(count.unwrap_or_default() + 1))?; - Ok(()) - } - - pub fn remove_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> { - let count = self - .checkpoints - .may_load(store, height)? - .unwrap_or_default(); - if count <= 1 { - self.checkpoints.remove(store, height); - Ok(()) - } else { - self.checkpoints.save(store, height, &(count - 1)) - } - } -} - -impl<'a, K, T> Snapshot<'a, K, T> -where - T: Serialize + DeserializeOwned + Clone, - K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, -{ - /// should_checkpoint looks at the strategy and determines if we want to checkpoint - pub fn should_checkpoint(&self, store: &dyn Storage, k: &K) -> StdResult { - match self.strategy { - Strategy::EveryBlock => Ok(true), - Strategy::Never => Ok(false), - Strategy::Selected => self.should_checkpoint_selected(store, k), - } - } - - /// this is just pulled out from above for the selected block - fn should_checkpoint_selected(&self, store: &dyn Storage, k: &K) -> StdResult { - // most recent checkpoint - let checkpoint = self - .checkpoints - .range(store, None, None, Order::Descending) - .next() - .transpose()?; - if let Some((height, _)) = checkpoint { - // any changelog for the given key since then? - let start = Bound::inclusive(height); - let first = self - .changelog - .prefix(k.clone()) - .range_raw(store, Some(start), None, Order::Ascending) - .next() - .transpose()?; - if first.is_none() { - // there must be at least one open checkpoint and no changelog for the given height since then - return Ok(true); - } - } - // otherwise, we don't save this - Ok(false) - } - - // If there is no checkpoint for that height, then we return StdError::NotFound - pub fn assert_checkpointed(&self, store: &dyn Storage, height: u64) -> StdResult<()> { - let has = match self.strategy { - Strategy::EveryBlock => true, - Strategy::Never => false, - Strategy::Selected => self.checkpoints.may_load(store, height)?.is_some(), - }; - match has { - true => Ok(()), - false => Err(StdError::not_found("checkpoint")), - } - } - - pub fn has_changelog(&self, store: &mut dyn Storage, key: K, height: u64) -> StdResult { - Ok(self.changelog.may_load(store, (key, height))?.is_some()) - } - - pub fn write_changelog( - &self, - store: &mut dyn Storage, - key: K, - height: u64, - old: Option, - ) -> StdResult<()> { - self.changelog - .save(store, (key, height), &ChangeSet { old }) - } - - // may_load_at_height reads historical data from given checkpoints. - // Returns StdError::NotFound if we have no checkpoint, and can give no data. - // Returns Ok(None) if there is a checkpoint, but no cached data (no changes since the - // checkpoint. Caller should query current state). - // Return Ok(Some(x)) if there is a checkpoint and data written to changelog, returning the state at that time - pub fn may_load_at_height( - &self, - store: &dyn Storage, - key: K, - height: u64, - ) -> StdResult>> { - self.assert_checkpointed(store, height)?; - - // this will look for the first snapshot of height >= given height - // If None, there is no snapshot since that time. - let start = Bound::inclusive(height); - let first = self - .changelog - .prefix(key) - .range_raw(store, Some(start), None, Order::Ascending) - .next(); - - if let Some(r) = first { - // if we found a match, return this last one - r.map(|(_, v)| Some(v.old)) - } else { - Ok(None) - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub enum Strategy { - EveryBlock, - Never, - /// Only writes for linked blocks - does a few more reads to save some writes. - /// Probably uses more gas, but less total disk usage. - /// - /// Note that you need a trusted source (eg. own contract) to set/remove checkpoints. - /// Useful when the checkpoint setting happens in the same contract as the snapshotting. - Selected, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub struct ChangeSet { - pub old: Option, -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::MockStorage; - - type TestSnapshot = Snapshot<'static, &'static str, u64>; - - const NEVER: TestSnapshot = Snapshot::new("never__check", "never__change", Strategy::Never); - const EVERY: TestSnapshot = - Snapshot::new("every__check", "every__change", Strategy::EveryBlock); - const SELECT: TestSnapshot = - Snapshot::new("select__check", "select__change", Strategy::Selected); - - const DUMMY_KEY: &str = "dummy"; - - #[test] - fn should_checkpoint() { - let storage = MockStorage::new(); - - assert_eq!(NEVER.should_checkpoint(&storage, &DUMMY_KEY), Ok(false)); - assert_eq!(EVERY.should_checkpoint(&storage, &DUMMY_KEY), Ok(true)); - assert_eq!(SELECT.should_checkpoint(&storage, &DUMMY_KEY), Ok(false)); - } - - #[test] - fn assert_checkpointed() { - let mut storage = MockStorage::new(); - - assert_eq!( - NEVER.assert_checkpointed(&storage, 1), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!(EVERY.assert_checkpointed(&storage, 1), Ok(())); - assert_eq!( - SELECT.assert_checkpointed(&storage, 1), - Err(StdError::not_found("checkpoint")) - ); - - // Add a checkpoint at 1 - NEVER.add_checkpoint(&mut storage, 1).unwrap(); - EVERY.add_checkpoint(&mut storage, 1).unwrap(); - SELECT.add_checkpoint(&mut storage, 1).unwrap(); - - assert_eq!( - NEVER.assert_checkpointed(&storage, 1), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!(EVERY.assert_checkpointed(&storage, 1), Ok(())); - assert_eq!(SELECT.assert_checkpointed(&storage, 1), Ok(())); - - // Remove checkpoint - NEVER.remove_checkpoint(&mut storage, 1).unwrap(); - EVERY.remove_checkpoint(&mut storage, 1).unwrap(); - SELECT.remove_checkpoint(&mut storage, 1).unwrap(); - - assert_eq!( - NEVER.assert_checkpointed(&storage, 1), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!(EVERY.assert_checkpointed(&storage, 1), Ok(())); - assert_eq!( - SELECT.assert_checkpointed(&storage, 1), - Err(StdError::not_found("checkpoint")) - ); - } - - #[test] - fn has_changelog() { - let mut storage = MockStorage::new(); - - assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false)); - assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false)); - assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false)); - - assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(false)); - assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(false)); - assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(false)); - - assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false)); - assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false)); - assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false)); - - // Write a changelog at 2 - NEVER - .write_changelog(&mut storage, DUMMY_KEY, 2, Some(3)) - .unwrap(); - EVERY - .write_changelog(&mut storage, DUMMY_KEY, 2, Some(4)) - .unwrap(); - SELECT - .write_changelog(&mut storage, DUMMY_KEY, 2, Some(5)) - .unwrap(); - - assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false)); - assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false)); - assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false)); - - assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(true)); - assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(true)); - assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(true)); - - assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false)); - assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false)); - assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false)); - } - - #[test] - fn may_load_at_height() { - let mut storage = MockStorage::new(); - - assert_eq!( - NEVER.may_load_at_height(&storage, DUMMY_KEY, 3), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!(EVERY.may_load_at_height(&storage, DUMMY_KEY, 3), Ok(None)); - assert_eq!( - SELECT.may_load_at_height(&storage, DUMMY_KEY, 3), - Err(StdError::not_found("checkpoint")) - ); - - // Add a checkpoint at 3 - NEVER.add_checkpoint(&mut storage, 3).unwrap(); - EVERY.add_checkpoint(&mut storage, 3).unwrap(); - SELECT.add_checkpoint(&mut storage, 3).unwrap(); - - assert_eq!( - NEVER.may_load_at_height(&storage, DUMMY_KEY, 3), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!(EVERY.may_load_at_height(&storage, DUMMY_KEY, 3), Ok(None)); - assert_eq!(SELECT.may_load_at_height(&storage, DUMMY_KEY, 3), Ok(None)); - - // Write a changelog at 3 - NEVER - .write_changelog(&mut storage, DUMMY_KEY, 3, Some(100)) - .unwrap(); - EVERY - .write_changelog(&mut storage, DUMMY_KEY, 3, Some(101)) - .unwrap(); - SELECT - .write_changelog(&mut storage, DUMMY_KEY, 3, Some(102)) - .unwrap(); - - assert_eq!( - NEVER.may_load_at_height(&storage, DUMMY_KEY, 3), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!( - EVERY.may_load_at_height(&storage, DUMMY_KEY, 3), - Ok(Some(Some(101))) - ); - assert_eq!( - SELECT.may_load_at_height(&storage, DUMMY_KEY, 3), - Ok(Some(Some(102))) - ); - // Check that may_load_at_height at a previous value will return the first change after that. - // (Only with EVERY). - assert_eq!( - NEVER.may_load_at_height(&storage, DUMMY_KEY, 2), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!( - EVERY.may_load_at_height(&storage, DUMMY_KEY, 2), - Ok(Some(Some(101))) - ); - assert_eq!( - SELECT.may_load_at_height(&storage, DUMMY_KEY, 2), - Err(StdError::not_found("checkpoint")) - ); - - // Write a changelog at 4, removing the value - NEVER - .write_changelog(&mut storage, DUMMY_KEY, 4, None) - .unwrap(); - EVERY - .write_changelog(&mut storage, DUMMY_KEY, 4, None) - .unwrap(); - SELECT - .write_changelog(&mut storage, DUMMY_KEY, 4, None) - .unwrap(); - // And add a checkpoint at 4 - NEVER.add_checkpoint(&mut storage, 4).unwrap(); - EVERY.add_checkpoint(&mut storage, 4).unwrap(); - SELECT.add_checkpoint(&mut storage, 4).unwrap(); - - assert_eq!( - NEVER.may_load_at_height(&storage, DUMMY_KEY, 4), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!( - EVERY.may_load_at_height(&storage, DUMMY_KEY, 4), - Ok(Some(None)) - ); - assert_eq!( - SELECT.may_load_at_height(&storage, DUMMY_KEY, 4), - Ok(Some(None)) - ); - - // Confirm old value at 3 - assert_eq!( - NEVER.may_load_at_height(&storage, DUMMY_KEY, 3), - Err(StdError::not_found("checkpoint")) - ); - assert_eq!( - EVERY.may_load_at_height(&storage, DUMMY_KEY, 3), - Ok(Some(Some(101))) - ); - assert_eq!( - SELECT.may_load_at_height(&storage, DUMMY_KEY, 3), - Ok(Some(Some(102))) - ); - } -} diff --git a/packages/storage-plus/tests/index_list.rs b/packages/storage-plus/tests/index_list.rs deleted file mode 100644 index db0d2b843..000000000 --- a/packages/storage-plus/tests/index_list.rs +++ /dev/null @@ -1,76 +0,0 @@ -#[cfg(all(test, feature = "iterator", feature = "macro"))] -mod test { - use cosmwasm_std::{testing::MockStorage, Addr}; - use cw_storage_macro::index_list; - use cw_storage_plus::{IndexedMap, MultiIndex, UniqueIndex}; - use serde::{Deserialize, Serialize}; - - #[test] - fn index_list_compiles() { - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] - struct TestStruct { - id: u64, - id2: u32, - addr: Addr, - } - - #[index_list(TestStruct)] - struct TestIndexes<'a> { - id: MultiIndex<'a, u32, TestStruct, u64>, - addr: UniqueIndex<'a, Addr, TestStruct>, - } - - let _: IndexedMap = IndexedMap::new( - "t", - TestIndexes { - id: MultiIndex::new(|_pk, t| t.id2, "t", "t_id2"), - addr: UniqueIndex::new(|t| t.addr.clone(), "t_addr"), - }, - ); - } - - #[test] - fn index_list_works() { - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] - struct TestStruct { - id: u64, - id2: u32, - addr: Addr, - } - - #[index_list(TestStruct)] - struct TestIndexes<'a> { - id: MultiIndex<'a, u32, TestStruct, u64>, - addr: UniqueIndex<'a, Addr, TestStruct>, - } - - let mut storage = MockStorage::new(); - let idm: IndexedMap = IndexedMap::new( - "t", - TestIndexes { - id: MultiIndex::new(|_pk, t| t.id2, "t", "t_2"), - addr: UniqueIndex::new(|t| t.addr.clone(), "t_addr"), - }, - ); - - idm.save( - &mut storage, - 0, - &TestStruct { - id: 0, - id2: 100, - addr: Addr::unchecked("1"), - }, - ) - .unwrap(); - - assert_eq!( - idm.load(&storage, 0).unwrap(), - TestStruct { - id: 0, - id2: 100, - addr: Addr::unchecked("1"), - } - ); - } -} diff --git a/packages/utils/Cargo.toml b/packages/utils/Cargo.toml deleted file mode 100644 index 3611a9c26..000000000 --- a/packages/utils/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "cw-utils" -version = "0.16.0" -authors = ["Ethan Frey "] -edition = "2018" -description = "Common helpers for other cw specs" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-plus" -homepage = "https://cosmwasm.com" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -cosmwasm-schema = "1.1.0" -cosmwasm-std = "1.1.0" -cw2 = { path = "../../packages/cw2", version = "0.16.0" } -schemars = "0.8.1" -semver = "1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = "1.0.21" - -[dev-dependencies] -cw-storage-plus = { path = "../../packages/storage-plus", version = "0.16.0" } -prost = "0.9" diff --git a/packages/utils/NOTICE b/packages/utils/NOTICE deleted file mode 100644 index 6ce7d69d2..000000000 --- a/packages/utils/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -Utils: Common types and helpers for CosmWasm specs -Copyright (C) 2020 Confio OÜ - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/utils/README.md b/packages/utils/README.md deleted file mode 100644 index cfff5a1f5..000000000 --- a/packages/utils/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Utils: Common types / utilities for specs - -This is a collection of common types shared among many specs. -For example `Expiration`, which is embedded in many places. - -Types should only be added here after they are duplicated in -a second contract, not "because we might need it" diff --git a/packages/utils/src/balance.rs b/packages/utils/src/balance.rs deleted file mode 100644 index 16a15d561..000000000 --- a/packages/utils/src/balance.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::{fmt, ops}; - -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, OverflowError, OverflowOperation, StdError, StdResult, Uint128}; - -// Balance wraps Vec and provides some nice helpers. It mutates the Vec and can be -// unwrapped when done. -#[cw_serde] -#[derive(Default)] -pub struct NativeBalance(pub Vec); - -impl NativeBalance { - pub fn into_vec(self) -> Vec { - self.0 - } - - /// returns true if the list of coins has at least the required amount - pub fn has(&self, required: &Coin) -> bool { - self.0 - .iter() - .find(|c| c.denom == required.denom) - .map(|m| m.amount >= required.amount) - .unwrap_or(false) - } - - /// normalize Wallet (sorted by denom, no 0 elements, no duplicate denoms) - pub fn normalize(&mut self) { - // drop 0's - self.0.retain(|c| c.amount.u128() != 0); - // sort - self.0.sort_unstable_by(|a, b| a.denom.cmp(&b.denom)); - - // find all i where (self[i-1].denom == self[i].denom). - let mut dups: Vec = self - .0 - .iter() - .enumerate() - .filter_map(|(i, c)| { - if i != 0 && c.denom == self.0[i - 1].denom { - Some(i) - } else { - None - } - }) - .collect(); - dups.reverse(); - - // we go through the dups in reverse order (to avoid shifting indexes of other ones) - for dup in dups { - let add = self.0[dup].amount; - self.0[dup - 1].amount += add; - self.0.remove(dup); - } - } - - fn find(&self, denom: &str) -> Option<(usize, &Coin)> { - self.0.iter().enumerate().find(|(_i, c)| c.denom == denom) - } - - /// insert_pos should only be called when denom is not in the Wallet. - /// it returns the position where denom should be inserted at (via splice). - /// It returns None if this should be appended - fn insert_pos(&self, denom: &str) -> Option { - self.0.iter().position(|c| c.denom.as_str() >= denom) - } - - pub fn is_empty(&self) -> bool { - !self.0.iter().any(|x| x.amount != Uint128::zero()) - } - - /// similar to `Balance.sub`, but doesn't fail when minuend less than subtrahend - pub fn sub_saturating(mut self, other: Coin) -> StdResult { - match self.find(&other.denom) { - Some((i, c)) => { - if c.amount <= other.amount { - self.0.remove(i); - } else { - self.0[i].amount = self.0[i].amount.checked_sub(other.amount)?; - } - } - // error if no tokens - None => { - return Err(StdError::overflow(OverflowError::new( - OverflowOperation::Sub, - 0, - other.amount.u128(), - ))) - } - }; - Ok(self) - } -} - -impl fmt::Display for NativeBalance { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for c in &self.0 { - write!(f, "{}{}", c.denom, c.amount)? - } - Ok(()) - } -} - -impl ops::AddAssign for NativeBalance { - fn add_assign(&mut self, other: Coin) { - match self.find(&other.denom) { - Some((i, c)) => { - self.0[i].amount = c.amount + other.amount; - } - // place this in proper sorted order - None => match self.insert_pos(&other.denom) { - Some(idx) => self.0.insert(idx, other), - None => self.0.push(other), - }, - }; - } -} - -impl ops::Add for NativeBalance { - type Output = Self; - - fn add(mut self, other: Coin) -> Self { - self += other; - self - } -} - -impl ops::AddAssign for NativeBalance { - fn add_assign(&mut self, other: NativeBalance) { - for coin in other.0.into_iter() { - self.add_assign(coin); - } - } -} - -impl ops::Add for NativeBalance { - type Output = Self; - - fn add(mut self, other: NativeBalance) -> Self { - self += other; - self - } -} - -impl ops::Sub for NativeBalance { - type Output = StdResult; - - fn sub(mut self, other: Coin) -> StdResult { - match self.find(&other.denom) { - Some((i, c)) => { - let remainder = c.amount.checked_sub(other.amount)?; - if remainder.u128() == 0 { - self.0.remove(i); - } else { - self.0[i].amount = remainder; - } - } - // error if no tokens - None => { - return Err(StdError::overflow(OverflowError::new( - OverflowOperation::Sub, - 0, - other.amount.u128(), - ))) - } - }; - Ok(self) - } -} - -impl ops::Sub> for NativeBalance { - type Output = StdResult; - - fn sub(self, amount: Vec) -> StdResult { - let mut res = self; - for coin in amount { - res = res.sub(coin.clone())?; - } - Ok(res) - } -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::coin; - - #[test] - fn balance_has_works() { - let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]); - - // less than same type - assert!(balance.has(&coin(777, "ETH"))); - // equal to same type - assert!(balance.has(&coin(555, "BTC"))); - - // too high - assert!(!balance.has(&coin(12346, "ETH"))); - // wrong type - assert!(!balance.has(&coin(456, "ETC"))); - } - - #[test] - fn balance_add_works() { - let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]); - - // add an existing coin - let more_eth = balance.clone() + coin(54321, "ETH"); - assert_eq!( - more_eth, - NativeBalance(vec![coin(555, "BTC"), coin(66666, "ETH")]) - ); - - // add an new coin - let add_atom = balance + coin(777, "ATOM"); - assert_eq!( - add_atom, - NativeBalance(vec![ - coin(777, "ATOM"), - coin(555, "BTC"), - coin(12345, "ETH"), - ]) - ); - } - - #[test] - fn balance_in_place_addition() { - let mut balance = NativeBalance(vec![coin(555, "BTC")]); - balance += coin(777, "ATOM"); - assert_eq!( - &balance, - &NativeBalance(vec![coin(777, "ATOM"), coin(555, "BTC")]) - ); - - balance += NativeBalance(vec![coin(666, "ETH"), coin(123, "ATOM")]); - assert_eq!( - &balance, - &NativeBalance(vec![coin(900, "ATOM"), coin(555, "BTC"), coin(666, "ETH")]) - ); - - let sum = balance + NativeBalance(vec![coin(234, "BTC")]); - assert_eq!( - sum, - NativeBalance(vec![coin(900, "ATOM"), coin(789, "BTC"), coin(666, "ETH")]) - ); - } - - #[test] - fn balance_subtract_works() { - let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]); - - // subtract less than we have - let less_eth = (balance.clone() - coin(2345, "ETH")).unwrap(); - assert_eq!( - less_eth, - NativeBalance(vec![coin(555, "BTC"), coin(10000, "ETH")]) - ); - - // subtract all of one coin (and remove with 0 amount) - let no_btc = (balance.clone() - coin(555, "BTC")).unwrap(); - assert_eq!(no_btc, NativeBalance(vec![coin(12345, "ETH")])); - - // subtract more than we have - let underflow = balance.clone() - coin(666, "BTC"); - assert!(underflow.is_err()); - - // subtract non-existent denom - let missing = balance - coin(1, "ATOM"); - assert!(missing.is_err()); - } - - #[test] - fn balance_subtract_saturating_works() { - let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]); - - // subtract less than we have - let less_eth = balance.clone().sub_saturating(coin(2345, "ETH")).unwrap(); - assert_eq!( - less_eth, - NativeBalance(vec![coin(555, "BTC"), coin(10000, "ETH")]) - ); - - // subtract all of one coin (and remove with 0 amount) - let no_btc = balance.clone().sub_saturating(coin(555, "BTC")).unwrap(); - assert_eq!(no_btc, NativeBalance(vec![coin(12345, "ETH")])); - - // subtract more than we have - let saturating = balance.clone().sub_saturating(coin(666, "BTC")); - assert!(saturating.is_ok()); - assert_eq!(saturating.unwrap(), NativeBalance(vec![coin(12345, "ETH")])); - - // subtract non-existent denom - let missing = balance - coin(1, "ATOM"); - assert!(missing.is_err()); - } - - #[test] - fn normalize_balance() { - // remove 0 value items and sort - let mut balance = NativeBalance(vec![coin(123, "ETH"), coin(0, "BTC"), coin(8990, "ATOM")]); - balance.normalize(); - assert_eq!( - balance, - NativeBalance(vec![coin(8990, "ATOM"), coin(123, "ETH")]) - ); - - // merge duplicate entries of same denom - let mut balance = NativeBalance(vec![ - coin(123, "ETH"), - coin(789, "BTC"), - coin(321, "ETH"), - coin(11, "BTC"), - ]); - balance.normalize(); - assert_eq!( - balance, - NativeBalance(vec![coin(800, "BTC"), coin(444, "ETH")]) - ); - } -} diff --git a/packages/utils/src/event.rs b/packages/utils/src/event.rs deleted file mode 100644 index 79abed5a5..000000000 --- a/packages/utils/src/event.rs +++ /dev/null @@ -1,7 +0,0 @@ -use cosmwasm_std::Response; - -/// This defines a set of attributes which should be added to `Response`. -pub trait Event { - /// Append attributes to response - fn add_attributes(&self, response: &mut Response); -} diff --git a/packages/utils/src/expiration.rs b/packages/utils/src/expiration.rs deleted file mode 100644 index fe1d707dc..000000000 --- a/packages/utils/src/expiration.rs +++ /dev/null @@ -1,237 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{BlockInfo, StdError, StdResult, Timestamp}; -use std::cmp::Ordering; -use std::fmt; -use std::ops::{Add, Mul}; - -/// Expiration represents a point in time when some event happens. -/// It can compare with a BlockInfo and will return is_expired() == true -/// once the condition is hit (and for every block in the future) -#[cw_serde] -#[derive(Copy)] -pub enum Expiration { - /// AtHeight will expire when `env.block.height` >= height - AtHeight(u64), - /// AtTime will expire when `env.block.time` >= time - AtTime(Timestamp), - /// Never will never expire. Used to express the empty variant - Never {}, -} - -impl fmt::Display for Expiration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Expiration::AtHeight(height) => write!(f, "expiration height: {}", height), - Expiration::AtTime(time) => write!(f, "expiration time: {}", time), - Expiration::Never {} => write!(f, "expiration: never"), - } - } -} - -/// The default (empty value) is to never expire -impl Default for Expiration { - fn default() -> Self { - Expiration::Never {} - } -} - -impl Expiration { - pub fn is_expired(&self, block: &BlockInfo) -> bool { - match self { - Expiration::AtHeight(height) => block.height >= *height, - Expiration::AtTime(time) => block.time >= *time, - Expiration::Never {} => false, - } - } -} - -impl Add for Expiration { - type Output = StdResult; - - fn add(self, duration: Duration) -> StdResult { - match (self, duration) { - (Expiration::AtTime(t), Duration::Time(delta)) => { - Ok(Expiration::AtTime(t.plus_seconds(delta))) - } - (Expiration::AtHeight(h), Duration::Height(delta)) => { - Ok(Expiration::AtHeight(h + delta)) - } - (Expiration::Never {}, _) => Ok(Expiration::Never {}), - _ => Err(StdError::generic_err("Cannot add height and time")), - } - } -} - -// TODO: does this make sense? do we get expected info/error when None is returned??? -impl PartialOrd for Expiration { - fn partial_cmp(&self, other: &Expiration) -> Option { - match (self, other) { - // compare if both height or both time - (Expiration::AtHeight(h1), Expiration::AtHeight(h2)) => Some(h1.cmp(h2)), - (Expiration::AtTime(t1), Expiration::AtTime(t2)) => Some(t1.cmp(t2)), - // if at least one is never, we can compare with anything - (Expiration::Never {}, Expiration::Never {}) => Some(Ordering::Equal), - (Expiration::Never {}, _) => Some(Ordering::Greater), - (_, Expiration::Never {}) => Some(Ordering::Less), - // if they are mis-matched finite ends, no compare possible - _ => None, - } - } -} - -pub const HOUR: Duration = Duration::Time(60 * 60); -pub const DAY: Duration = Duration::Time(24 * 60 * 60); -pub const WEEK: Duration = Duration::Time(7 * 24 * 60 * 60); - -/// Duration is a delta of time. You can add it to a BlockInfo or Expiration to -/// move that further in the future. Note that an height-based Duration and -/// a time-based Expiration cannot be combined -#[cw_serde] -#[derive(Copy)] -pub enum Duration { - Height(u64), - /// Time in seconds - Time(u64), -} - -impl fmt::Display for Duration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Duration::Height(height) => write!(f, "height: {}", height), - Duration::Time(time) => write!(f, "time: {}", time), - } - } -} - -impl Duration { - /// Create an expiration for Duration after current block - pub fn after(&self, block: &BlockInfo) -> Expiration { - match self { - Duration::Height(h) => Expiration::AtHeight(block.height + h), - Duration::Time(t) => Expiration::AtTime(block.time.plus_seconds(*t)), - } - } - - // creates a number just a little bigger, so we can use it to pass expiration point - pub fn plus_one(&self) -> Duration { - match self { - Duration::Height(h) => Duration::Height(h + 1), - Duration::Time(t) => Duration::Time(t + 1), - } - } -} - -impl Add for Duration { - type Output = StdResult; - - fn add(self, rhs: Duration) -> StdResult { - match (self, rhs) { - (Duration::Time(t), Duration::Time(t2)) => Ok(Duration::Time(t + t2)), - (Duration::Height(h), Duration::Height(h2)) => Ok(Duration::Height(h + h2)), - _ => Err(StdError::generic_err("Cannot add height and time")), - } - } -} - -impl Mul for Duration { - type Output = Duration; - - fn mul(self, rhs: u64) -> Self::Output { - match self { - Duration::Time(t) => Duration::Time(t * rhs), - Duration::Height(h) => Duration::Height(h * rhs), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn compare_expiration() { - // matching pairs - assert!(Expiration::AtHeight(5) < Expiration::AtHeight(10)); - assert!(Expiration::AtHeight(8) > Expiration::AtHeight(7)); - assert!( - Expiration::AtTime(Timestamp::from_seconds(555)) - < Expiration::AtTime(Timestamp::from_seconds(777)) - ); - assert!( - Expiration::AtTime(Timestamp::from_seconds(86)) - < Expiration::AtTime(Timestamp::from_seconds(100)) - ); - - // never as infinity - assert!(Expiration::AtHeight(500000) < Expiration::Never {}); - assert!(Expiration::Never {} > Expiration::AtTime(Timestamp::from_seconds(500000))); - - // what happens for the uncomparables?? all compares are false - assert_eq!( - None, - Expiration::AtTime(Timestamp::from_seconds(1000)) - .partial_cmp(&Expiration::AtHeight(230)) - ); - assert_eq!( - Expiration::AtTime(Timestamp::from_seconds(1000)) - .partial_cmp(&Expiration::AtHeight(230)), - None - ); - assert_eq!( - Expiration::AtTime(Timestamp::from_seconds(1000)) - .partial_cmp(&Expiration::AtHeight(230)), - None - ); - assert!(!(Expiration::AtTime(Timestamp::from_seconds(1000)) == Expiration::AtHeight(230))); - } - - #[test] - fn expiration_addition() { - // height - let end = Expiration::AtHeight(12345) + Duration::Height(400); - assert_eq!(end.unwrap(), Expiration::AtHeight(12745)); - - // time - let end = Expiration::AtTime(Timestamp::from_seconds(55544433)) + Duration::Time(40300); - assert_eq!( - end.unwrap(), - Expiration::AtTime(Timestamp::from_seconds(55584733)) - ); - - // never - let end = Expiration::Never {} + Duration::Time(40300); - assert_eq!(end.unwrap(), Expiration::Never {}); - - // mismatched - let end = Expiration::AtHeight(12345) + Duration::Time(1500); - end.unwrap_err(); - - // // not possible other way - // let end = Duration::Time(1000) + Expiration::AtTime(50000); - // assert_eq!(end.unwrap(), Expiration::AtTime(51000)); - } - - #[test] - fn block_plus_duration() { - let block = BlockInfo { - height: 1000, - time: Timestamp::from_seconds(7777), - chain_id: "foo".to_string(), - }; - - let end = Duration::Height(456).after(&block); - assert_eq!(Expiration::AtHeight(1456), end); - - let end = Duration::Time(1212).after(&block); - assert_eq!(Expiration::AtTime(Timestamp::from_seconds(8989)), end); - } - - #[test] - fn duration_math() { - let long = (Duration::Height(444) + Duration::Height(555)).unwrap(); - assert_eq!(Duration::Height(999), long); - - let days = DAY * 3; - assert_eq!(Duration::Time(3 * 24 * 60 * 60), days); - } -} diff --git a/packages/utils/src/lib.rs b/packages/utils/src/lib.rs deleted file mode 100644 index d88e65921..000000000 --- a/packages/utils/src/lib.rs +++ /dev/null @@ -1,34 +0,0 @@ -/*! -This is a collection of common types shared among many specs. -For example [`Expiration`], which is embedded in many places. - -Types should only be added here after they are duplicated in -a second contract, not "because we might need it" -*/ - -mod balance; -mod event; -mod expiration; -mod migrate; -mod pagination; -mod parse_reply; -mod payment; -mod scheduled; -mod threshold; - -pub use migrate::ensure_from_older_version; -pub use pagination::{ - calc_range_end, calc_range_start, calc_range_start_string, maybe_addr, maybe_canonical, -}; -pub use parse_reply::{ - parse_execute_response_data, parse_instantiate_response_data, parse_reply_execute_data, - parse_reply_instantiate_data, MsgExecuteContractResponse, MsgInstantiateContractResponse, - ParseReplyError, -}; -pub use payment::{may_pay, must_pay, nonpayable, one_coin, PaymentError}; -pub use threshold::{Threshold, ThresholdError, ThresholdResponse}; - -pub use crate::balance::NativeBalance; -pub use crate::event::Event; -pub use crate::expiration::{Duration, Expiration, DAY, HOUR, WEEK}; -pub use crate::scheduled::Scheduled; diff --git a/packages/utils/src/migrate.rs b/packages/utils/src/migrate.rs deleted file mode 100644 index e537cba5a..000000000 --- a/packages/utils/src/migrate.rs +++ /dev/null @@ -1,100 +0,0 @@ -use cosmwasm_std::{StdError, StdResult, Storage}; -use cw2::{get_contract_version, set_contract_version}; -use semver::Version; - -/// This function not only validates that the right contract and version can be migrated, but also -/// updates the contract version from the original (stored) version to the new version. -/// It returns the original version for the convenience of doing external checks. -pub fn ensure_from_older_version( - storage: &mut dyn Storage, - name: &str, - new_version: &str, -) -> StdResult { - let version: Version = new_version.parse().map_err(from_semver)?; - let stored = get_contract_version(storage)?; - let storage_version: Version = stored.version.parse().map_err(from_semver)?; - - if name != stored.contract { - let msg = format!("Cannot migrate from {} to {}", stored.contract, name); - return Err(StdError::generic_err(msg)); - } - - if storage_version > version { - let msg = format!( - "Cannot migrate from newer version ({}) to older ({})", - stored.version, new_version - ); - return Err(StdError::generic_err(msg)); - } - if storage_version < version { - // we don't need to save anything if migrating from the same version - set_contract_version(storage, name, new_version)?; - } - - Ok(storage_version) -} - -fn from_semver(err: semver::Error) -> StdError { - StdError::generic_err(format!("Semver: {}", err)) -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::MockStorage; - - #[test] - fn accepts_identical_version() { - let mut storage = MockStorage::new(); - set_contract_version(&mut storage, "demo", "0.1.2").unwrap(); - // ensure this matches - ensure_from_older_version(&mut storage, "demo", "0.1.2").unwrap(); - } - - #[test] - fn accepts_and_updates_on_newer_version() { - let mut storage = MockStorage::new(); - set_contract_version(&mut storage, "demo", "0.4.0").unwrap(); - // ensure this matches - let original_version = ensure_from_older_version(&mut storage, "demo", "0.4.2").unwrap(); - - // check the original version is returned - assert_eq!(original_version.to_string(), "0.4.0".to_string()); - - // check the version is updated - let stored = get_contract_version(&storage).unwrap(); - assert_eq!(stored.contract, "demo".to_string()); - assert_eq!(stored.version, "0.4.2".to_string()); - } - - #[test] - fn errors_on_name_mismatch() { - let mut storage = MockStorage::new(); - set_contract_version(&mut storage, "demo", "0.1.2").unwrap(); - // ensure this matches - let err = ensure_from_older_version(&mut storage, "cw20-base", "0.1.2").unwrap_err(); - assert!(err.to_string().contains("cw20-base"), "{}", err); - assert!(err.to_string().contains("demo"), "{}", err); - } - - #[test] - fn errors_on_older_version() { - let mut storage = MockStorage::new(); - set_contract_version(&mut storage, "demo", "0.10.2").unwrap(); - // ensure this matches - let err = ensure_from_older_version(&mut storage, "demo", "0.9.7").unwrap_err(); - assert!(err.to_string().contains("0.10.2"), "{}", err); - assert!(err.to_string().contains("0.9.7"), "{}", err); - } - - #[test] - fn errors_on_broken_version() { - let mut storage = MockStorage::new(); - let err = ensure_from_older_version(&mut storage, "demo", "0.a.7").unwrap_err(); - assert!( - err.to_string().contains("unexpected character 'a'"), - "{}", - err - ); - } -} diff --git a/packages/utils/src/pagination.rs b/packages/utils/src/pagination.rs deleted file mode 100644 index a88996acb..000000000 --- a/packages/utils/src/pagination.rs +++ /dev/null @@ -1,115 +0,0 @@ -use cosmwasm_std::{Addr, Api, CanonicalAddr, StdResult}; - -// this is used for pagination. Maybe we move it into the std lib one day? -pub fn maybe_canonical(api: &dyn Api, human: Option) -> StdResult> { - human.map(|x| api.addr_canonicalize(x.as_ref())).transpose() -} - -// This is used for pagination. Maybe we move it into the std lib one day? -pub fn maybe_addr(api: &dyn Api, human: Option) -> StdResult> { - human.map(|x| api.addr_validate(&x)).transpose() -} - -// this will set the first key after the provided key, by appending a 0 byte -pub fn calc_range_start(start_after: Option) -> Option> { - start_after.map(|addr| { - let mut v: Vec = addr.as_bytes().into(); - v.push(0); - v - }) -} - -// set the end to the canonicalized format (used for Order::Descending) -pub fn calc_range_end(end_before: Option) -> Option> { - end_before.map(|addr| addr.as_bytes().into()) -} - -// this will set the first key after the provided key, by appending a 0 byte -pub fn calc_range_start_string(start_after: Option) -> Option> { - start_after.map(|token_id| { - let mut v: Vec = token_id.into_bytes(); - v.push(0); - v - }) -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::{testing::mock_dependencies, Order}; - use cw_storage_plus::{Bound, Map}; - - pub const HOLDERS: Map<&Addr, usize> = Map::new("some_data"); - const LIMIT: usize = 30; - - fn addr_from_i(i: usize) -> Addr { - Addr::unchecked(format!("addr{:0>8}", i)) - } - - #[test] - fn calc_range_start_works_as_expected() { - let total_elements_count = 100; - let mut deps = mock_dependencies(); - for i in 0..total_elements_count { - let holder = (addr_from_i(i), i); - HOLDERS - .save(&mut deps.storage, &holder.0, &holder.1) - .unwrap(); - } - - for j in 0..4 { - let start_after = if j == 0 { - None - } else { - Some(addr_from_i(j * LIMIT - 1)) - }; - - let start = calc_range_start(start_after).map(Bound::ExclusiveRaw); - - let holders = HOLDERS - .keys(&deps.storage, start, None, Order::Ascending) - .take(LIMIT) - .collect::>>() - .unwrap(); - - for (i, holder) in holders.into_iter().enumerate() { - let global_index = j * LIMIT + i; - assert_eq!(holder, addr_from_i(global_index)); - } - } - } - - #[test] - fn calc_range_end_works_as_expected() { - let total_elements_count = 100; - let mut deps = mock_dependencies(); - for i in 0..total_elements_count { - let holder = (addr_from_i(i), i); - HOLDERS - .save(&mut deps.storage, &holder.0, &holder.1) - .unwrap(); - } - - for j in 0..4 { - let end_before = Some(addr_from_i(total_elements_count - j * LIMIT)); - - let end = calc_range_end(end_before).map(Bound::ExclusiveRaw); - - let holders = HOLDERS - .keys(&deps.storage, None, end, Order::Descending) - .take(LIMIT) - .collect::>>() - .unwrap(); - - for (i, holder) in holders.into_iter().enumerate() { - let global_index = total_elements_count - i - j * LIMIT - 1; - assert_eq!(holder, addr_from_i(global_index)); - } - } - } - - // TODO: add unit tests - #[ignore] - #[test] - fn add_more_tests() {} -} diff --git a/packages/utils/src/parse_reply.rs b/packages/utils/src/parse_reply.rs deleted file mode 100644 index f52d18933..000000000 --- a/packages/utils/src/parse_reply.rs +++ /dev/null @@ -1,528 +0,0 @@ -use thiserror::Error; - -use cosmwasm_std::{Binary, Reply}; - -// Protobuf wire types (https://developers.google.com/protocol-buffers/docs/encoding) -const WIRE_TYPE_LENGTH_DELIMITED: u8 = 2; -// Up to 9 bytes of varints as a practical limit (https://github.com/multiformats/unsigned-varint#practical-maximum-of-9-bytes-for-security) -const VARINT_MAX_BYTES: usize = 9; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MsgInstantiateContractResponse { - pub contract_address: String, - pub data: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MsgExecuteContractResponse { - pub data: Option, -} - -/// Base128 varint decoding. -/// The remaining of the data is kept in the data parameter. -fn parse_protobuf_varint(data: &mut Vec, field_number: u8) -> Result { - let data_len = data.len(); - let mut len: u64 = 0; - let mut i = 0; - while i < VARINT_MAX_BYTES { - if data_len == i { - return Err(ParseReplyError::ParseFailure(format!( - "failed to decode Protobuf message: field #{}: varint data too short", - field_number - ))); - } - len += ((data[i] & 0x7f) as u64) << (i * 7); - if data[i] & 0x80 == 0 { - break; - } - i += 1; - } - if i == VARINT_MAX_BYTES { - return Err(ParseReplyError::ParseFailure(format!( - "failed to decode Protobuf message: field #{}: varint data too long", - field_number - ))); - } - *data = data[i + 1..].to_owned(); - - Ok(len as usize) // Gently fall back to the arch's max addressable size -} - -/// Helper function to parse length-prefixed protobuf fields. -/// The remaining of the data is kept in the data parameter. -fn parse_protobuf_length_prefixed( - data: &mut Vec, - field_number: u8, -) -> Result, ParseReplyError> { - if data.is_empty() { - return Ok(vec![]); - }; - let mut rest_1 = data.split_off(1); - let wire_type = data[0] & 0b11; - let field = data[0] >> 3; - - if field != field_number { - return Err(ParseReplyError::ParseFailure(format!( - "failed to decode Protobuf message: invalid field #{} for field #{}", - field, field_number - ))); - } - if wire_type != WIRE_TYPE_LENGTH_DELIMITED { - return Err(ParseReplyError::ParseFailure(format!( - "failed to decode Protobuf message: field #{}: invalid wire type {}", - field_number, wire_type - ))); - } - - let len = parse_protobuf_varint(&mut rest_1, field_number)?; - if rest_1.len() < len { - return Err(ParseReplyError::ParseFailure(format!( - "failed to decode Protobuf message: field #{}: message too short", - field_number - ))); - } - *data = rest_1.split_off(len); - - Ok(rest_1) -} - -fn parse_protobuf_string(data: &mut Vec, field_number: u8) -> Result { - let str_field = parse_protobuf_length_prefixed(data, field_number)?; - Ok(String::from_utf8(str_field)?) -} - -fn parse_protobuf_bytes( - data: &mut Vec, - field_number: u8, -) -> Result, ParseReplyError> { - let bytes_field = parse_protobuf_length_prefixed(data, field_number)?; - if bytes_field.is_empty() { - Ok(None) - } else { - Ok(Some(Binary(bytes_field))) - } -} - -pub fn parse_reply_instantiate_data( - msg: Reply, -) -> Result { - let data = msg - .result - .into_result() - .map_err(ParseReplyError::SubMsgFailure)? - .data - .ok_or_else(|| ParseReplyError::ParseFailure("Missing reply data".to_owned()))?; - parse_instantiate_response_data(&data.0) -} - -pub fn parse_reply_execute_data(msg: Reply) -> Result { - let data = msg - .result - .into_result() - .map_err(ParseReplyError::SubMsgFailure)? - .data - .ok_or_else(|| ParseReplyError::ParseFailure("Missing reply data".to_owned()))?; - parse_execute_response_data(&data.0) -} - -pub fn parse_instantiate_response_data( - data: &[u8], -) -> Result { - // Manual protobuf decoding - let mut data = data.to_vec(); - // Parse contract addr - let contract_addr = parse_protobuf_string(&mut data, 1)?; - - // Parse (optional) data - let data = parse_protobuf_bytes(&mut data, 2)?; - - Ok(MsgInstantiateContractResponse { - contract_address: contract_addr, - data, - }) -} - -pub fn parse_execute_response_data( - data: &[u8], -) -> Result { - // Manual protobuf decoding - let mut data = data.to_vec(); - let inner_data = parse_protobuf_bytes(&mut data, 1)?; - - Ok(MsgExecuteContractResponse { data: inner_data }) -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum ParseReplyError { - #[error("Failure response from sub-message: {0}")] - SubMsgFailure(String), - - #[error("Invalid reply from sub-message: {0}")] - ParseFailure(String), - - #[error("Error occurred while converting from UTF-8")] - BrokenUtf8(#[from] std::string::FromUtf8Error), -} - -#[cfg(test)] -mod test { - use super::*; - use crate::parse_reply::ParseReplyError::{BrokenUtf8, ParseFailure}; - use cosmwasm_std::{SubMsgResponse, SubMsgResult}; - use prost::Message; - use std::str::from_utf8; - - fn encode_bytes(data: &[u8]) -> Vec { - #[derive(Clone, PartialEq, Message)] - struct ProtobufBytes { - #[prost(bytes, tag = "1")] - pub data: Vec, - } - - let data = ProtobufBytes { - data: data.to_vec(), - }; - let mut encoded_data = Vec::::with_capacity(data.encoded_len()); - data.encode(&mut encoded_data).unwrap(); - - encoded_data - } - - fn encode_string(data: &str) -> Vec { - #[derive(Clone, PartialEq, Message)] - struct ProtobufString { - #[prost(string, tag = "1")] - pub data: String, - } - - let data = ProtobufString { - data: data.to_string(), - }; - let mut encoded_data = Vec::::with_capacity(data.encoded_len()); - data.encode(&mut encoded_data).unwrap(); - - encoded_data - } - - #[derive(Clone, PartialEq, Message)] - struct MsgInstantiateContractResponse { - #[prost(string, tag = "1")] - pub contract_address: ::prost::alloc::string::String, - #[prost(bytes, tag = "2")] - pub data: ::prost::alloc::vec::Vec, - } - - #[derive(Clone, PartialEq, Message)] - struct MsgExecuteContractResponse { - #[prost(bytes, tag = "1")] - pub data: ::prost::alloc::vec::Vec, - } - - #[test] - fn parse_protobuf_varint_tests() { - let field_number = 1; - // Single-byte varint works - let mut data = b"\x0a".to_vec(); - let len = parse_protobuf_varint(&mut data, field_number).unwrap(); - assert_eq!(len, 10); - - // Rest is returned - let mut data = b"\x0a\x0b".to_vec(); - let len = parse_protobuf_varint(&mut data, field_number).unwrap(); - assert_eq!(len, 10); - assert_eq!(data, b"\x0b".to_vec()); - - // Multi-byte varint works - // 300 % 128 = 44. 44 + 128 = 172 (0xac) (1st byte) - // 300 / 128 = 2 (x02) (2nd byte) - let mut data = b"\xac\x02".to_vec(); - let len = parse_protobuf_varint(&mut data, field_number).unwrap(); - assert_eq!(len, 300); - - // Rest is returned - let mut data = b"\xac\x02\x0c".to_vec(); - let len = parse_protobuf_varint(&mut data, field_number).unwrap(); - assert_eq!(len, 300); - assert_eq!(data, b"\x0c".to_vec()); - - // varint data too short (Empty varint) - let mut data = vec![]; - let err = parse_protobuf_varint(&mut data, field_number).unwrap_err(); - assert!(matches!(err, ParseFailure(..))); - - // varint data too short (Incomplete varint) - let mut data = b"\x80".to_vec(); - let err = parse_protobuf_varint(&mut data, field_number).unwrap_err(); - assert!(matches!(err, ParseFailure(..))); - - // varint data too long - let mut data = b"\x80\x81\x82\x83\x84\x83\x82\x81\x80".to_vec(); - let err = parse_protobuf_varint(&mut data, field_number).unwrap_err(); - assert!(matches!(err, ParseFailure(..))); - } - - #[test] - fn parse_protobuf_length_prefixed_tests() { - let field_number = 1; - // Single-byte length-prefixed works - let mut data = b"\x0a\x03abc".to_vec(); - let res = parse_protobuf_length_prefixed(&mut data, field_number).unwrap(); - assert_eq!(res, b"abc".to_vec()); - assert_eq!(data, vec![0u8; 0]); - - // Rest is returned - let mut data = b"\x0a\x03abcd".to_vec(); - let res = parse_protobuf_length_prefixed(&mut data, field_number).unwrap(); - assert_eq!(res, b"abc".to_vec()); - assert_eq!(data, b"d".to_vec()); - - // Multi-byte length-prefixed works - let mut data = [b"\x0a\xac\x02", vec![65u8; 300].as_slice()] - .concat() - .to_vec(); - let res = parse_protobuf_length_prefixed(&mut data, field_number).unwrap(); - assert_eq!(res, vec![65u8; 300]); - assert_eq!(data, vec![0u8; 0]); - - // Rest is returned - let mut data = [b"\x0a\xac\x02", vec![65u8; 300].as_slice(), b"rest"] - .concat() - .to_vec(); - let res = parse_protobuf_length_prefixed(&mut data, field_number).unwrap(); - assert_eq!(res, vec![65u8; 300]); - assert_eq!(data, b"rest"); - - // message too short - let mut data = b"\x0a\x01".to_vec(); - let field_number = 1; - let err = parse_protobuf_length_prefixed(&mut data, field_number).unwrap_err(); - assert!(matches!(err, ParseFailure(..))); - - // invalid wire type - let mut data = b"\x0b\x01a".to_vec(); - let err = parse_protobuf_length_prefixed(&mut data, field_number).unwrap_err(); - assert!(matches!(err, ParseFailure(..))); - - // invalid field number - let field_number = 2; - let mut data = b"\x0a\x01a".to_vec(); - let err = parse_protobuf_length_prefixed(&mut data, field_number).unwrap_err(); - assert!(matches!(err, ParseFailure(..))); - } - - #[test] - fn parse_protobuf_bytes_works() { - let field_number = 1; - - // Empty works - let data = vec![]; - let mut encoded_data = encode_bytes(&data); - - let res = parse_protobuf_bytes(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, None); - - // Simple works - let data = b"test".to_vec(); - let mut encoded_data = encode_bytes(&data); - - let res = parse_protobuf_bytes(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, Some(Binary(data))); - - // Large works - let data = vec![0x40; 300]; - let mut encoded_data = encode_bytes(&data); - - let res = parse_protobuf_bytes(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, Some(Binary(data))); - - // Field number works - let field_number = 5; - let data = b"test field 5".to_vec(); - let mut encoded_data = encode_bytes(&data); - encoded_data[0] = (field_number << 3) + WIRE_TYPE_LENGTH_DELIMITED; - - let res = parse_protobuf_bytes(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, Some(Binary(data))); - - // Remainder is kept - let field_number = 1; - let test_len: usize = 4; - let data = b"test_remainder".to_vec(); - let mut encoded_data = encode_bytes(&data); - encoded_data[1] = test_len as u8; - - let res = parse_protobuf_bytes(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, Some(Binary(data[..test_len].to_owned()))); - assert_eq!(encoded_data, data[test_len..].to_owned()); - } - - #[test] - fn parse_protobuf_string_tests() { - let field_number = 1; - - // Empty works - let data = ""; - let mut encoded_data = encode_string(data); - - let res = parse_protobuf_string(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, data); - - // Simple works - let data = "test"; - let mut encoded_data = encode_string(data); - - let res = parse_protobuf_string(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, data); - - // Large works - let data = vec![0x40; 300]; - let str_data = from_utf8(data.as_slice()).unwrap(); - let mut encoded_data = encode_string(str_data); - - let res = parse_protobuf_string(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, str_data); - - // Field number works - let field_number = 5; - let data = "test field 5"; - let mut encoded_data = encode_string(data); - encoded_data[0] = (field_number << 3) + WIRE_TYPE_LENGTH_DELIMITED; - - let res = parse_protobuf_string(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, data); - - // Remainder is kept - let field_number = 1; - let test_len: usize = 4; - let data = "test_remainder"; - let mut encoded_data = encode_string(data); - encoded_data[1] = test_len as u8; - - let res = parse_protobuf_string(&mut encoded_data, field_number).unwrap(); - assert_eq!(res, data[..test_len]); - assert_eq!(encoded_data, data[test_len..].as_bytes()); - - // Broken utf-8 errs - let field_number = 1; - let data = "test_X"; - let mut encoded_data = encode_string(data); - let encoded_len = encoded_data.len(); - encoded_data[encoded_len - 1] = 0xd3; - let err = parse_protobuf_string(&mut encoded_data, field_number).unwrap_err(); - assert!(matches!(err, BrokenUtf8(..))); - } - - #[test] - fn parse_reply_instantiate_data_works() { - let contract_addr: &str = "Contract #1"; - for (data, expected) in [ - ( - vec![], - super::MsgInstantiateContractResponse { - contract_address: contract_addr.to_string(), - data: None, - }, - ), - ( - vec![1u8, 2, 255, 7, 5], - super::MsgInstantiateContractResponse { - contract_address: contract_addr.to_string(), - data: Some(Binary(vec![1u8, 2, 255, 7, 5])), - }, - ), - ( - vec![1u8; 127], - super::MsgInstantiateContractResponse { - contract_address: contract_addr.to_string(), - data: Some(Binary(vec![1u8; 127])), - }, - ), - ( - vec![2u8; 128], - super::MsgInstantiateContractResponse { - contract_address: contract_addr.to_string(), - data: Some(Binary(vec![2u8; 128])), - }, - ), - ( - vec![3u8; 257], - super::MsgInstantiateContractResponse { - contract_address: contract_addr.to_string(), - data: Some(Binary(vec![3u8; 257])), - }, - ), - ] { - let instantiate_reply = MsgInstantiateContractResponse { - contract_address: contract_addr.to_string(), - data, - }; - let mut encoded_instantiate_reply = - Vec::::with_capacity(instantiate_reply.encoded_len()); - // The data must encode successfully - instantiate_reply - .encode(&mut encoded_instantiate_reply) - .unwrap(); - - // Build reply message - let msg = Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some(encoded_instantiate_reply.into()), - }), - }; - - let res = parse_reply_instantiate_data(msg).unwrap(); - assert_eq!(res, expected); - } - } - - #[test] - fn parse_reply_execute_data_works() { - for (data, expected) in [ - (vec![], super::MsgExecuteContractResponse { data: None }), - ( - vec![1u8, 2, 3, 127, 15], - super::MsgExecuteContractResponse { - data: Some(Binary(vec![1u8, 2, 3, 127, 15])), - }, - ), - ( - vec![0u8; 255], - super::MsgExecuteContractResponse { - data: Some(Binary(vec![0u8; 255])), - }, - ), - ( - vec![1u8; 256], - super::MsgExecuteContractResponse { - data: Some(Binary(vec![1u8; 256])), - }, - ), - ( - vec![2u8; 32769], - super::MsgExecuteContractResponse { - data: Some(Binary(vec![2u8; 32769])), - }, - ), - ] { - let execute_reply = MsgExecuteContractResponse { data }; - let mut encoded_execute_reply = Vec::::with_capacity(execute_reply.encoded_len()); - // The data must encode successfully - execute_reply.encode(&mut encoded_execute_reply).unwrap(); - - // Build reply message - let msg = Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some(encoded_execute_reply.into()), - }), - }; - - let res = parse_reply_execute_data(msg).unwrap(); - - assert_eq!(res, expected); - } - } -} diff --git a/packages/utils/src/payment.rs b/packages/utils/src/payment.rs deleted file mode 100644 index f6ccdd201..000000000 --- a/packages/utils/src/payment.rs +++ /dev/null @@ -1,136 +0,0 @@ -use cosmwasm_std::{Coin, MessageInfo, Uint128}; -use thiserror::Error; - -/// returns an error if any coins were sent -pub fn nonpayable(info: &MessageInfo) -> Result<(), PaymentError> { - if info.funds.is_empty() { - Ok(()) - } else { - Err(PaymentError::NonPayable {}) - } -} - -/// If exactly one coin was sent, returns it regardless of denom. -/// Returns error if 0 or 2+ coins were sent -pub fn one_coin(info: &MessageInfo) -> Result { - match info.funds.len() { - 0 => Err(PaymentError::NoFunds {}), - 1 => { - let coin = &info.funds[0]; - if coin.amount.is_zero() { - Err(PaymentError::NoFunds {}) - } else { - Ok(coin.clone()) - } - } - _ => Err(PaymentError::MultipleDenoms {}), - } -} - -/// Requires exactly one denom sent, which matches the requested denom. -/// Returns the amount if only one denom and non-zero amount. Errors otherwise. -pub fn must_pay(info: &MessageInfo, denom: &str) -> Result { - let coin = one_coin(info)?; - if coin.denom != denom { - Err(PaymentError::MissingDenom(denom.to_string())) - } else { - Ok(coin.amount) - } -} - -/// Similar to must_pay, but it any payment is optional. Returns an error if a different -/// denom was sent. Otherwise, returns the amount of `denom` sent, or 0 if nothing sent. -pub fn may_pay(info: &MessageInfo, denom: &str) -> Result { - if info.funds.is_empty() { - Ok(Uint128::zero()) - } else if info.funds.len() == 1 && info.funds[0].denom == denom { - Ok(info.funds[0].amount) - } else { - // find first mis-match - let wrong = info.funds.iter().find(|c| c.denom != denom).unwrap(); - Err(PaymentError::ExtraDenom(wrong.denom.to_string())) - } -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum PaymentError { - #[error("Must send reserve token '{0}'")] - MissingDenom(String), - - #[error("Received unsupported denom '{0}'")] - ExtraDenom(String), - - #[error("Sent more than one denomination")] - MultipleDenoms {}, - - #[error("No funds sent")] - NoFunds {}, - - #[error("This message does no accept funds")] - NonPayable {}, -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{coin, coins}; - - const SENDER: &str = "sender"; - - #[test] - fn nonpayable_works() { - let no_payment = mock_info(SENDER, &[]); - nonpayable(&no_payment).unwrap(); - - let payment = mock_info(SENDER, &coins(100, "uatom")); - let res = nonpayable(&payment); - assert_eq!(res.unwrap_err(), PaymentError::NonPayable {}); - } - - #[test] - fn may_pay_works() { - let atom: &str = "uatom"; - let no_payment = mock_info(SENDER, &[]); - let atom_payment = mock_info(SENDER, &coins(100, atom)); - let eth_payment = mock_info(SENDER, &coins(100, "wei")); - let mixed_payment = mock_info(SENDER, &[coin(50, atom), coin(120, "wei")]); - - let res = may_pay(&no_payment, atom).unwrap(); - assert_eq!(res, Uint128::zero()); - - let res = may_pay(&atom_payment, atom).unwrap(); - assert_eq!(res, Uint128::new(100)); - - let err = may_pay(ð_payment, atom).unwrap_err(); - assert_eq!(err, PaymentError::ExtraDenom("wei".to_string())); - - let err = may_pay(&mixed_payment, atom).unwrap_err(); - assert_eq!(err, PaymentError::ExtraDenom("wei".to_string())); - } - - #[test] - fn must_pay_works() { - let atom: &str = "uatom"; - let no_payment = mock_info(SENDER, &[]); - let atom_payment = mock_info(SENDER, &coins(100, atom)); - let zero_payment = mock_info(SENDER, &coins(0, atom)); - let eth_payment = mock_info(SENDER, &coins(100, "wei")); - let mixed_payment = mock_info(SENDER, &[coin(50, atom), coin(120, "wei")]); - - let res = must_pay(&atom_payment, atom).unwrap(); - assert_eq!(res, Uint128::new(100)); - - let err = must_pay(&no_payment, atom).unwrap_err(); - assert_eq!(err, PaymentError::NoFunds {}); - - let err = must_pay(&zero_payment, atom).unwrap_err(); - assert_eq!(err, PaymentError::NoFunds {}); - - let err = must_pay(ð_payment, atom).unwrap_err(); - assert_eq!(err, PaymentError::MissingDenom(atom.to_string())); - - let err = must_pay(&mixed_payment, atom).unwrap_err(); - assert_eq!(err, PaymentError::MultipleDenoms {}); - } -} diff --git a/packages/utils/src/scheduled.rs b/packages/utils/src/scheduled.rs deleted file mode 100644 index f96794cdb..000000000 --- a/packages/utils/src/scheduled.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::Duration; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{BlockInfo, StdError, StdResult, Timestamp}; -use std::cmp::Ordering; -use std::fmt; -use std::ops::Add; - -/// Scheduled represents a point in time when an event happens. -/// It can compare with a BlockInfo and will return is_triggered() == true -/// once the condition is hit (and for every block in the future) -#[cw_serde] -#[derive(Copy)] -pub enum Scheduled { - /// AtHeight will schedule when `env.block.height` >= height - AtHeight(u64), - /// AtTime will schedule when `env.block.time` >= time - AtTime(Timestamp), -} - -impl fmt::Display for Scheduled { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Scheduled::AtHeight(height) => write!(f, "scheduled height: {}", height), - Scheduled::AtTime(time) => write!(f, "scheduled time: {}", time), - } - } -} - -impl Scheduled { - #[allow(dead_code)] - pub fn is_triggered(&self, block: &BlockInfo) -> bool { - match self { - Scheduled::AtHeight(height) => block.height >= *height, - Scheduled::AtTime(time) => block.time >= *time, - } - } -} - -impl Add for Scheduled { - type Output = StdResult; - - fn add(self, duration: Duration) -> StdResult { - match (self, duration) { - (Scheduled::AtTime(t), Duration::Time(delta)) => { - Ok(Scheduled::AtTime(t.plus_seconds(delta))) - } - (Scheduled::AtHeight(h), Duration::Height(delta)) => Ok(Scheduled::AtHeight(h + delta)), - _ => Err(StdError::generic_err("Cannot add height and time")), - } - } -} - -impl PartialOrd for Scheduled { - fn partial_cmp(&self, other: &Scheduled) -> Option { - match (self, other) { - // compare if both height or both time - (Scheduled::AtHeight(h1), Scheduled::AtHeight(h2)) => Some(h1.cmp(h2)), - (Scheduled::AtTime(t1), Scheduled::AtTime(t2)) => Some(t1.cmp(t2)), - _ => None, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn compare_schedules() { - // matching pairs - assert!(Scheduled::AtHeight(5) < Scheduled::AtHeight(10)); - assert!(Scheduled::AtHeight(8) > Scheduled::AtHeight(7)); - assert!( - Scheduled::AtTime(Timestamp::from_seconds(555)) - < Scheduled::AtTime(Timestamp::from_seconds(777)) - ); - assert!( - Scheduled::AtTime(Timestamp::from_seconds(86)) - < Scheduled::AtTime(Timestamp::from_seconds(100)) - ); - - // what happens for the uncomparables?? all compares are false - assert_eq!( - None, - Scheduled::AtTime(Timestamp::from_seconds(1000)).partial_cmp(&Scheduled::AtHeight(230)) - ); - assert_eq!( - Scheduled::AtTime(Timestamp::from_seconds(1000)).partial_cmp(&Scheduled::AtHeight(230)), - None - ); - assert_eq!( - Scheduled::AtTime(Timestamp::from_seconds(1000)).partial_cmp(&Scheduled::AtHeight(230)), - None - ); - assert!(!(Scheduled::AtTime(Timestamp::from_seconds(1000)) == Scheduled::AtHeight(230))); - } - - #[test] - fn schedule_addition() { - // height - let end = Scheduled::AtHeight(12345) + Duration::Height(400); - assert_eq!(end.unwrap(), Scheduled::AtHeight(12745)); - - // time - let end = Scheduled::AtTime(Timestamp::from_seconds(55544433)) + Duration::Time(40300); - assert_eq!( - end.unwrap(), - Scheduled::AtTime(Timestamp::from_seconds(55584733)) - ); - - // mismatched - let end = Scheduled::AtHeight(12345) + Duration::Time(1500); - end.unwrap_err(); - } -} diff --git a/packages/utils/src/threshold.rs b/packages/utils/src/threshold.rs deleted file mode 100644 index 312a298e0..000000000 --- a/packages/utils/src/threshold.rs +++ /dev/null @@ -1,337 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, StdError}; -use thiserror::Error; - -/// This defines the different ways tallies can happen. -/// -/// The total_weight used for calculating success as well as the weights of each -/// individual voter used in tallying should be snapshotted at the beginning of -/// the block at which the proposal starts (this is likely the responsibility of a -/// correct cw4 implementation). -/// See also `ThresholdResponse` in the cw3 spec. -#[cw_serde] -pub enum Threshold { - /// Declares that a fixed weight of Yes votes is needed to pass. - /// See `ThresholdResponse.AbsoluteCount` in the cw3 spec for details. - AbsoluteCount { weight: u64 }, - - /// Declares a percentage of the total weight that must cast Yes votes in order for - /// a proposal to pass. - /// See `ThresholdResponse.AbsolutePercentage` in the cw3 spec for details. - AbsolutePercentage { percentage: Decimal }, - - /// Declares a `quorum` of the total votes that must participate in the election in order - /// for the vote to be considered at all. - /// See `ThresholdResponse.ThresholdQuorum` in the cw3 spec for details. - ThresholdQuorum { threshold: Decimal, quorum: Decimal }, -} - -impl Threshold { - /// returns error if this is an unreachable value, - /// given a total weight of all members in the group - pub fn validate(&self, total_weight: u64) -> Result<(), ThresholdError> { - match self { - Threshold::AbsoluteCount { - weight: weight_needed, - } => { - if *weight_needed == 0 { - Err(ThresholdError::ZeroWeight {}) - } else if *weight_needed > total_weight { - Err(ThresholdError::UnreachableWeight {}) - } else { - Ok(()) - } - } - Threshold::AbsolutePercentage { - percentage: percentage_needed, - } => valid_threshold(percentage_needed), - Threshold::ThresholdQuorum { - threshold, - quorum: quroum, - } => { - valid_threshold(threshold)?; - valid_quorum(quroum) - } - } - } - - /// Creates a response from the saved data, just missing the total_weight info - pub fn to_response(&self, total_weight: u64) -> ThresholdResponse { - match self.clone() { - Threshold::AbsoluteCount { weight } => ThresholdResponse::AbsoluteCount { - weight, - total_weight, - }, - Threshold::AbsolutePercentage { percentage } => ThresholdResponse::AbsolutePercentage { - percentage, - total_weight, - }, - Threshold::ThresholdQuorum { threshold, quorum } => { - ThresholdResponse::ThresholdQuorum { - threshold, - quorum, - total_weight, - } - } - } - } -} - -/// Asserts that the 0.5 < percent <= 1.0 -fn valid_threshold(percent: &Decimal) -> Result<(), ThresholdError> { - if *percent > Decimal::percent(100) || *percent < Decimal::percent(50) { - Err(ThresholdError::InvalidThreshold {}) - } else { - Ok(()) - } -} - -/// Asserts that the 0.5 < percent <= 1.0 -fn valid_quorum(percent: &Decimal) -> Result<(), ThresholdError> { - if percent.is_zero() { - Err(ThresholdError::ZeroQuorumThreshold {}) - } else if *percent > Decimal::one() { - Err(ThresholdError::UnreachableQuorumThreshold {}) - } else { - Ok(()) - } -} - -/// This defines the different ways tallies can happen. -/// Every contract should support a subset of these, ideally all. -/// -/// The total_weight used for calculating success as well as the weights of each -/// individual voter used in tallying should be snapshotted at the beginning of -/// the block at which the proposal starts (this is likely the responsibility of a -/// correct cw4 implementation). -#[cw_serde] -pub enum ThresholdResponse { - /// Declares that a fixed weight of yes votes is needed to pass. - /// It does not matter how many no votes are cast, or how many do not vote, - /// as long as `weight` yes votes are cast. - /// - /// This is the simplest format and usually suitable for small multisigs of trusted parties, - /// like 3 of 5. (weight: 3, total_weight: 5) - /// - /// A proposal of this type can pass early as soon as the needed weight of yes votes has been cast. - AbsoluteCount { weight: u64, total_weight: u64 }, - - /// Declares a percentage of the total weight that must cast Yes votes, in order for - /// a proposal to pass. The passing weight is computed over the total weight minus the weight of the - /// abstained votes. - /// - /// This is useful for similar circumstances as `AbsoluteCount`, where we have a relatively - /// small set of voters, and participation is required. - /// It is understood that if the voting set (group) changes between different proposals that - /// refer to the same group, each proposal will work with a different set of voter weights - /// (the ones snapshotted at proposal creation), and the passing weight for each proposal - /// will be computed based on the absolute percentage, times the total weights of the members - /// at the time of each proposal creation. - /// - /// Example: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. - /// This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the - /// `total_weight` of the group has increased to 9. That proposal will then automatically - /// require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`. - AbsolutePercentage { - percentage: Decimal, - total_weight: u64, - }, - - /// In addition to a `threshold`, declares a `quorum` of the total votes that must participate - /// in the election in order for the vote to be considered at all. Within the votes that - /// were cast, it requires `threshold` votes in favor. That is calculated by ignoring - /// the Abstain votes (they count towards `quorum`, but do not influence `threshold`). - /// That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider - /// if the proposal was passed. - /// - /// It is rather difficult for a proposal of this type to pass early. That can only happen if - /// the required quorum has been already met, and there are already enough Yes votes for the - /// proposal to pass. - /// - /// 30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% - /// (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting - /// no => 30% yes + 50% no). Once the voting period has passed with no additional votes, - /// that same proposal would be considered successful if quorum <= 60% and threshold <= 75% - /// (percent in favor if we ignore abstain votes). - /// - /// This type is more common in general elections, where participation is often expected to - /// be low, and `AbsolutePercentage` would either be too high to pass anything, - /// or allow low percentages to pass, independently of if there was high participation in the - /// election or not. - ThresholdQuorum { - threshold: Decimal, - quorum: Decimal, - total_weight: u64, - }, -} - -#[derive(Error, Debug, PartialEq)] -pub enum ThresholdError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Invalid voting threshold percentage, must be in the 0.5-1.0 range")] - InvalidThreshold {}, - - #[error("Required quorum threshold cannot be zero")] - ZeroQuorumThreshold {}, - - #[error("Not possible to reach required quorum threshold")] - UnreachableQuorumThreshold {}, - - #[error("Required weight cannot be zero")] - ZeroWeight {}, - - #[error("Not possible to reach required (passing) weight")] - UnreachableWeight {}, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn validate_quorum_percentage() { - // TODO: test the error messages - - // 0 is never a valid percentage - let err = valid_quorum(&Decimal::zero()).unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::ZeroQuorumThreshold {}.to_string() - ); - - // 100% is - valid_quorum(&Decimal::one()).unwrap(); - - // 101% is not - let err = valid_quorum(&Decimal::percent(101)).unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::UnreachableQuorumThreshold {}.to_string() - ); - // not 100.1% - let err = valid_quorum(&Decimal::permille(1001)).unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::UnreachableQuorumThreshold {}.to_string() - ); - } - - #[test] - fn validate_threshold_percentage() { - // other values in between 0.5 and 1 are valid - valid_threshold(&Decimal::percent(51)).unwrap(); - valid_threshold(&Decimal::percent(67)).unwrap(); - valid_threshold(&Decimal::percent(99)).unwrap(); - let err = valid_threshold(&Decimal::percent(101)).unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::InvalidThreshold {}.to_string() - ); - } - - #[test] - fn validate_threshold() { - // absolute count ensures 0 < required <= total_weight - let err = Threshold::AbsoluteCount { weight: 0 } - .validate(5) - .unwrap_err(); - // TODO: remove to_string() when PartialEq implemented - assert_eq!(err.to_string(), ThresholdError::ZeroWeight {}.to_string()); - let err = Threshold::AbsoluteCount { weight: 6 } - .validate(5) - .unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::UnreachableWeight {}.to_string() - ); - - Threshold::AbsoluteCount { weight: 1 }.validate(5).unwrap(); - Threshold::AbsoluteCount { weight: 5 }.validate(5).unwrap(); - - // AbsolutePercentage just enforces valid_percentage (tested above) - let err = Threshold::AbsolutePercentage { - percentage: Decimal::zero(), - } - .validate(5) - .unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::InvalidThreshold {}.to_string() - ); - Threshold::AbsolutePercentage { - percentage: Decimal::percent(51), - } - .validate(5) - .unwrap(); - - // Quorum enforces both valid just enforces valid_percentage (tested above) - Threshold::ThresholdQuorum { - threshold: Decimal::percent(51), - quorum: Decimal::percent(40), - } - .validate(5) - .unwrap(); - let err = Threshold::ThresholdQuorum { - threshold: Decimal::percent(101), - quorum: Decimal::percent(40), - } - .validate(5) - .unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::InvalidThreshold {}.to_string() - ); - let err = Threshold::ThresholdQuorum { - threshold: Decimal::percent(51), - quorum: Decimal::percent(0), - } - .validate(5) - .unwrap_err(); - assert_eq!( - err.to_string(), - ThresholdError::ZeroQuorumThreshold {}.to_string() - ); - } - - #[test] - fn threshold_response() { - let total_weight: u64 = 100; - - let res = Threshold::AbsoluteCount { weight: 42 }.to_response(total_weight); - assert_eq!( - res, - ThresholdResponse::AbsoluteCount { - weight: 42, - total_weight - } - ); - - let res = Threshold::AbsolutePercentage { - percentage: Decimal::percent(51), - } - .to_response(total_weight); - assert_eq!( - res, - ThresholdResponse::AbsolutePercentage { - percentage: Decimal::percent(51), - total_weight - } - ); - - let res = Threshold::ThresholdQuorum { - threshold: Decimal::percent(66), - quorum: Decimal::percent(50), - } - .to_response(total_weight); - assert_eq!( - res, - ThresholdResponse::ThresholdQuorum { - threshold: Decimal::percent(66), - quorum: Decimal::percent(50), - total_weight - } - ); - } -} From 241959f5302d490abdffb1400cd153536b7e5cb7 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 18 Oct 2022 22:25:37 +0200 Subject: [PATCH 2/4] Remove cw1155 stuff --- Cargo.lock | 26 - contracts/cw1155-base/.cargo/config | 5 - contracts/cw1155-base/Cargo.toml | 29 - contracts/cw1155-base/README.md | 24 - contracts/cw1155-base/src/bin/schema.rs | 12 - contracts/cw1155-base/src/contract.rs | 109 ---- contracts/cw1155-base/src/error.rs | 14 - contracts/cw1155-base/src/execute.rs | 312 ----------- contracts/cw1155-base/src/helpers.rs | 30 - contracts/cw1155-base/src/lib.rs | 21 - contracts/cw1155-base/src/msg.rs | 9 - contracts/cw1155-base/src/query.rs | 130 ----- contracts/cw1155-base/src/state.rs | 13 - contracts/cw1155-base/src/tests.rs | 694 ------------------------ packages/cw1155/.cargo/config | 4 - packages/cw1155/Cargo.toml | 16 - packages/cw1155/README.md | 104 ---- packages/cw1155/src/bin/schema.rs | 22 - packages/cw1155/src/event.rs | 56 -- packages/cw1155/src/lib.rs | 30 - packages/cw1155/src/msg.rs | 67 --- packages/cw1155/src/query.rs | 94 ---- packages/cw1155/src/receiver.rs | 73 --- 23 files changed, 1894 deletions(-) delete mode 100644 contracts/cw1155-base/.cargo/config delete mode 100644 contracts/cw1155-base/Cargo.toml delete mode 100644 contracts/cw1155-base/README.md delete mode 100644 contracts/cw1155-base/src/bin/schema.rs delete mode 100644 contracts/cw1155-base/src/contract.rs delete mode 100644 contracts/cw1155-base/src/error.rs delete mode 100644 contracts/cw1155-base/src/execute.rs delete mode 100644 contracts/cw1155-base/src/helpers.rs delete mode 100644 contracts/cw1155-base/src/lib.rs delete mode 100644 contracts/cw1155-base/src/msg.rs delete mode 100644 contracts/cw1155-base/src/query.rs delete mode 100644 contracts/cw1155-base/src/state.rs delete mode 100644 contracts/cw1155-base/src/tests.rs delete mode 100644 packages/cw1155/.cargo/config delete mode 100644 packages/cw1155/Cargo.toml delete mode 100644 packages/cw1155/README.md delete mode 100644 packages/cw1155/src/bin/schema.rs delete mode 100644 packages/cw1155/src/event.rs delete mode 100644 packages/cw1155/src/lib.rs delete mode 100644 packages/cw1155/src/msg.rs delete mode 100644 packages/cw1155/src/query.rs delete mode 100644 packages/cw1155/src/receiver.rs diff --git a/Cargo.lock b/Cargo.lock index 3527aa4c4..cc4a30196 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,32 +291,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw1155" -version = "0.16.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils", - "schemars", - "serde", -] - -[[package]] -name = "cw1155-base" -version = "0.16.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw1155", - "cw2 0.16.0", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw2" version = "0.16.0" diff --git a/contracts/cw1155-base/.cargo/config b/contracts/cw1155-base/.cargo/config deleted file mode 100644 index de2d36ac7..000000000 --- a/contracts/cw1155-base/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --lib --target wasm32-unknown-unknown" -wasm-debug = "build --lib --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --bin schema" diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml deleted file mode 100644 index 1519faf63..000000000 --- a/contracts/cw1155-base/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "cw1155-base" -version = "0.16.0" -authors = ["Huang Yi "] -edition = "2018" -description = "Basic implementation of a CosmWasm-1155 compliant token" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-plus" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -cosmwasm-schema = { version = "1.1.0" } -cw-utils = "0.16.0" -cw2 = { path = "../../packages/cw2", version = "0.16.0" } -cw1155 = { path = "../../packages/cw1155", version = "0.16.0" } -cw-storage-plus = "0.16.0" -cosmwasm-std = { version = "1.1.0" } -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.20" } diff --git a/contracts/cw1155-base/README.md b/contracts/cw1155-base/README.md deleted file mode 100644 index 6da195595..000000000 --- a/contracts/cw1155-base/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# CW1155 Basic - -This is a basic implementation of a cw1155 contract. -It implements the [CW1155 spec](../../packages/cw1155/README.md) and manages multiple tokens -(fungible or non-fungible) under one contract. - -## Instantiation - -To create it, you must pass in a `minter` address. - -```rust -#[cw_serde] -pub struct InstantiateMsg { - /// The minter is the only one who can create new tokens. - /// This is designed for a base token platform that is controlled by an external program or - /// contract. - pub minter: String, -} -``` - -## Messages - -All other messages and queries are defined by the -[CW1155 spec](../../packages/cw1155/README.md). Please refer to it for more info. \ No newline at end of file diff --git a/contracts/cw1155-base/src/bin/schema.rs b/contracts/cw1155-base/src/bin/schema.rs deleted file mode 100644 index a4c4d13d2..000000000 --- a/contracts/cw1155-base/src/bin/schema.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmwasm_schema::write_api; - -use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; -use cw1155_base::msg::InstantiateMsg; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: Cw1155ExecuteMsg, - query: Cw1155QueryMsg, - } -} diff --git a/contracts/cw1155-base/src/contract.rs b/contracts/cw1155-base/src/contract.rs deleted file mode 100644 index eab6b12e8..000000000 --- a/contracts/cw1155-base/src/contract.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::{error::ContractError, execute, msg::InstantiateMsg, query, state::MINTER}; -use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; -use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; -use cw2::set_contract_version; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:cw1155-base"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let minter = deps.api.addr_validate(&msg.minter)?; - MINTER.save(deps.storage, &minter)?; - Ok(Response::default()) -} - -/// To mitigate clippy::too_many_arguments warning -pub struct ExecuteEnv<'a> { - pub deps: DepsMut<'a>, - pub env: Env, - pub info: MessageInfo, -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw1155ExecuteMsg, -) -> Result { - let env = ExecuteEnv { deps, env, info }; - match msg { - Cw1155ExecuteMsg::SendFrom { - from, - to, - token_id, - value, - msg, - } => execute::send_from(env, from, to, token_id, value, msg), - Cw1155ExecuteMsg::BatchSendFrom { - from, - to, - batch, - msg, - } => execute::batch_send_from(env, from, to, batch, msg), - Cw1155ExecuteMsg::Mint { - to, - token_id, - value, - msg, - } => execute::mint(env, to, token_id, value, msg), - Cw1155ExecuteMsg::BatchMint { to, batch, msg } => execute::batch_mint(env, to, batch, msg), - Cw1155ExecuteMsg::Burn { - from, - token_id, - value, - } => execute::burn(env, from, token_id, value), - Cw1155ExecuteMsg::BatchBurn { from, batch } => execute::batch_burn(env, from, batch), - Cw1155ExecuteMsg::ApproveAll { operator, expires } => { - execute::approve_all(env, operator, expires) - } - Cw1155ExecuteMsg::RevokeAll { operator } => execute::revoke_all(env, operator), - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { - match msg { - Cw1155QueryMsg::Balance { owner, token_id } => { - to_binary(&query::balance(deps, owner, token_id)?) - } - Cw1155QueryMsg::BatchBalance { owner, token_ids } => { - to_binary(&query::batch_balance(deps, owner, token_ids)?) - } - Cw1155QueryMsg::IsApprovedForAll { owner, operator } => { - to_binary(&query::is_approved_for_all(deps, env, owner, operator)?) - } - Cw1155QueryMsg::ApprovedForAll { - owner, - include_expired, - start_after, - limit, - } => to_binary(&query::approved_for_all( - deps, - env, - owner, - include_expired.unwrap_or(false), - start_after, - limit, - )?), - Cw1155QueryMsg::TokenInfo { token_id } => to_binary(&query::token_info(deps, token_id)?), - Cw1155QueryMsg::Tokens { - owner, - start_after, - limit, - } => to_binary(&query::tokens(deps, owner, start_after, limit)?), - Cw1155QueryMsg::AllTokens { start_after, limit } => { - to_binary(&query::all_tokens(deps, start_after, limit)?) - } - } -} diff --git a/contracts/cw1155-base/src/error.rs b/contracts/cw1155-base/src/error.rs deleted file mode 100644 index 109695593..000000000 --- a/contracts/cw1155-base/src/error.rs +++ /dev/null @@ -1,14 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Expired")] - Expired {}, -} diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs deleted file mode 100644 index ab5a56d78..000000000 --- a/contracts/cw1155-base/src/execute.rs +++ /dev/null @@ -1,312 +0,0 @@ -use cosmwasm_std::{Addr, Binary, DepsMut, Response, StdResult, SubMsg, Uint128}; -use cw1155::{ApproveAllEvent, Cw1155BatchReceiveMsg, Cw1155ReceiveMsg, TokenId, TransferEvent}; -use cw_utils::{Event, Expiration}; - -use crate::{ - contract::ExecuteEnv, - helpers::guard_can_approve, - state::{APPROVES, BALANCES, MINTER, TOKENS}, - ContractError, -}; - -/// When from is None: mint new coins -/// When to is None: burn coins -/// When both are None: no token balance is changed, pointless but valid -/// -/// Make sure permissions are checked before calling this. -fn transfer_inner<'a>( - deps: &'a mut DepsMut, - from: Option<&'a Addr>, - to: Option<&'a Addr>, - token_id: &'a str, - amount: Uint128, -) -> Result, ContractError> { - if let Some(from_addr) = from { - BALANCES.update( - deps.storage, - (from_addr, token_id), - |balance: Option| -> StdResult<_> { - Ok(balance.unwrap_or_default().checked_sub(amount)?) - }, - )?; - } - - if let Some(to_addr) = to { - BALANCES.update( - deps.storage, - (to_addr, token_id), - |balance: Option| -> StdResult<_> { - Ok(balance.unwrap_or_default().checked_add(amount)?) - }, - )?; - } - - Ok(TransferEvent { - from: from.map(|x| x.as_ref()), - to: to.map(|x| x.as_ref()), - token_id, - amount, - }) -} - -pub fn send_from( - env: ExecuteEnv, - from: String, - to: String, - token_id: TokenId, - amount: Uint128, - msg: Option, -) -> Result { - let from_addr = env.deps.api.addr_validate(&from)?; - let to_addr = env.deps.api.addr_validate(&to)?; - - let ExecuteEnv { - mut deps, - env, - info, - } = env; - - guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; - - let mut rsp = Response::default(); - - let event = transfer_inner( - &mut deps, - Some(&from_addr), - Some(&to_addr), - &token_id, - amount, - )?; - event.add_attributes(&mut rsp); - - if let Some(msg) = msg { - rsp.messages = vec![SubMsg::new( - Cw1155ReceiveMsg { - operator: info.sender.to_string(), - from: Some(from), - amount, - token_id: token_id.clone(), - msg, - } - .into_cosmos_msg(to)?, - )] - } - - Ok(rsp) -} - -pub fn mint( - env: ExecuteEnv, - to: String, - token_id: TokenId, - amount: Uint128, - msg: Option, -) -> Result { - let ExecuteEnv { mut deps, info, .. } = env; - - let to_addr = deps.api.addr_validate(&to)?; - - if info.sender != MINTER.load(deps.storage)? { - return Err(ContractError::Unauthorized {}); - } - - let mut rsp = Response::default(); - - let event = transfer_inner(&mut deps, None, Some(&to_addr), &token_id, amount)?; - event.add_attributes(&mut rsp); - - if let Some(msg) = msg { - rsp.messages = vec![SubMsg::new( - Cw1155ReceiveMsg { - operator: info.sender.to_string(), - from: None, - amount, - token_id: token_id.clone(), - msg, - } - .into_cosmos_msg(to)?, - )] - } - - // insert if not exist - if !TOKENS.has(deps.storage, &token_id) { - // we must save some valid data here - TOKENS.save(deps.storage, &token_id, &String::new())?; - } - - Ok(rsp) -} - -pub fn burn( - env: ExecuteEnv, - from: String, - token_id: TokenId, - amount: Uint128, -) -> Result { - let ExecuteEnv { - mut deps, - info, - env, - } = env; - - let from_addr = deps.api.addr_validate(&from)?; - - // whoever can transfer these tokens can burn - guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; - - let mut rsp = Response::default(); - let event = transfer_inner(&mut deps, Some(&from_addr), None, &token_id, amount)?; - event.add_attributes(&mut rsp); - Ok(rsp) -} - -pub fn batch_send_from( - env: ExecuteEnv, - from: String, - to: String, - batch: Vec<(TokenId, Uint128)>, - msg: Option, -) -> Result { - let ExecuteEnv { - mut deps, - env, - info, - } = env; - - let from_addr = deps.api.addr_validate(&from)?; - let to_addr = deps.api.addr_validate(&to)?; - - guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; - - let mut rsp = Response::default(); - for (token_id, amount) in batch.iter() { - let event = transfer_inner( - &mut deps, - Some(&from_addr), - Some(&to_addr), - token_id, - *amount, - )?; - event.add_attributes(&mut rsp); - } - - if let Some(msg) = msg { - rsp.messages = vec![SubMsg::new( - Cw1155BatchReceiveMsg { - operator: info.sender.to_string(), - from: Some(from), - batch, - msg, - } - .into_cosmos_msg(to)?, - )] - }; - - Ok(rsp) -} - -pub fn batch_mint( - env: ExecuteEnv, - to: String, - batch: Vec<(TokenId, Uint128)>, - msg: Option, -) -> Result { - let ExecuteEnv { mut deps, info, .. } = env; - if info.sender != MINTER.load(deps.storage)? { - return Err(ContractError::Unauthorized {}); - } - - let to_addr = deps.api.addr_validate(&to)?; - - let mut rsp = Response::default(); - - for (token_id, amount) in batch.iter() { - let event = transfer_inner(&mut deps, None, Some(&to_addr), token_id, *amount)?; - event.add_attributes(&mut rsp); - - // insert if not exist - if !TOKENS.has(deps.storage, token_id) { - // we must save some valid data here - TOKENS.save(deps.storage, token_id, &String::new())?; - } - } - - if let Some(msg) = msg { - rsp.messages = vec![SubMsg::new( - Cw1155BatchReceiveMsg { - operator: info.sender.to_string(), - from: None, - batch, - msg, - } - .into_cosmos_msg(to)?, - )] - }; - - Ok(rsp) -} - -pub fn batch_burn( - env: ExecuteEnv, - from: String, - batch: Vec<(TokenId, Uint128)>, -) -> Result { - let ExecuteEnv { - mut deps, - info, - env, - } = env; - - let from_addr = deps.api.addr_validate(&from)?; - - guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; - - let mut rsp = Response::default(); - for (token_id, amount) in batch.into_iter() { - let event = transfer_inner(&mut deps, Some(&from_addr), None, &token_id, amount)?; - event.add_attributes(&mut rsp); - } - Ok(rsp) -} - -pub fn approve_all( - env: ExecuteEnv, - operator: String, - expires: Option, -) -> Result { - let ExecuteEnv { deps, info, env } = env; - - // reject expired data as invalid - let expires = expires.unwrap_or_default(); - if expires.is_expired(&env.block) { - return Err(ContractError::Expired {}); - } - - // set the operator for us - let operator_addr = deps.api.addr_validate(&operator)?; - APPROVES.save(deps.storage, (&info.sender, &operator_addr), &expires)?; - - let mut rsp = Response::default(); - ApproveAllEvent { - sender: info.sender.as_ref(), - operator: &operator, - approved: true, - } - .add_attributes(&mut rsp); - Ok(rsp) -} - -pub fn revoke_all(env: ExecuteEnv, operator: String) -> Result { - let ExecuteEnv { deps, info, .. } = env; - let operator_addr = deps.api.addr_validate(&operator)?; - APPROVES.remove(deps.storage, (&info.sender, &operator_addr)); - - let mut rsp = Response::default(); - ApproveAllEvent { - sender: info.sender.as_ref(), - operator: &operator, - approved: false, - } - .add_attributes(&mut rsp); - Ok(rsp) -} diff --git a/contracts/cw1155-base/src/helpers.rs b/contracts/cw1155-base/src/helpers.rs deleted file mode 100644 index 638054f3e..000000000 --- a/contracts/cw1155-base/src/helpers.rs +++ /dev/null @@ -1,30 +0,0 @@ -use cosmwasm_std::{Addr, Deps, Env, StdResult}; - -use crate::{state::APPROVES, ContractError}; - -/// returns true if the sender can execute approve or reject on the contract -pub fn check_can_approve(deps: Deps, env: &Env, owner: &Addr, operator: &Addr) -> StdResult { - // owner can approve - if owner == operator { - return Ok(true); - } - // operator can approve - let op = APPROVES.may_load(deps.storage, (owner, operator))?; - Ok(match op { - Some(ex) => !ex.is_expired(&env.block), - None => false, - }) -} - -pub fn guard_can_approve( - deps: Deps, - env: &Env, - owner: &Addr, - operator: &Addr, -) -> Result<(), ContractError> { - if !check_can_approve(deps, env, owner, operator)? { - Err(ContractError::Unauthorized {}) - } else { - Ok(()) - } -} diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs deleted file mode 100644 index 809e67e24..000000000 --- a/contracts/cw1155-base/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -/*! -This is a basic implementation of a cw1155 contract. -It implements the [CW1155 spec](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw1155/README.md) and manages multiple tokens -(fungible or non-fungible) under one contract. - -For more information on this contract, please check out the -[README](https://github.com/CosmWasm/cw-plus/blob/main/contracts/cw1155-base/README.md). -*/ - -pub mod contract; -mod error; -pub mod execute; -pub mod helpers; -pub mod msg; -pub mod query; -pub mod state; - -pub use crate::error::ContractError; - -#[cfg(test)] -mod tests; diff --git a/contracts/cw1155-base/src/msg.rs b/contracts/cw1155-base/src/msg.rs deleted file mode 100644 index 07e718d5b..000000000 --- a/contracts/cw1155-base/src/msg.rs +++ /dev/null @@ -1,9 +0,0 @@ -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub struct InstantiateMsg { - /// The minter is the only one who can create new tokens. - /// This is designed for a base token platform that is controlled by an external program or - /// contract. - pub minter: String, -} diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs deleted file mode 100644 index 0b89b0cba..000000000 --- a/contracts/cw1155-base/src/query.rs +++ /dev/null @@ -1,130 +0,0 @@ -use cosmwasm_std::{Addr, Deps, Env, Order, StdResult}; -use cw1155::{ - ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, IsApprovedForAllResponse, - TokenInfoResponse, TokensResponse, -}; -use cw_storage_plus::Bound; -use cw_utils::{maybe_addr, Expiration}; - -use crate::{ - helpers::check_can_approve, - state::{APPROVES, BALANCES, TOKENS}, -}; - -pub const DEFAULT_LIMIT: u32 = 10; -pub const MAX_LIMIT: u32 = 30; - -pub fn balance(deps: Deps, owner: String, token_id: String) -> StdResult { - let owner = deps.api.addr_validate(&owner)?; - - let balance = BALANCES - .may_load(deps.storage, (&owner, &token_id))? - .unwrap_or_default(); - - Ok(BalanceResponse { balance }) -} - -pub fn batch_balance( - deps: Deps, - owner: String, - token_ids: Vec, -) -> StdResult { - let owner = deps.api.addr_validate(&owner)?; - - let balances = token_ids - .into_iter() - .map(|token_id| -> StdResult<_> { - Ok(BALANCES - .may_load(deps.storage, (&owner, &token_id))? - .unwrap_or_default()) - }) - .collect::>()?; - - Ok(BatchBalanceResponse { balances }) -} - -fn build_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { - item.map(|(addr, expires)| cw1155::Approval { - spender: addr.into(), - expires, - }) -} - -pub fn approved_for_all( - deps: Deps, - env: Env, - owner: String, - include_expired: bool, - start_after: Option, - limit: Option, -) -> StdResult { - let owner = deps.api.addr_validate(&owner)?; - let start_after = maybe_addr(deps.api, start_after)?; - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.as_ref().map(Bound::exclusive); - - let operators = APPROVES - .prefix(&owner) - .range(deps.storage, start, None, Order::Ascending) - .filter(|r| include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block)) - .take(limit) - .map(build_approval) - .collect::>()?; - - Ok(ApprovedForAllResponse { operators }) -} - -pub fn token_info(deps: Deps, token_id: String) -> StdResult { - let url = TOKENS.load(deps.storage, &token_id)?; - - Ok(TokenInfoResponse { url }) -} - -pub fn is_approved_for_all( - deps: Deps, - env: Env, - owner: String, - operator: String, -) -> StdResult { - let owner_addr = deps.api.addr_validate(&owner)?; - let operator_addr = deps.api.addr_validate(&operator)?; - - let approved = check_can_approve(deps, &env, &owner_addr, &operator_addr)?; - - Ok(IsApprovedForAllResponse { approved }) -} - -pub fn tokens( - deps: Deps, - owner: String, - start_after: Option, - limit: Option, -) -> StdResult { - let owner = deps.api.addr_validate(&owner)?; - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); - - let tokens = BALANCES - .prefix(&owner) - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>()?; - - Ok(TokensResponse { tokens }) -} - -pub fn all_tokens( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); - - let tokens = TOKENS - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>()?; - - Ok(TokensResponse { tokens }) -} diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs deleted file mode 100644 index 1238f5094..000000000 --- a/contracts/cw1155-base/src/state.rs +++ /dev/null @@ -1,13 +0,0 @@ -use cosmwasm_std::{Addr, Uint128}; -use cw1155::Expiration; -use cw_storage_plus::{Item, Map}; - -/// Store the minter address who have permission to mint new tokens. -pub const MINTER: Item = Item::new("minter"); -/// Store the balance map, `(owner, token_id) -> balance` -pub const BALANCES: Map<(&Addr, &str), Uint128> = Map::new("balances"); -/// Store the approval status, `(owner, spender) -> expiration` -pub const APPROVES: Map<(&Addr, &Addr), Expiration> = Map::new("approves"); -/// Store the tokens metadata url, also supports enumerating tokens, -/// An entry for token_id must exist as long as there's tokens in circulation. -pub const TOKENS: Map<&str, String> = Map::new("tokens"); diff --git a/contracts/cw1155-base/src/tests.rs b/contracts/cw1155-base/src/tests.rs deleted file mode 100644 index 6202bdef8..000000000 --- a/contracts/cw1155-base/src/tests.rs +++ /dev/null @@ -1,694 +0,0 @@ -use crate::{ - contract::{execute, instantiate, query}, - msg::InstantiateMsg, - ContractError, -}; -use cosmwasm_std::{ - testing::{mock_dependencies, mock_env, mock_info}, - to_binary, Binary, OverflowError, Response, StdError, -}; -use cw1155::{ - ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, Cw1155BatchReceiveMsg, - Cw1155ExecuteMsg, Cw1155QueryMsg, Cw1155ReceiveMsg, IsApprovedForAllResponse, - TokenInfoResponse, TokensResponse, -}; -use cw_utils::Expiration; - -#[test] -fn check_transfers() { - // A long test case that try to cover as many cases as possible. - // Summary of what it does: - // - try mint without permission, fail - // - mint with permission, success - // - query balance of receipant, success - // - try transfer without approval, fail - // - approve - // - transfer again, success - // - query balance of transfer participants - // - batch mint token2 and token3, success - // - try batch transfer without approval, fail - // - approve and try batch transfer again, success - // - batch query balances - // - user1 revoke approval to minter - // - query approval status - // - minter try to transfer, fail - // - user1 burn token1 - // - user1 batch burn token2 and token3 - let token1 = "token1".to_owned(); - let token2 = "token2".to_owned(); - let token3 = "token3".to_owned(); - let minter = String::from("minter"); - let user1 = String::from("user1"); - let user2 = String::from("user2"); - - let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - minter: minter.clone(), - }; - let res = instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // invalid mint, user1 don't mint permission - let mint_msg = Cw1155ExecuteMsg::Mint { - to: user1.clone(), - token_id: token1.clone(), - value: 1u64.into(), - msg: None, - }; - assert!(matches!( - execute( - deps.as_mut(), - mock_env(), - mock_info(user1.as_ref(), &[]), - mint_msg.clone(), - ), - Err(ContractError::Unauthorized {}) - )); - - // valid mint - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - mint_msg, - ) - .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("to", &user1) - ); - - // query balance - assert_eq!( - to_binary(&BalanceResponse { - balance: 1u64.into() - }), - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::Balance { - owner: user1.clone(), - token_id: token1.clone(), - } - ), - ); - - let transfer_msg = Cw1155ExecuteMsg::SendFrom { - from: user1.clone(), - to: user2.clone(), - token_id: token1.clone(), - value: 1u64.into(), - msg: None, - }; - - // not approved yet - assert!(matches!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - transfer_msg.clone(), - ), - Err(ContractError::Unauthorized {}) - )); - - // approve - execute( - deps.as_mut(), - mock_env(), - mock_info(user1.as_ref(), &[]), - Cw1155ExecuteMsg::ApproveAll { - operator: minter.clone(), - expires: None, - }, - ) - .unwrap(); - - // transfer - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - transfer_msg, - ) - .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - .add_attribute("to", &user2) - ); - - // query balance - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::Balance { - owner: user2.clone(), - token_id: token1.clone(), - } - ), - to_binary(&BalanceResponse { - balance: 1u64.into() - }), - ); - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::Balance { - owner: user1.clone(), - token_id: token1.clone(), - } - ), - to_binary(&BalanceResponse { - balance: 0u64.into() - }), - ); - - // batch mint token2 and token3 - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::BatchMint { - to: user2.clone(), - batch: vec![(token2.clone(), 1u64.into()), (token3.clone(), 1u64.into())], - msg: None - }, - ) - .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token2) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("to", &user2) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token3) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("to", &user2) - ); - - // invalid batch transfer, (user2 not approved yet) - let batch_transfer_msg = Cw1155ExecuteMsg::BatchSendFrom { - from: user2.clone(), - to: user1.clone(), - batch: vec![ - (token1.clone(), 1u64.into()), - (token2.clone(), 1u64.into()), - (token3.clone(), 1u64.into()), - ], - msg: None, - }; - assert!(matches!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - batch_transfer_msg.clone(), - ), - Err(ContractError::Unauthorized {}), - )); - - // user2 approve - execute( - deps.as_mut(), - mock_env(), - mock_info(user2.as_ref(), &[]), - Cw1155ExecuteMsg::ApproveAll { - operator: minter.clone(), - expires: None, - }, - ) - .unwrap(); - - // valid batch transfer - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - batch_transfer_msg, - ) - .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user2) - .add_attribute("to", &user1) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token2) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user2) - .add_attribute("to", &user1) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token3) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user2) - .add_attribute("to", &user1) - ); - - // batch query - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::BatchBalance { - owner: user1.clone(), - token_ids: vec![token1.clone(), token2.clone(), token3.clone()], - } - ), - to_binary(&BatchBalanceResponse { - balances: vec![1u64.into(), 1u64.into(), 1u64.into()] - }), - ); - - // user1 revoke approval - execute( - deps.as_mut(), - mock_env(), - mock_info(user1.as_ref(), &[]), - Cw1155ExecuteMsg::RevokeAll { - operator: minter.clone(), - }, - ) - .unwrap(); - - // query approval status - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::IsApprovedForAll { - owner: user1.clone(), - operator: minter.clone(), - } - ), - to_binary(&IsApprovedForAllResponse { approved: false }), - ); - - // tranfer without approval - assert!(matches!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::SendFrom { - from: user1.clone(), - to: user2, - token_id: token1.clone(), - value: 1u64.into(), - msg: None, - }, - ), - Err(ContractError::Unauthorized {}) - )); - - // burn token1 - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(user1.as_ref(), &[]), - Cw1155ExecuteMsg::Burn { - from: user1.clone(), - token_id: token1.clone(), - value: 1u64.into(), - } - ) - .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - ); - - // burn them all - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(user1.as_ref(), &[]), - Cw1155ExecuteMsg::BatchBurn { - from: user1.clone(), - batch: vec![(token2.clone(), 1u64.into()), (token3.clone(), 1u64.into())] - } - ) - .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token2) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token3) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - ); -} - -#[test] -fn check_send_contract() { - let receiver = String::from("receive_contract"); - let minter = String::from("minter"); - let user1 = String::from("user1"); - let token1 = "token1".to_owned(); - let token2 = "token2".to_owned(); - let dummy_msg = Binary::default(); - - let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - minter: minter.clone(), - }; - let res = instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg).unwrap(); - assert_eq!(0, res.messages.len()); - - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::Mint { - to: user1.clone(), - token_id: token2.clone(), - value: 1u64.into(), - msg: None, - }, - ) - .unwrap(); - - // mint to contract - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::Mint { - to: receiver.clone(), - token_id: token1.clone(), - value: 1u64.into(), - msg: Some(dummy_msg.clone()), - }, - ) - .unwrap(), - Response::new() - .add_message( - Cw1155ReceiveMsg { - operator: minter.clone(), - from: None, - amount: 1u64.into(), - token_id: token1.clone(), - msg: dummy_msg.clone(), - } - .into_cosmos_msg(receiver.clone()) - .unwrap() - ) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("to", &receiver) - ); - - // BatchSendFrom - assert_eq!( - execute( - deps.as_mut(), - mock_env(), - mock_info(user1.as_ref(), &[]), - Cw1155ExecuteMsg::BatchSendFrom { - from: user1.clone(), - to: receiver.clone(), - batch: vec![(token2.clone(), 1u64.into())], - msg: Some(dummy_msg.clone()), - }, - ) - .unwrap(), - Response::new() - .add_message( - Cw1155BatchReceiveMsg { - operator: user1.clone(), - from: Some(user1.clone()), - batch: vec![(token2.clone(), 1u64.into())], - msg: dummy_msg, - } - .into_cosmos_msg(receiver.clone()) - .unwrap() - ) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token2) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - .add_attribute("to", &receiver) - ); -} - -#[test] -fn check_queries() { - // mint multiple types of tokens, and query them - // grant approval to multiple operators, and query them - let tokens = (0..10).map(|i| format!("token{}", i)).collect::>(); - let users = (0..10).map(|i| format!("user{}", i)).collect::>(); - let minter = String::from("minter"); - - let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - minter: minter.clone(), - }; - let res = instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg).unwrap(); - assert_eq!(0, res.messages.len()); - - execute( - deps.as_mut(), - mock_env(), - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::BatchMint { - to: users[0].clone(), - batch: tokens - .iter() - .map(|token_id| (token_id.clone(), 1u64.into())) - .collect::>(), - msg: None, - }, - ) - .unwrap(); - - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::Tokens { - owner: users[0].clone(), - start_after: None, - limit: Some(5), - }, - ), - to_binary(&TokensResponse { - tokens: tokens[..5].to_owned() - }) - ); - - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::Tokens { - owner: users[0].clone(), - start_after: Some("token5".to_owned()), - limit: Some(5), - }, - ), - to_binary(&TokensResponse { - tokens: tokens[6..].to_owned() - }) - ); - - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::AllTokens { - start_after: Some("token5".to_owned()), - limit: Some(5), - }, - ), - to_binary(&TokensResponse { - tokens: tokens[6..].to_owned() - }) - ); - - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::TokenInfo { - token_id: "token5".to_owned() - }, - ), - to_binary(&TokenInfoResponse { url: "".to_owned() }) - ); - - for user in users[1..].iter() { - execute( - deps.as_mut(), - mock_env(), - mock_info(users[0].as_ref(), &[]), - Cw1155ExecuteMsg::ApproveAll { - operator: user.clone(), - expires: None, - }, - ) - .unwrap(); - } - - assert_eq!( - query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::ApprovedForAll { - owner: users[0].clone(), - include_expired: None, - start_after: Some(String::from("user2")), - limit: Some(1), - }, - ), - to_binary(&ApprovedForAllResponse { - operators: vec![cw1155::Approval { - spender: users[3].clone(), - expires: Expiration::Never {} - }], - }) - ); -} - -#[test] -fn approval_expires() { - let mut deps = mock_dependencies(); - let token1 = "token1".to_owned(); - let minter = String::from("minter"); - let user1 = String::from("user1"); - let user2 = String::from("user2"); - - let env = { - let mut env = mock_env(); - env.block.height = 10; - env - }; - - let msg = InstantiateMsg { - minter: minter.clone(), - }; - let res = instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg).unwrap(); - assert_eq!(0, res.messages.len()); - - execute( - deps.as_mut(), - env.clone(), - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::Mint { - to: user1.clone(), - token_id: token1, - value: 1u64.into(), - msg: None, - }, - ) - .unwrap(); - - // invalid expires should be rejected - assert!(matches!( - execute( - deps.as_mut(), - env.clone(), - mock_info(user1.as_ref(), &[]), - Cw1155ExecuteMsg::ApproveAll { - operator: user2.clone(), - expires: Some(Expiration::AtHeight(5)), - }, - ), - Err(_) - )); - - execute( - deps.as_mut(), - env.clone(), - mock_info(user1.as_ref(), &[]), - Cw1155ExecuteMsg::ApproveAll { - operator: user2.clone(), - expires: Some(Expiration::AtHeight(100)), - }, - ) - .unwrap(); - - let query_msg = Cw1155QueryMsg::IsApprovedForAll { - owner: user1, - operator: user2, - }; - assert_eq!( - query(deps.as_ref(), env, query_msg.clone()), - to_binary(&IsApprovedForAllResponse { approved: true }) - ); - - let env = { - let mut env = mock_env(); - env.block.height = 100; - env - }; - - assert_eq!( - query(deps.as_ref(), env, query_msg,), - to_binary(&IsApprovedForAllResponse { approved: false }) - ); -} - -#[test] -fn mint_overflow() { - let mut deps = mock_dependencies(); - let token1 = "token1".to_owned(); - let minter = String::from("minter"); - let user1 = String::from("user1"); - - let env = mock_env(); - let msg = InstantiateMsg { - minter: minter.clone(), - }; - let res = instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg).unwrap(); - assert_eq!(0, res.messages.len()); - - execute( - deps.as_mut(), - env.clone(), - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::Mint { - to: user1.clone(), - token_id: token1.clone(), - value: u128::MAX.into(), - msg: None, - }, - ) - .unwrap(); - - assert!(matches!( - execute( - deps.as_mut(), - env, - mock_info(minter.as_ref(), &[]), - Cw1155ExecuteMsg::Mint { - to: user1, - token_id: token1, - value: 1u64.into(), - msg: None, - }, - ), - Err(ContractError::Std(StdError::Overflow { - source: OverflowError { .. }, - .. - })) - )); -} diff --git a/packages/cw1155/.cargo/config b/packages/cw1155/.cargo/config deleted file mode 100644 index 628e18e5b..000000000 --- a/packages/cw1155/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --lib --target wasm32-unknown-unknown" -wasm-debug = "build --lib --target wasm32-unknown-unknown" -schema = "run --bin schema" diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml deleted file mode 100644 index 784b5f4c9..000000000 --- a/packages/cw1155/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "cw1155" -version = "0.16.0" -authors = ["Huang Yi "] -edition = "2018" -description = "Definition and types for the CosmWasm-1155 interface" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-plus" -homepage = "https://cosmwasm.com" - -[dependencies] -cw-utils = "0.16.0" -cosmwasm-schema = "1.1.0" -cosmwasm-std = { version = "1.1.0" } -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/cw1155/README.md b/packages/cw1155/README.md deleted file mode 100644 index a37785ca6..000000000 --- a/packages/cw1155/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# CW1155 Spec: Multiple Tokens - -CW1155 is a specification for managing multiple tokens based on CosmWasm. -The name and design is based on Ethereum's ERC1155 standard. - -The specification is split into multiple sections, a contract may only -implement some of this functionality, but must implement the base. - -Fungible tokens and non-fungible tokens are treated equally, non-fungible tokens just have one max supply. - -Approval is set or unset to some operator over entire set of tokens. (More nuanced control is defined in -[ERC1761](https://eips.ethereum.org/EIPS/eip-1761)) - -## Base - -### Messages - -`SendFrom{from, to, token_id, value, msg}` - This transfers some amount of tokens between two accounts. If `to` is an -address controlled by a smart contract, it must implement the `CW1155Receiver` interface, `msg` will be passed to it -along with other fields, otherwise, `msg` should be `None`. The operator should either be the `from` account or have -approval from it. - -`BatchSendFrom{from, to, batch: Vec<(token_id, value)>, msg}` - Batched version of `SendFrom` which can handle multiple -types of tokens at once. - -`Mint {to, token_id, value, msg}` - This mints some tokens to `to` account, If `to` is controlled by a smart contract, -it should implement `CW1155Receiver` interface, `msg` will be passed to it along with other fields, otherwise, `msg` -should be `None`. - -`BatchMint {to, batch: Vec<(token_id, value)>, msg}` - Batched version of `Mint`. - -`Burn {from, token_id, value}` - This burns some tokens from `from` account. - -`BatchBurn {from, batch: Vec<(token_id, value)>}` - Batched version of `Burn`. - -`ApproveAll{ operator, expires }` - Allows operator to transfer / send any token from the owner's account. If expiration -is set, then this allowance has a time/height limit. - -`RevokeAll { operator }` - Remove previously granted ApproveAll permission - -### Queries - -`Balance { owner, token_id }` - Query the balance of `owner` on particular type of token, default to `0` when record not -exist. - -`BatchBalance { owner, token_ids }` - Query the balance of `owner` on multiple types of tokens, batched version of -`Balance`. - -`ApprovedForAll{owner, include_expired, start_after, limit}` - List all operators that can access all of the owner's -tokens. Return type is `ApprovedForAllResponse`. If `include_expired` is set, show expired owners in the results, -otherwise, ignore them. - -`IsApprovedForAll{owner, operator}` - Query approved status `owner` granted to `operator`. Return type is -`IsApprovedForAllResponse`. - -### Receiver - -Any contract wish to receive CW1155 tokens must implement `Cw1155ReceiveMsg` and `Cw1155BatchReceiveMsg`. - -`Cw1155ReceiveMsg { operator, from, token_id, amount, msg}` - - -`Cw1155BatchReceiveMsg { operator, from, batch, msg}` - - -### Events - -- `transfer(from, to, token_id, value)` - - `from`/`to` are optional, no `from` attribute means minting, no `to` attribute means burning, but they mustn't be -neglected at the same time. - - -## Metadata - -### Queries - -`TokenInfo{ token_id }` - Query metadata url of `token_id`. - -### Events - -`token_info(url, token_id)` - -Metadata url of `token_id` is changed, `url` should point to a json file. - -## Enumerable - -### Queries - -Pagination is acheived via `start_after` and `limit`. Limit is a request -set by the client, if unset, the contract will automatically set it to -`DefaultLimit` (suggested 10). If set, it will be used up to a `MaxLimit` -value (suggested 30). Contracts can define other `DefaultLimit` and `MaxLimit` -values without violating the CW1155 spec, and clients should not rely on -any particular values. - -If `start_after` is unset, the query returns the first results, ordered by -lexogaphically by `token_id`. If `start_after` is set, then it returns the -first `limit` tokens *after* the given one. This allows straight-forward -pagination by taking the last result returned (a `token_id`) and using it -as the `start_after` value in a future query. - -`Tokens{owner, start_after, limit}` - List all token_ids that belong to a given owner. -Return type is `TokensResponse{tokens: Vec}`. - -`AllTokens{start_after, limit}` - Requires pagination. Lists all token_ids controlled by the contract. diff --git a/packages/cw1155/src/bin/schema.rs b/packages/cw1155/src/bin/schema.rs deleted file mode 100644 index 6fe55cb08..000000000 --- a/packages/cw1155/src/bin/schema.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -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!(cw1155::Cw1155ExecuteMsg), &out_dir); - export_schema(&schema_for!(cw1155::Cw1155QueryMsg), &out_dir); - export_schema(&schema_for!(cw1155::Cw1155ReceiveMsg), &out_dir); - export_schema(&schema_for!(cw1155::Cw1155BatchReceiveMsg), &out_dir); - export_schema(&schema_for!(cw1155::BalanceResponse), &out_dir); - export_schema(&schema_for!(cw1155::BatchBalanceResponse), &out_dir); - export_schema(&schema_for!(cw1155::ApprovedForAllResponse), &out_dir); - export_schema(&schema_for!(cw1155::IsApprovedForAllResponse), &out_dir); - export_schema(&schema_for!(cw1155::TokenInfoResponse), &out_dir); - export_schema(&schema_for!(cw1155::TokensResponse), &out_dir); -} diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs deleted file mode 100644 index 2d7709fa8..000000000 --- a/packages/cw1155/src/event.rs +++ /dev/null @@ -1,56 +0,0 @@ -use cosmwasm_std::{attr, Response, Uint128}; -use cw_utils::Event; - -/// Tracks token transfer/mint/burn actions -pub struct TransferEvent<'a> { - pub from: Option<&'a str>, - pub to: Option<&'a str>, - pub token_id: &'a str, - pub amount: Uint128, -} - -impl<'a> Event for TransferEvent<'a> { - fn add_attributes(&self, rsp: &mut Response) { - rsp.attributes.push(attr("action", "transfer")); - rsp.attributes.push(attr("token_id", self.token_id)); - rsp.attributes.push(attr("amount", self.amount)); - if let Some(from) = self.from { - rsp.attributes.push(attr("from", from.to_string())); - } - if let Some(to) = self.to { - rsp.attributes.push(attr("to", to.to_string())); - } - } -} - -/// Tracks token metadata changes -pub struct MetadataEvent<'a> { - pub url: &'a str, - pub token_id: &'a str, -} - -impl<'a> Event for MetadataEvent<'a> { - fn add_attributes(&self, rsp: &mut Response) { - rsp.attributes.push(attr("action", "set_metadata")); - rsp.attributes.push(attr("url", self.url)); - rsp.attributes.push(attr("token_id", self.token_id)); - } -} - -/// Tracks approve_all status changes -pub struct ApproveAllEvent<'a> { - pub sender: &'a str, - pub operator: &'a str, - pub approved: bool, -} - -impl<'a> Event for ApproveAllEvent<'a> { - fn add_attributes(&self, rsp: &mut Response) { - rsp.attributes.push(attr("action", "approve_all")); - rsp.attributes.push(attr("sender", self.sender.to_string())); - rsp.attributes - .push(attr("operator", self.operator.to_string())); - rsp.attributes - .push(attr("approved", (self.approved as u32).to_string())); - } -} diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs deleted file mode 100644 index 69bfc5f77..000000000 --- a/packages/cw1155/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -/*! -CW1155 is a specification for managing multiple tokens based on CosmWasm. -The name and design is based on Ethereum's ERC1155 standard. - -The specification is split into multiple sections, a contract may only -implement some of this functionality, but must implement the base. - -Fungible tokens and non-fungible tokens are treated equally, non-fungible tokens just have one max supply. - -Approval is set or unset to some operator over entire set of tokens. (More nuanced control is defined in -[ERC1761](https://eips.ethereum.org/EIPS/eip-1761)) - -For more information on this specification, please check out the -[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw1155/README.md). -*/ - -pub use cw_utils::Expiration; - -pub use crate::event::{ApproveAllEvent, MetadataEvent, TransferEvent}; -pub use crate::msg::{Cw1155ExecuteMsg, TokenId}; -pub use crate::query::{ - Approval, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, - IsApprovedForAllResponse, TokenInfoResponse, TokensResponse, -}; -pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; - -mod event; -mod msg; -mod query; -mod receiver; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs deleted file mode 100644 index e8ddce34b..000000000 --- a/packages/cw1155/src/msg.rs +++ /dev/null @@ -1,67 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Binary, Uint128}; -use cw_utils::Expiration; - -pub type TokenId = String; - -#[cw_serde] - -pub enum Cw1155ExecuteMsg { - /// SendFrom is a base message to move tokens, - /// if `env.sender` is the owner or has sufficient pre-approval. - SendFrom { - from: String, - /// If `to` is not contract, `msg` should be `None` - to: String, - token_id: TokenId, - value: Uint128, - /// `None` means don't call the receiver interface - msg: Option, - }, - /// BatchSendFrom is a base message to move multiple types of tokens in batch, - /// if `env.sender` is the owner or has sufficient pre-approval. - BatchSendFrom { - from: String, - /// if `to` is not contract, `msg` should be `None` - to: String, - batch: Vec<(TokenId, Uint128)>, - /// `None` means don't call the receiver interface - msg: Option, - }, - /// Mint is a base message to mint tokens. - Mint { - /// If `to` is not contract, `msg` should be `None` - to: String, - token_id: TokenId, - value: Uint128, - /// `None` means don't call the receiver interface - msg: Option, - }, - /// BatchMint is a base message to mint multiple types of tokens in batch. - BatchMint { - /// If `to` is not contract, `msg` should be `None` - to: String, - batch: Vec<(TokenId, Uint128)>, - /// `None` means don't call the receiver interface - msg: Option, - }, - /// Burn is a base message to burn tokens. - Burn { - from: String, - token_id: TokenId, - value: Uint128, - }, - /// BatchBurn is a base message to burn multiple types of tokens in batch. - BatchBurn { - from: String, - batch: Vec<(TokenId, Uint128)>, - }, - /// Allows operator to transfer / send any token from the owner's account. - /// If expiration is set, then this allowance has a time/height limit - ApproveAll { - operator: String, - expires: Option, - }, - /// Remove previously granted ApproveAll permission - RevokeAll { operator: String }, -} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs deleted file mode 100644 index b20076911..000000000 --- a/packages/cw1155/src/query.rs +++ /dev/null @@ -1,94 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; -use cw_utils::Expiration; - -use crate::msg::TokenId; - -#[cw_serde] -#[derive(QueryResponses)] -pub enum Cw1155QueryMsg { - /// Returns the current balance of the given address, 0 if unset. - #[returns(BalanceResponse)] - Balance { owner: String, token_id: TokenId }, - /// Returns the current balance of the given address for a batch of tokens, 0 if unset. - #[returns(BatchBalanceResponse)] - BatchBalance { - owner: String, - token_ids: Vec, - }, - /// List all operators that can access all of the owner's tokens. - #[returns(ApprovedForAllResponse)] - ApprovedForAll { - owner: String, - /// unset or false will filter out expired approvals, you must set to true to see them - include_expired: Option, - start_after: Option, - limit: Option, - }, - /// Query approved status `owner` granted to `operator`. - #[returns(IsApprovedForAllResponse)] - IsApprovedForAll { owner: String, operator: String }, - - /// With MetaData Extension. - /// Query metadata of token - #[returns(TokenInfoResponse)] - TokenInfo { token_id: TokenId }, - - /// With Enumerable extension. - /// Returns all tokens owned by the given address, [] if unset. - #[returns(TokensResponse)] - Tokens { - owner: String, - start_after: Option, - limit: Option, - }, - /// With Enumerable extension. - /// Requires pagination. Lists all token_ids controlled by the contract. - #[returns(TokensResponse)] - AllTokens { - start_after: Option, - limit: Option, - }, -} - -#[cw_serde] -pub struct BalanceResponse { - pub balance: Uint128, -} - -#[cw_serde] -pub struct BatchBalanceResponse { - pub balances: Vec, -} - -#[cw_serde] -pub struct Approval { - /// Account that can transfer/send the token - pub spender: String, - /// When the Approval expires (maybe Expiration::never) - pub expires: Expiration, -} - -#[cw_serde] -pub struct ApprovedForAllResponse { - pub operators: Vec, -} - -#[cw_serde] -pub struct IsApprovedForAllResponse { - pub approved: bool, -} - -#[cw_serde] -pub struct TokenInfoResponse { - /// Should be a url point to a json file - pub url: String, -} - -#[cw_serde] -pub struct TokensResponse { - /// Contains all token_ids in lexicographical ordering - /// If there are more than `limit`, use `start_from` in future queries - /// to achieve pagination. - pub tokens: Vec, -} diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs deleted file mode 100644 index c56dab43a..000000000 --- a/packages/cw1155/src/receiver.rs +++ /dev/null @@ -1,73 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; - -use crate::msg::TokenId; - -/// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg -#[cw_serde] - -pub struct Cw1155ReceiveMsg { - /// The account that executed the send message - pub operator: String, - /// The account that the token transfered from - pub from: Option, - pub token_id: TokenId, - pub amount: Uint128, - pub msg: Binary, -} - -impl Cw1155ReceiveMsg { - /// serializes the message - pub fn into_binary(self) -> StdResult { - let msg = ReceiverExecuteMsg::Receive(self); - to_binary(&msg) - } - - /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg>(self, contract_addr: T) -> StdResult { - let msg = self.into_binary()?; - let execute = WasmMsg::Execute { - contract_addr: contract_addr.into(), - msg, - funds: vec![], - }; - Ok(execute.into()) - } -} - -/// Cw1155BatchReceiveMsg should be de/serialized under `BatchReceive()` variant in a ExecuteMsg -#[cw_serde] - -pub struct Cw1155BatchReceiveMsg { - pub operator: String, - pub from: Option, - pub batch: Vec<(TokenId, Uint128)>, - pub msg: Binary, -} - -impl Cw1155BatchReceiveMsg { - /// serializes the message - pub fn into_binary(self) -> StdResult { - let msg = ReceiverExecuteMsg::BatchReceive(self); - to_binary(&msg) - } - - /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg>(self, contract_addr: T) -> StdResult { - let msg = self.into_binary()?; - let execute = WasmMsg::Execute { - contract_addr: contract_addr.into(), - msg, - funds: vec![], - }; - Ok(execute.into()) - } -} - -// This is just a helper to properly serialize the above message -#[cw_serde] - -enum ReceiverExecuteMsg { - Receive(Cw1155ReceiveMsg), - BatchReceive(Cw1155BatchReceiveMsg), -} From 350961a101f40dcf56feb76c2f8737c21482037c Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 18 Oct 2022 22:29:21 +0200 Subject: [PATCH 3/4] align CI --- .circleci/config.yml | 149 ------------------------------------------- 1 file changed, 149 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1bd7f83a6..aa1cc07b6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,28 +15,14 @@ workflows: - contract_cw4_stake - contract_cw20_base - contract_cw20_ics20 - - contract_cw1155_base - package_controllers - - package_utils - package_cw1 - package_cw2 - package_cw3 - package_cw4 - package_cw20 - - package_cw1155 - - package_multi_test - - package_storage_plus - lint - wasm-build - - benchmarking: - requires: - - package_storage_plus - filters: - branches: - only: - # Long living branches - - main - # 👇Add your branch here if benchmarking matters to your work - coverage deploy: jobs: @@ -270,33 +256,6 @@ jobs: - target key: cargocache-cw20-ics20-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} - contract_cw1155_base: - docker: - - image: rust:1.58.1 - working_directory: ~/project/contracts/cw1155-base - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-cw1155-base-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-cw1155-base-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} - package_controllers: docker: - image: rust:1.58.1 @@ -322,31 +281,6 @@ jobs: - target key: cargocache-v2-controllers:1.58.1-{{ checksum "~/project/Cargo.lock" }} - package_utils: - docker: - - image: rust:1.58.1 - working_directory: ~/project/packages/utils - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version; rustup target list --installed - - restore_cache: - keys: - - cargocache-v2-utils:1.58.1-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Build library for native target - command: cargo build --locked - - run: - name: Run unit tests - command: cargo test --locked - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-v2-utils:1.58.1-{{ checksum "~/project/Cargo.lock" }} - package_cw1: docker: - image: rust:1.58.1 @@ -584,89 +518,6 @@ jobs: name: Check wasm contracts command: cosmwasm-check ./target/wasm32-unknown-unknown/release/*.wasm - package_multi_test: - docker: - - image: rust:1.58.1 - working_directory: ~/project/packages/multi-test - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version; rustup target list --installed - - restore_cache: - keys: - - cargocache-v2-multi-test:1.58.1-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Build library for native target - command: cargo build --locked - - run: - name: Run unit tests - command: cargo test --locked - - run: - name: Run unit tests (with iterator) - command: cargo test --locked --features iterator - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-v2-multi-test:1.58.1-{{ checksum "~/project/Cargo.lock" }} - - package_storage_plus: - docker: - - image: rust:1.58.1 - working_directory: ~/project/packages/storage-plus - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version; rustup target list --installed - - restore_cache: - keys: - - cargocache-v2-storage-plus:1.58.1-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Build library for native target (no iterator) - command: cargo build --locked --no-default-features - - run: - name: Run unit tests (no iterator) - command: cargo test --locked --no-default-features - - run: - name: Build library for native target (with iterator and macro) - command: cargo build --locked --all-features - - run: - name: Run unit tests (with iterator and macro) - command: cargo test --locked --all-features - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-v2-storage-plus:1.58.1-{{ checksum "~/project/Cargo.lock" }} - - benchmarking: - docker: - - image: rust:1.58.1 - environment: - RUST_BACKTRACE: 1 - steps: - - checkout: - path: ~/project - - run: - name: Version information (default; stable) - command: rustc --version && cargo --version - - restore_cache: - keys: - - cargocache-v2-benchmarking-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Run storage-plus benchmarks - working_directory: ~/project/packages/storage-plus - command: cargo bench -- --color never --save-baseline - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-v2-benchmarking-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} - coverage: # https://circleci.com/developer/images?imageType=machine machine: From 0ceab105974bc4e9fac75feb1bcbfaa201878a6a Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 18 Oct 2022 22:54:52 +0200 Subject: [PATCH 4/4] Align publish.sh --- scripts/publish.sh | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/scripts/publish.sh b/scripts/publish.sh index 27028ac21..3a210db91 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -13,34 +13,19 @@ then exit 1 fi -# this should really more to cosmwasm... -STORAGE_PACKAGES="storage-macro storage-plus" # these are imported by other packages BASE_PACKAGES="cw2" -UTILS_PACKAGES="utils" -ALL_PACKAGES="controllers cw1 cw3 cw4 cw20 cw1155 multi-test" +ALL_PACKAGES="controllers cw1 cw3 cw4 cw20" # This is imported by cw3-fixed-multisig, which is imported by cw3-flex-multisig # need to make a separate category to remove race conditions CW20_BASE="cw20-base" # these are imported by other contracts BASE_CONTRACTS="cw1-whitelist cw4-group cw3-fixed-multisig " -ALL_CONTRACTS="cw1-subkeys cw3-flex-multisig cw4-stake cw20-ics20 cw1155-base" +ALL_CONTRACTS="cw1-subkeys cw3-flex-multisig cw4-stake cw20-ics20" SLEEP_TIME=30 -for pack in $STORAGE_PACKAGES; do - ( - cd "packages/$pack" - echo "Publishing $pack" - cargo publish - ) -done - -# wait for these to be processed on crates.io -echo "Waiting for publishing storage packages" -sleep $SLEEP_TIME - for pack in $BASE_PACKAGES; do ( cd "packages/$pack" @@ -53,18 +38,6 @@ done echo "Waiting for publishing base packages" sleep $SLEEP_TIME -for pack in $UTILS_PACKAGES; do - ( - cd "packages/$pack" - echo "Publishing $pack" - cargo publish - ) -done - -# wait for these to be processed on crates.io -echo "Waiting for publishing utils packages" -sleep $SLEEP_TIME - for pack in $ALL_PACKAGES; do ( cd "packages/$pack"