diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 28551b2..b1b328a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: 1.72.0 + toolchain: 1.81.0 target: wasm32-unknown-unknown profile: minimal override: true diff --git a/Cargo.lock b/Cargo.lock index 82c7bf3..d7db25e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.0.5" @@ -37,12 +49,139 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rayon", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", + "rayon", +] + [[package]] name = "async-trait" version = "0.1.73" @@ -51,7 +190,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] @@ -87,6 +226,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -99,27 +244,30 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bindgen" -version = "0.69.1" +version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ "bitflags 2.4.1", "cexpr", "clang-sys", - "lazy_static", - "lazycell", + "itertools 0.13.0", "log", - "peeking_take_while", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.60", - "which", + "syn 2.0.82", ] [[package]] @@ -174,6 +322,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" +[[package]] +name = "bnum" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" + [[package]] name = "bs58" version = "0.5.0" @@ -287,20 +441,19 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cosmos-sdk-proto" -version = "0.20.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" +checksum = "e8ce7f4797cdf5cd18be6555ff3f0a8d37023c2d60f3b2708895d601b85c1c46" dependencies = [ - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.13.3", "tendermint-proto", ] [[package]] name = "cosmrs" -version = "0.15.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea" +checksum = "09f90935b72d9fa65a2a784e09f25778637b7e88e9d6f87c717081470f7fa726" dependencies = [ "bip32", "cosmos-sdk-proto", @@ -317,34 +470,73 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-core" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6ceb8624260d0d3a67c4e1a1d43fc7e9406720afbcb124521501dd138f90aa" + +[[package]] +name = "cosmwasm-crypto" +version = "1.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58535cbcd599b3c193e3967c8292fe1dbbb5de7c2a2d87380661091dd4744044" +dependencies = [ + "digest 0.10.7", + "ed25519-zebra 3.1.0", + "k256", + "rand_core 0.6.4", + "thiserror", +] + [[package]] name = "cosmwasm-crypto" -version = "1.5.4" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b4c3f9c4616d6413d4b5fc4c270a4cc32a374b9be08671e80e1a019f805d8f" +checksum = "4125381e5fd7fefe9f614640049648088015eca2b60d861465329a5d87dfa538" dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "cosmwasm-core", "digest 0.10.7", "ecdsa", - "ed25519-zebra", + "ed25519-zebra 4.0.3", "k256", + "num-traits", + "p256", "rand_core 0.6.4", + "rayon", + "sha2 0.10.7", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.5.4" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c586ced10c3b00e809ee664a895025a024f60d65d34fe4c09daed4a4db68a3f3" +checksum = "a8e07de16c800ac82fd188d055ecdb923ead0cf33960d3350089260bb982c09f" dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cosmwasm-derive" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5658b1dc64e10b56ae7a449f678f96932a96f6cfad1769d608d1d1d656480a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "cosmwasm-schema" -version = "1.5.4" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8467874827d384c131955ff6f4d47d02e72a956a08eb3c0ff24f8c903a5517b4" +checksum = "f86b4d949b6041519c58993a73f4bbfba8083ba14f7001eae704865a09065845" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -355,26 +547,26 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.4" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6db85d98ac80922aef465e564d5b21fa9cfac5058cb62df7f116c3682337393" +checksum = "c8ef1b5835a65fcca3ab8b9a02b4f4dacc78e233a5c2f20b270efb9db0666d12" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.82", ] [[package]] name = "cosmwasm-std" -version = "1.5.4" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712fe58f39d55c812f7b2c84e097cdede3a39d520f89b6dc3153837e31741927" +checksum = "c21fde95ccd20044a23c0ac6fd8c941f3e8c158169dc94b5aa6491a2d9551a8d" dependencies = [ - "base64", - "bech32", - "bnum", - "cosmwasm-crypto", - "cosmwasm-derive", + "base64 0.21.5", + "bech32 0.9.1", + "bnum 0.10.0", + "cosmwasm-crypto 1.5.8", + "cosmwasm-derive 1.5.8", "derivative", "forward_ref", "hex", @@ -386,13 +578,36 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-std" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70eb7ab0c1e99dd6207496963ba2a457c4128ac9ad9c72a83f8d9808542b849b" +dependencies = [ + "base64 0.22.1", + "bech32 0.11.0", + "bnum 0.11.0", + "cosmwasm-core", + "cosmwasm-crypto 2.1.4", + "cosmwasm-derive 2.1.4", + "derive_more", + "hex", + "rand_core 0.6.4", + "schemars", + "serde", + "serde-json-wasm 1.0.1", + "sha2 0.10.7", + "static_assertions", + "thiserror", +] + [[package]] name = "cosmwasm-storage" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b52be0d56b78f502f3acb75e40908a0d04d9f265b6beb0f86b36ec2ece048748" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.5.8", "serde", ] @@ -405,6 +620,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-bigint" version = "0.5.3" @@ -440,6 +680,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -455,25 +722,26 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" +checksum = "f13360e9007f51998d42b1bc6b7fa0141f74feae61ed5fd1e5b0a89eec7b5de1" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 2.1.4", "schemars", "serde", ] [[package]] name = "cw2" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" +checksum = "b04852cd38f044c0751259d5f78255d07590d136b8a86d4e09efdd7666bd6d27" dependencies = [ "cosmwasm-schema", - "cosmwasm-std", + "cosmwasm-std 2.1.4", "cw-storage-plus", "schemars", + "semver", "serde", "thiserror", ] @@ -499,6 +767,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", + "unicode-xid", +] + [[package]] name = "digest" version = "0.9.0" @@ -569,7 +858,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 3.2.0", "hashbrown 0.12.3", "hex", "rand_core 0.6.4", @@ -578,6 +867,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-zebra" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519", + "hashbrown 0.14.0", + "hex", + "rand_core 0.6.4", + "sha2 0.10.7", + "zeroize", +] + [[package]] name = "either" version = "1.9.0" @@ -638,6 +942,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "flex-error" version = "0.4.4" @@ -671,9 +981,9 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -686,9 +996,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -696,15 +1006,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -713,44 +1023,44 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -836,7 +1146,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", ] [[package]] @@ -844,6 +1163,16 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "hermit-abi" @@ -966,12 +1295,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.15.0", ] [[package]] @@ -991,18 +1320,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -1024,9 +1344,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -1038,9 +1358,9 @@ dependencies = [ [[package]] name = "konst" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030400e39b2dff8beaa55986a17e0014ad657f569ca92426aafcb5e8e71faee7" +checksum = "50a0ba6de5f7af397afff922f22c149ff605c766cd3269cf6c1cd5e466dbe3b9" dependencies = [ "const_panic", "konst_kernel", @@ -1050,9 +1370,9 @@ dependencies = [ [[package]] name = "konst_kernel" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3376133edc39f027d551eb77b077c2865a0ef252b2e7d0dd6b6dc303db95d8b5" +checksum = "be0a455a1719220fd6adf756088e1c69a85bf14b6a9e24537a5cc04f503edb2b" dependencies = [ "typewit", ] @@ -1063,18 +1383,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e28ab1dc35e09d60c2b8c90d12a9a8d9666c876c10a3739a3196db0103b6043" -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.151" @@ -1146,21 +1454,29 @@ dependencies = [ ] [[package]] -name = "num-derive" -version = "0.3.3" +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", ] [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1213,15 +1529,15 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "osmosis-std" -version = "0.22.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8641c376f01f5af329dc2a34e4f5527eaeb0bde18cda8d86fed958d04c86159c" +checksum = "cd621cc2f26474c6fb689ccc114dc0d8b53369a322f1fa5f24802f3de3d3def3" dependencies = [ "chrono", - "cosmwasm-std", + "cosmwasm-std 2.1.4", "osmosis-std-derive", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.13.3", + "prost-types 0.13.3", "schemars", "serde", "serde-cw-value", @@ -1229,9 +1545,9 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.20.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ebdfd1bc8ed04db596e110c6baa9b174b04f6ed1ec22c666ddc5cb3fa91bd7" +checksum = "8b0240fd030a4bbc79fa6cbea0b3eb0260a4b79075ebc039b93e2652bff8655b" dependencies = [ "itertools 0.10.5", "proc-macro2", @@ -1242,16 +1558,16 @@ dependencies = [ [[package]] name = "osmosis-test-tube" -version = "22.1.0" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a082b97136d15470a37aa758f227c865594590b69d74721248ed5adf59bf1ca2" +checksum = "8bb569512b10f8fb1fa4fe2e2dc0b474f1fca7ed80500a4328593fc85ad0c3e6" dependencies = [ - "base64", + "base64 0.22.1", "bindgen", "cosmrs", - "cosmwasm-std", + "cosmwasm-std 2.1.4", "osmosis-std", - "prost 0.12.3", + "prost 0.13.3", "serde", "serde_json", "test-tube", @@ -1259,22 +1575,28 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.14" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.7", +] [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "peg" -version = "0.7.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" dependencies = [ "peg-macros", "peg-runtime", @@ -1282,9 +1604,9 @@ dependencies = [ [[package]] name = "peg-macros" -version = "0.7.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" dependencies = [ "peg-runtime", "proc-macro2", @@ -1293,9 +1615,9 @@ dependencies = [ [[package]] name = "peg-runtime" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" [[package]] name = "percent-encoding" @@ -1320,7 +1642,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] @@ -1345,6 +1667,15 @@ dependencies = [ "spki", ] +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.15" @@ -1352,17 +1683,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.60", + "syn 2.0.82", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "once_cell", - "toml_edit", + "toml_edit 0.22.22", ] [[package]] @@ -1391,9 +1730,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -1410,12 +1749,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive 0.13.3", ] [[package]] @@ -1433,15 +1772,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] @@ -1455,22 +1794,43 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.3" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ - "prost 0.12.3", + "prost 0.13.3", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 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.4", +] + +[[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.4", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -1486,11 +1846,31 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" -version = "1.9.4" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1500,9 +1880,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1511,15 +1891,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" -version = "1.9.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" @@ -1527,7 +1907,7 @@ version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -1596,9 +1976,9 @@ dependencies = [ [[package]] name = "rstest" -version = "0.18.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" dependencies = [ "futures", "futures-timer", @@ -1608,18 +1988,19 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.18.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" dependencies = [ "cfg-if", "glob", + "proc-macro-crate", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", - "syn 2.0.60", + "syn 2.0.82", "unicode-ident", ] @@ -1637,9 +2018,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -1674,7 +2055,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.5", ] [[package]] @@ -1713,9 +2094,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.13" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -1725,14 +2106,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.13" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.82", ] [[package]] @@ -1790,9 +2171,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -1835,24 +2216,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.82", ] [[package]] @@ -1874,7 +2255,16 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", ] [[package]] @@ -2003,12 +2393,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "sylvia" -version = "0.10.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64c8e892525ec035e5bdbeceec19309b1534734da7ff7bc69e9f639b077c497" +checksum = "96e34c4bc8b8847011ca9c1478cfd5f532acb691377bca5fecf748833acffde8" dependencies = [ "cosmwasm-schema", - "cosmwasm-std", + "cosmwasm-std 2.1.4", + "derivative", "konst", "schemars", "serde", @@ -2019,17 +2410,17 @@ dependencies = [ [[package]] name = "sylvia-derive" -version = "0.10.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae5e0f41752efba2c6895514fa4caae742f69a2a73b4790bb6e365400c563bc1" +checksum = "ad349313e23cfe1d54876be195a31b3293ca91ae31754abcf1f908e10812b4e7" dependencies = [ "convert_case", - "itertools 0.12.0", + "itertools 0.13.0", "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] @@ -2045,9 +2436,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", @@ -2077,9 +2468,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.34.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2294fa667c8b548ee27a9ba59115472d0a09c2ba255771092a7f1dcf03a789" +checksum = "2f3afea7809ffaaf1e5d9c3c9997cb3a834df7e94fbfab2fad2bc4577f1cde41" dependencies = [ "bytes", "digest 0.10.7", @@ -2090,8 +2481,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.13.3", "ripemd", "serde", "serde_bytes", @@ -2108,9 +2498,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.34.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a25dbe8b953e80f3d61789fbdb83bf9ad6c0ef16df5ca6546f49912542cc137" +checksum = "d8add7b85b0282e5901521f78fe441956ac1e2752452f4e1f2c0ce7e1f10d485" dependencies = [ "flex-error", "serde", @@ -2122,16 +2512,13 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.34.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" +checksum = "bf3abf34ecf33125621519e9952688e7a59a98232d51538037ba21fbe526a802" dependencies = [ "bytes", "flex-error", - "num-derive", - "num-traits", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.13.3", "serde", "serde_bytes", "subtle-encoding", @@ -2140,9 +2527,9 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.34.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b" +checksum = "9693f42544bf3b41be3cbbfa418650c86e137fb8f5a57981659a84b677721ecf" dependencies = [ "async-trait", "bytes", @@ -2151,6 +2538,7 @@ dependencies = [ "getrandom", "peg", "pin-project", + "rand", "reqwest", "semver", "serde", @@ -2172,15 +2560,15 @@ dependencies = [ [[package]] name = "test-tube" -version = "0.5.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09184c7655b2bdaf4517b06141a2e4c44360904f2706a05b24c831bd97ad1db6" +checksum = "dc02a1036b1d92445cb09078bcc933a84937586e6acb28ee5dfa7b5c20be569f" dependencies = [ - "base64", + "base64 0.22.1", "cosmrs", - "cosmwasm-std", + "cosmwasm-std 2.1.4", "osmosis-std", - "prost 0.12.3", + "prost 0.13.3", "serde", "serde_json", "thiserror", @@ -2203,7 +2591,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] @@ -2263,7 +2651,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] [[package]] @@ -2292,28 +2680,47 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.5.15", +] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.6.0", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] @@ -2344,14 +2751,14 @@ dependencies = [ [[package]] name = "transmuter" -version = "3.2.0" +version = "4.0.0" dependencies = [ "cosmwasm-schema", - "cosmwasm-std", + "cosmwasm-std 2.1.4", "cosmwasm-storage", "cw-storage-plus", "cw2", - "itertools 0.12.0", + "itertools 0.13.0", "osmosis-std", "osmosis-test-tube", "rstest", @@ -2367,7 +2774,7 @@ name = "transmuter_math" version = "1.0.0" dependencies = [ "cosmwasm-schema", - "cosmwasm-std", + "cosmwasm-std 2.1.4", "thiserror", ] @@ -2397,9 +2804,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -2416,6 +2823,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -2435,9 +2848,9 @@ dependencies = [ [[package]] name = "uuid" -version = "0.8.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "version_check" @@ -2491,7 +2904,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", "wasm-bindgen-shared", ] @@ -2525,7 +2938,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2546,17 +2959,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2663,6 +3065,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -2673,6 +3084,27 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "zeroize" version = "1.7.0" @@ -2690,5 +3122,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.82", ] diff --git a/Cargo.toml b/Cargo.toml index a64a38e..2a43f4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ members = [ ] [workspace.dependencies] -cosmwasm-schema = "1.3.1" -cosmwasm-std = {version = "1.5.4"} +cosmwasm-schema = "2.1" +cosmwasm-std = {version = "2.1"} [profile.release] codegen-units = 1 diff --git a/contracts/transmuter/Cargo.toml b/contracts/transmuter/Cargo.toml index 8ba3e47..ecc62fc 100644 --- a/contracts/transmuter/Cargo.toml +++ b/contracts/transmuter/Cargo.toml @@ -2,12 +2,12 @@ authors = ["Supanat Potiwarakorn "] edition = "2021" name = "transmuter" -version = "3.2.0" +version = "4.0.0" exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -29,8 +29,7 @@ rpath = false [features] # skip integration test for cases like running `cargo mutants` skip-integration-test = [] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] + # use library feature to disable all instantiate/execute/query exports library = [] @@ -42,25 +41,25 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } +cosmwasm-schema = {workspace = true} +cosmwasm-std = {workspace = true, features = ["cosmwasm_1_1"]} cosmwasm-storage = "1.3.1" -cw-storage-plus = "1.1.0" -cw2 = "1.1.0" -osmosis-std = "0.22.0" +cw-storage-plus = "2.0.0" +cw2 = "2.0.0" +osmosis-std = "0.26.0" schemars = "0.8.12" -serde = { version = "1.0.183", default-features = false, features = ["derive"] } -sylvia = "0.10.1" -thiserror = { version = "1.0.44" } -transmuter_math = { version = "1.0.0", path = "../../packages/transmuter_math" } +serde = {version = "1.0.183", default-features = false, features = ["derive"]} +sylvia = "1.2.1" +thiserror = {version = "1.0.44"} +transmuter_math = {version = "1.0.0", path = "../../packages/transmuter_math"} [dev-dependencies] -itertools = "0.12.0" -osmosis-test-tube = "22.1.0" -rstest = "0.18.2" +itertools = "0.13.0" +osmosis-test-tube = "26.0.1" +rstest = "0.23.0" [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(tarpaulin)', - 'cfg(tarpaulin_include)', -] } +unexpected_cfgs = {level = "warn", check-cfg = [ + 'cfg(tarpaulin)', + 'cfg(tarpaulin_include)', +]} diff --git a/contracts/transmuter/src/alloyed_asset.rs b/contracts/transmuter/src/alloyed_asset.rs index 2d87466..e1ad5dc 100644 --- a/contracts/transmuter/src/alloyed_asset.rs +++ b/contracts/transmuter/src/alloyed_asset.rs @@ -10,15 +10,15 @@ use crate::{ /// and since the pool is a 1:1 multi-asset pool, it act /// as a composite of the underlying assets and assume 1:1 /// value to the underlying assets. -pub struct AlloyedAsset<'a> { - alloyed_denom: Item<'a, String>, - normalization_factor: Item<'a, Uint128>, +pub struct AlloyedAsset { + alloyed_denom: Item, + normalization_factor: Item, } -impl<'a> AlloyedAsset<'a> { +impl AlloyedAsset { pub const fn new( - alloyed_denom_namespace: &'a str, - normalization_factor_namespace: &'a str, + alloyed_denom_namespace: &'static str, + normalization_factor_namespace: &'static str, ) -> Self { Self { alloyed_denom: Item::new(alloyed_denom_namespace), @@ -211,7 +211,7 @@ pub mod swap_from_alloyed { #[cfg(test)] mod tests { - use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::{coin, testing::mock_dependencies}; use super::*; @@ -226,7 +226,7 @@ mod tests { .set_alloyed_denom(&mut deps.storage, &alloyed_denom) .unwrap(); - deps.querier.update_balance( + deps.querier.bank.update_balance( "osmo1addr1", vec![Coin { denom: alloyed_denom.clone(), @@ -234,7 +234,7 @@ mod tests { }], ); - deps.querier.update_balance( + deps.querier.bank.update_balance( "osmo1addr2", vec![Coin { denom: alloyed_denom.clone(), @@ -275,7 +275,7 @@ mod tests { // same normalization factor let amount = AlloyedAsset::amount_from( - &[(Coin::new(100, "ua"), Uint128::one())], + &[(coin(100, "ua"), Uint128::one())], Uint128::one(), Rounding::Up, ) @@ -286,8 +286,8 @@ mod tests { // different normalization factor let amount = AlloyedAsset::amount_from( &[ - (Coin::new(100, "ua"), Uint128::from(2u128)), - (Coin::new(100, "ub"), Uint128::from(3u128)), + (coin(100, "ua"), Uint128::from(2u128)), + (coin(100, "ub"), Uint128::from(3u128)), ], Uint128::one(), Rounding::Up, @@ -297,8 +297,8 @@ mod tests { let amount = AlloyedAsset::amount_from( &[ - (Coin::new(100, "ua"), Uint128::from(2u128)), - (Coin::new(100, "ub"), Uint128::from(3u128)), + (coin(100, "ua"), Uint128::from(2u128)), + (coin(100, "ub"), Uint128::from(3u128)), ], Uint128::one(), Rounding::Down, diff --git a/contracts/transmuter/src/asset.rs b/contracts/transmuter/src/asset.rs index 4337989..41fb1c6 100644 --- a/contracts/transmuter/src/asset.rs +++ b/contracts/transmuter/src/asset.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Coin, Deps, StdError, Uint128, Uint256}; -use crate::ContractError; +use crate::{corruptable::Corruptable, ContractError}; #[derive(PartialEq)] pub enum Rounding { @@ -121,16 +121,6 @@ impl Asset { Ok(self) } - pub fn mark_as_corrupted(&'_ mut self) -> &'_ Self { - self.is_corrupted = true; - self - } - - pub fn unmark_as_corrupted(&'_ mut self) -> &'_ Self { - self.is_corrupted = false; - self - } - pub fn denom(&self) -> &str { &self.denom } @@ -143,10 +133,6 @@ impl Asset { self.normalization_factor } - pub fn is_corrupted(&self) -> bool { - self.is_corrupted - } - pub fn config(&self) -> AssetConfig { AssetConfig { denom: self.denom.clone(), @@ -188,6 +174,22 @@ impl Asset { } } +impl Corruptable for Asset { + fn is_corrupted(&self) -> bool { + self.is_corrupted + } + + fn mark_as_corrupted(&mut self) -> &mut Self { + self.is_corrupted = true; + self + } + + fn unmark_as_corrupted(&mut self) -> &mut Self { + self.is_corrupted = false; + self + } +} + /// Convert amount to target asset's amount with the same value /// /// target_amount / target_normalization_factor = amount / source_normalization_factor @@ -219,7 +221,7 @@ pub fn convert_amount( #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::{testing::mock_dependencies_with_balances, Coin}; + use cosmwasm_std::{coin, testing::mock_dependencies_with_balances}; #[test] fn test_convert_amount() { @@ -351,8 +353,8 @@ mod tests { #[test] fn test_checked_init_asset() { let deps = mock_dependencies_with_balances(&[ - ("addr1", &[Coin::new(1, "denom1")]), - ("addr2", &[Coin::new(1, "denom2")]), + ("addr1", &[coin(1, "denom1")]), + ("addr2", &[coin(1, "denom2")]), ]); // denom1 diff --git a/contracts/transmuter/src/contract.rs b/contracts/transmuter/src/contract.rs index 9e7df39..0113c42 100644 --- a/contracts/transmuter/src/contract.rs +++ b/contracts/transmuter/src/contract.rs @@ -1,3 +1,4 @@ +use crate::{corruptable::Corruptable, scope::Scope}; use std::{collections::BTreeMap, iter}; use crate::{ @@ -9,7 +10,7 @@ use crate::{ math::{self, rescale}, role::Role, swap::{BurnTarget, Entrypoint, SwapFromAlloyedConstraint, SwapToAlloyedConstraint, SWAP_FEE}, - transmuter_pool::TransmuterPool, + transmuter_pool::{AssetGroup, TransmuterPool}, }; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ @@ -37,12 +38,12 @@ const CREATE_ALLOYED_DENOM_REPLY_ID: u64 = 1; /// Prefix for alloyed asset denom const ALLOYED_PREFIX: &str = "alloyed"; -pub struct Transmuter<'a> { - pub(crate) active_status: Item<'a, bool>, - pub(crate) pool: Item<'a, TransmuterPool>, - pub(crate) alloyed_asset: AlloyedAsset<'a>, - pub(crate) role: Role<'a>, - pub(crate) limiters: Limiters<'a>, +pub struct Transmuter { + pub(crate) active_status: Item, + pub(crate) pool: Item, + pub(crate) alloyed_asset: AlloyedAsset, + pub(crate) role: Role, + pub(crate) limiters: Limiters, } pub mod key { @@ -57,9 +58,9 @@ pub mod key { #[contract] #[sv::error(ContractError)] -impl Transmuter<'_> { +impl Transmuter { /// Create a Transmuter instance. - pub const fn default() -> Self { + pub const fn new() -> Self { Self { active_status: Item::new(key::ACTIVE_STATUS), pool: Item::new(key::POOL), @@ -225,67 +226,155 @@ impl Transmuter<'_> { pool.add_new_assets(assets)?; self.pool.save(deps.storage, &pool)?; + let asset_weights_iter = pool + .asset_weights()? + .unwrap_or_default() + .into_iter() + .map(|(denom, weight)| (Scope::denom(&denom).key(), weight)); + let asset_group_weights_iter = pool + .asset_group_weights()? + .into_iter() + .map(|(label, weight)| (Scope::asset_group(&label).key(), weight)); + self.limiters.reset_change_limiter_states( deps.storage, env.block.time, - pool.weights()?.unwrap_or_default(), + asset_weights_iter.chain(asset_group_weights_iter), )?; Ok(Response::new().add_attribute("method", "add_new_assets")) } - /// Mark designated denoms as corrupted assets. - /// As a result, the corrupted assets will not allowed to be increased by any means, - /// both in terms of amount and weight. - /// The only way to redeem other pool asset, is to also redeem the corrupted asset - /// with the same pool-defnined value. #[sv::msg(exec)] - fn mark_corrupted_assets( + fn create_asset_group( &self, ExecCtx { deps, env: _, info }: ExecCtx, + label: String, denoms: Vec, ) -> Result { - non_empty_input_required("denoms", &denoms)?; nonpayable(&info.funds)?; - // only moderator can mark corrupted assets - ensure_moderator_authority!(info.sender, self.role.moderator, deps.as_ref()); + // only admin can create asset group + ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref()); + // create asset group self.pool .update(deps.storage, |mut pool| -> Result<_, ContractError> { - pool.mark_corrupted_assets(&denoms)?; + pool.create_asset_group(label.clone(), denoms)?; Ok(pool) })?; - Ok(Response::new().add_attribute("method", "mark_corrupted_assets")) + Ok(Response::new() + .add_attribute("method", "create_asset_group") + .add_attribute("label", label)) } #[sv::msg(exec)] - fn unmark_corrupted_assets( + fn remove_asset_group( &self, ExecCtx { deps, env: _, info }: ExecCtx, - denoms: Vec, + label: String, ) -> Result { - non_empty_input_required("denoms", &denoms)?; nonpayable(&info.funds)?; - // only moderator can unmark corrupted assets - ensure_moderator_authority!(info.sender, self.role.moderator, deps.as_ref()); + // only admin can remove asset group + ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref()); self.pool .update(deps.storage, |mut pool| -> Result<_, ContractError> { - pool.unmark_corrupted_assets(&denoms)?; + pool.remove_asset_group(&label)?; Ok(pool) })?; - Ok(Response::new().add_attribute("method", "unmark_corrupted_assets")) + // remove all limiters for asset group + let limiters = self + .limiters + .list_limiters_by_scope(deps.storage, &Scope::AssetGroup(label.clone()))?; + + for (limiter_label, _) in limiters { + self.limiters.unchecked_deregister( + deps.storage, + Scope::AssetGroup(label.clone()), + &limiter_label, + )?; + } + + Ok(Response::new() + .add_attribute("method", "remove_asset_group") + .add_attribute("label", label)) + } + + /// Mark designated scopes as corrupted scopes. + /// As a result, the corrupted scopes will not allowed to be increased by any means, + /// both in terms of amount and weight. + /// The only way to redeem other pool asset outside of the corrupted scopes is + /// to also redeem asset within the corrupted scopes + /// with the same pool-defnined value. + #[sv::msg(exec)] + fn mark_corrupted_scopes( + &self, + ExecCtx { deps, env: _, info }: ExecCtx, + scopes: Vec, + ) -> Result { + non_empty_input_required("scopes", &scopes)?; + nonpayable(&info.funds)?; + + // only moderator can mark corrupted assets + ensure_moderator_authority!(info.sender, self.role.moderator, deps.as_ref()); + + let mut pool = self.pool.load(deps.storage)?; + + for scope in &scopes { + match scope { + Scope::Denom(denom) => { + pool.mark_corrupted_asset(denom)?; + } + Scope::AssetGroup(label) => { + pool.mark_corrupted_asset_group(label)?; + } + } + } + + self.pool.save(deps.storage, &pool)?; + + Ok(Response::new().add_attribute("method", "mark_corrupted_scopes")) + } + + #[sv::msg(exec)] + fn unmark_corrupted_scopes( + &self, + ExecCtx { deps, env: _, info }: ExecCtx, + scopes: Vec, + ) -> Result { + non_empty_input_required("scopes", &scopes)?; + nonpayable(&info.funds)?; + + // only moderator can unmark corrupted assets + ensure_moderator_authority!(info.sender, self.role.moderator, deps.as_ref()); + + let mut pool = self.pool.load(deps.storage)?; + + for scope in &scopes { + match scope { + Scope::Denom(denom) => { + pool.unmark_corrupted_asset(denom)?; + } + Scope::AssetGroup(label) => { + pool.unmark_corrupted_asset_group(label)?; + } + } + } + + self.pool.save(deps.storage, &pool)?; + + Ok(Response::new().add_attribute("method", "unmark_corrupted_scopes")) } #[sv::msg(exec)] fn register_limiter( &self, ExecCtx { deps, env: _, info }: ExecCtx, - denom: String, + scope: Scope, label: String, limiter_params: LimiterParams, ) -> Result { @@ -294,18 +383,32 @@ impl Transmuter<'_> { // only admin can register limiter ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref()); - // ensure pool has the specified denom let pool = self.pool.load(deps.storage)?; - ensure!( - pool.has_denom(&denom), - ContractError::InvalidPoolAssetDenom { denom } - ); + match scope.clone() { + Scope::Denom(denom) => { + // ensure pool has the specified denom + ensure!( + pool.has_denom(&denom), + ContractError::InvalidPoolAssetDenom { denom } + ); + } + Scope::AssetGroup(label) => { + // check if asset group exists + ensure!( + pool.has_asset_group(&label), + ContractError::AssetGroupNotFound { label } + ); + } + }; + + let scope_key = scope.key(); let base_attrs = vec![ ("method", "register_limiter"), - ("denom", &denom), ("label", &label), + ("scope", &scope_key), ]; + let limiter_attrs = match &limiter_params { LimiterParams::ChangeLimiter { window_config, @@ -330,7 +433,7 @@ impl Transmuter<'_> { // register limiter self.limiters - .register(deps.storage, &denom, &label, limiter_params)?; + .register(deps.storage, scope, &label, limiter_params)?; Ok(Response::new() .add_attributes(base_attrs) @@ -341,7 +444,7 @@ impl Transmuter<'_> { fn deregister_limiter( &self, ExecCtx { deps, env: _, info }: ExecCtx, - denom: String, + scope: Scope, label: String, ) -> Result { nonpayable(&info.funds)?; @@ -349,14 +452,15 @@ impl Transmuter<'_> { // only admin can deregister limiter ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref()); + let scope_key = scope.key(); let attrs = vec![ ("method", "deregister_limiter"), - ("denom", &denom), + ("scope", &scope_key), ("label", &label), ]; // deregister limiter - self.limiters.deregister(deps.storage, &denom, &label)?; + self.limiters.deregister(deps.storage, scope, &label)?; Ok(Response::new().add_attributes(attrs)) } @@ -365,7 +469,7 @@ impl Transmuter<'_> { fn set_change_limiter_boundary_offset( &self, ExecCtx { deps, env: _, info }: ExecCtx, - denom: String, + scope: Scope, label: String, boundary_offset: Decimal, ) -> Result { @@ -375,9 +479,10 @@ impl Transmuter<'_> { ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref()); let boundary_offset_string = boundary_offset.to_string(); + let scope_key = scope.key(); let attrs = vec![ ("method", "set_change_limiter_boundary_offset"), - ("denom", &denom), + ("scope", &scope_key), ("label", &label), ("boundary_offset", boundary_offset_string.as_str()), ]; @@ -385,7 +490,7 @@ impl Transmuter<'_> { // set boundary offset self.limiters.set_change_limiter_boundary_offset( deps.storage, - &denom, + scope, &label, boundary_offset, )?; @@ -397,7 +502,7 @@ impl Transmuter<'_> { fn set_static_limiter_upper_limit( &self, ExecCtx { deps, env: _, info }: ExecCtx, - denom: String, + scope: Scope, label: String, upper_limit: Decimal, ) -> Result { @@ -407,16 +512,18 @@ impl Transmuter<'_> { ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref()); let upper_limit_string = upper_limit.to_string(); + let scope_key = scope.key(); + let attrs = vec![ ("method", "set_static_limiter_upper_limit"), - ("denom", &denom), + ("scope", &scope_key), ("label", &label), ("upper_limit", upper_limit_string.as_str()), ]; // set upper limit self.limiters - .set_static_limiter_upper_limit(deps.storage, &denom, &label, upper_limit)?; + .set_static_limiter_upper_limit(deps.storage, scope, &label, upper_limit)?; Ok(Response::new().add_attributes(attrs)) } @@ -557,6 +664,18 @@ impl Transmuter<'_> { Ok(ListLimitersResponse { limiters }) } + #[sv::msg(query)] + fn list_asset_groups( + &self, + QueryCtx { deps, env: _ }: QueryCtx, + ) -> Result { + let pool = self.pool.load(deps.storage)?; + + Ok(ListAssetGroupsResponse { + asset_groups: pool.asset_groups, + }) + } + #[sv::msg(query)] pub fn get_shares( &self, @@ -701,18 +820,26 @@ impl Transmuter<'_> { } #[sv::msg(query)] - pub(crate) fn get_corrupted_denoms( + pub(crate) fn get_corrupted_scopes( &self, QueryCtx { deps, env: _ }: QueryCtx, - ) -> Result { + ) -> Result { let pool = self.pool.load(deps.storage)?; - let corrupted_denoms = pool + + let corrupted_assets = pool .corrupted_assets() .into_iter() - .map(|a| a.denom().to_string()) - .collect(); + .map(|a| Scope::denom(a.denom())); + + let corrupted_asset_groups = pool + .asset_groups + .iter() + .filter(|(_, asset_group)| asset_group.is_corrupted()) + .map(|(label, _)| Scope::asset_group(label)); - Ok(GetCorrruptedDenomsResponse { corrupted_denoms }) + Ok(GetCorrruptedScopesResponse { + corrupted_scopes: corrupted_assets.chain(corrupted_asset_groups).collect(), + }) } // --- admin --- @@ -824,6 +951,11 @@ pub struct ListLimitersResponse { pub limiters: Vec<((String, String), Limiter)>, } +#[cw_serde] +pub struct ListAssetGroupsResponse { + pub asset_groups: BTreeMap, +} + #[cw_serde] pub struct GetSharesResponse { pub shares: Uint128, @@ -870,8 +1002,8 @@ pub struct CalcInAmtGivenOutResponse { } #[cw_serde] -pub struct GetCorrruptedDenomsResponse { - pub corrupted_denoms: Vec, +pub struct GetCorrruptedScopesResponse { + pub corrupted_scopes: Vec, } #[cw_serde] @@ -898,9 +1030,10 @@ mod tests { use crate::sudo::SudoMsg; use crate::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; use cosmwasm_std::{ - attr, from_json, BankMsg, BlockInfo, Storage, SubMsgResponse, SubMsgResult, Uint64, + attr, coin, from_json, BankMsg, Binary, BlockInfo, MsgResponse, Storage, SubMsgResponse, + SubMsgResult, Uint64, }; use osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgBurn; @@ -910,10 +1043,11 @@ mod tests { // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "tbtc"), Coin::new(1, "nbtc")]); + .bank + .update_balance("someone", vec![coin(1, "tbtc"), coin(1, "nbtc")]); - let admin = "admin"; - let moderator = "moderator"; + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("tbtc"), @@ -925,7 +1059,7 @@ mod tests { moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. let err = instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap_err(); @@ -942,19 +1076,21 @@ mod tests { fn test_add_new_assets() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + // make denom has non-zero total supply - deps.querier.update_balance( - "someone", + deps.querier.bank.update_balance( + someone, vec![ - Coin::new(1, "uosmo"), - Coin::new(1, "uion"), - Coin::new(1, "new_asset1"), - Coin::new(1, "new_asset2"), + coin(1, "uosmo"), + coin(1, "uion"), + coin(1, "new_asset1"), + coin(1, "new_asset2"), ], ); - let admin = "admin"; - let moderator = "moderator"; + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -966,7 +1102,7 @@ mod tests { moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); @@ -974,35 +1110,56 @@ mod tests { // Manually reply let alloyed_denom = "usomoion"; + let msg_create_denom_response = MsgCreateDenomResponse { + new_token_denom: alloyed_denom.to_string(), + }; + reply( deps.as_mut(), env.clone(), Reply { id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), + result: SubMsgResult::Ok( + #[allow(deprecated)] + SubMsgResponse { + events: vec![], + data: Some(msg_create_denom_response.clone().into()), // DEPRECATED + msg_responses: vec![MsgResponse { + type_url: MsgCreateDenomResponse::TYPE_URL.to_string(), + value: msg_create_denom_response.into(), + }], + }, + ), + payload: Binary::new(vec![]), + gas_used: 0, }, ) .unwrap(); // join pool - let info = mock_info( - "someone", - &[ - Coin::new(1000000000, "uosmo"), - Coin::new(1000000000, "uion"), - ], + let someone = deps.api.addr_make("someone"); + let info = message_info( + &someone, + &[coin(1000000000, "uosmo"), coin(1000000000, "uion")], ); let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); execute(deps.as_mut(), env.clone(), info.clone(), join_pool_msg).unwrap(); + // Create asset group + let create_asset_group_msg = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "group1".to_string(), + denoms: vec!["uosmo".to_string(), "uion".to_string()], + }); + + let info = message_info(&admin, &[]); + execute( + deps.as_mut(), + env.clone(), + info.clone(), + create_asset_group_msg, + ) + .unwrap(); + // set limiters let change_limiter_params = LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1016,10 +1173,25 @@ mod tests { upper_limit: Decimal::percent(60), }; - let info = mock_info(admin, &[]); + // Register limiter for the asset group + let register_group_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { + scope: Scope::AssetGroup("group1".to_string()), + label: "group_change_limiter".to_string(), + limiter_params: change_limiter_params.clone(), + }); + + execute( + deps.as_mut(), + env.clone(), + info.clone(), + register_group_limiter_msg, + ) + .unwrap(); + + let info = message_info(&admin, &[]); for denom in ["uosmo", "uion"] { let register_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: denom.to_string(), + scope: Scope::Denom(denom.to_string()), label: "change_limiter".to_string(), limiter_params: change_limiter_params.clone(), }); @@ -1033,7 +1205,7 @@ mod tests { .unwrap(); let register_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: denom.to_string(), + scope: Scope::Denom(denom.to_string()), label: "static_limiter".to_string(), limiter_params: static_limiter_params.clone(), }); @@ -1051,33 +1223,35 @@ mod tests { let mut env = env.clone(); env.block.time = env.block.time.plus_nanos(360); - let info = mock_info( - "someone", - &[Coin::new(550, "uosmo"), Coin::new(500, "uion")], - ); + let someone = deps.api.addr_make("someone"); + let info = message_info(&someone, &[coin(550, "uosmo"), coin(500, "uion")]); let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); execute(deps.as_mut(), env.clone(), info.clone(), join_pool_msg).unwrap(); env.block.time = env.block.time.plus_nanos(3000); - let info = mock_info( - "someone", - &[Coin::new(450, "uosmo"), Coin::new(500, "uion")], - ); + let info = message_info(&someone, &[coin(450, "uosmo"), coin(500, "uion")]); let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); execute(deps.as_mut(), env.clone(), info.clone(), join_pool_msg).unwrap(); for denom in ["uosmo", "uion"] { - assert_dirty_change_limiters_by_denom!( - denom, - Transmuter::default().limiters, + assert_dirty_change_limiters_by_scope!( + &Scope::denom(denom), + Transmuter::new().limiters, deps.as_ref().storage ); } + assert_dirty_change_limiters_by_scope!( + &Scope::asset_group("group1"), + Transmuter::new().limiters, + deps.as_ref().storage + ); + // Add new assets // Attempt to add assets with invalid denom - let info = mock_info(admin, &[]); + let admin = deps.api.addr_make("admin"); + let info = message_info(&admin, &[]); let invalid_denoms = vec!["invalid_asset1".to_string(), "invalid_asset2".to_string()]; let add_invalid_assets_msg = ContractExecMsg::Transmuter(ExecMsg::AddNewAssets { asset_configs: invalid_denoms @@ -1113,8 +1287,9 @@ mod tests { env.block.time = env.block.time.plus_nanos(360); + let non_admin = deps.api.addr_make("non_admin"); // Attempt to add assets by non-admin - let non_admin_info = mock_info("non_admin", &[]); + let non_admin_info = message_info(&non_admin, &[]); let res = execute( deps.as_mut(), env.clone(), @@ -1135,18 +1310,25 @@ mod tests { execute(deps.as_mut(), env.clone(), info, add_assets_msg).unwrap(); let reset_at = env.block.time; - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); // Reset change limiter states if new assets are added for denom in ["uosmo", "uion"] { - assert_reset_change_limiters_by_denom!( - denom, + assert_reset_change_limiters_by_scope!( + &Scope::denom(denom), reset_at, transmuter, deps.as_ref().storage ); } + assert_reset_change_limiters_by_scope!( + &Scope::asset_group("group1"), + reset_at, + transmuter, + deps.as_ref().storage + ); + env.block.time = env.block.time.plus_nanos(360); // Check if the new assets were added @@ -1163,10 +1345,10 @@ mod tests { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1000001000, "uosmo"), - Coin::new(1000001000, "uion"), - Coin::new(0, "new_asset1"), - Coin::new(0, "new_asset2"), + coin(1000001000, "uosmo"), + coin(1000001000, "uion"), + coin(0, "new_asset1"), + coin(0, "new_asset2"), ] ); } @@ -1175,19 +1357,20 @@ mod tests { fn test_corrupted_assets() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); // make denom has non-zero total supply - deps.querier.update_balance( - "someone", + deps.querier.bank.update_balance( + &someone, vec![ - Coin::new(1, "wbtc"), - Coin::new(1, "tbtc"), - Coin::new(1, "nbtc"), - Coin::new(1, "stbtc"), + coin(1, "wbtc"), + coin(1, "tbtc"), + coin(1, "nbtc"), + coin(1, "stbtc"), ], ); - let admin = "admin"; - let moderator = "moderator"; + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); let alloyed_subdenom = "btc"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ @@ -1204,26 +1387,14 @@ mod tests { let env = mock_env(); // Instantiate the contract. - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); // Manually reply - let res = reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_subdenom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_subdenom), ) .unwrap(); @@ -1245,9 +1416,9 @@ mod tests { }; // Mark corrupted assets by non-moderator - let info = mock_info("someone", &[]); - let mark_corrupted_assets_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedAssets { - denoms: vec!["wbtc".to_string(), "tbtc".to_string()], + let info = message_info(&someone, &[]); + let mark_corrupted_assets_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedScopes { + scopes: vec![Scope::denom("wbtc"), Scope::denom("tbtc")], }); let res = execute( @@ -1264,12 +1435,12 @@ mod tests { let res = query( deps.as_ref(), env.clone(), - ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedDenoms {}), + ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}), ) .unwrap(); - let GetCorrruptedDenomsResponse { corrupted_denoms } = from_json(res).unwrap(); + let GetCorrruptedScopesResponse { corrupted_scopes } = from_json(res).unwrap(); - assert_eq!(corrupted_denoms, Vec::::new()); + assert_eq!(corrupted_scopes, Vec::::new()); // The asset must not yet be removed let res = query( @@ -1285,23 +1456,25 @@ mod tests { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, "wbtc"), - Coin::new(0, "tbtc"), - Coin::new(0, "nbtc"), - Coin::new(0, "stbtc"), + coin(0, "wbtc"), + coin(0, "tbtc"), + coin(0, "nbtc"), + coin(0, "stbtc"), ] ); // provide some liquidity let liquidity = vec![ - Coin::new(1_000_000_000_000, "wbtc"), - Coin::new(1_000_000_000_000, "tbtc"), - Coin::new(1_000_000_000_000, "nbtc"), - Coin::new(1_000_000_000_000, "stbtc"), + coin(1_000_000_000_000, "wbtc"), + coin(1_000_000_000_000, "tbtc"), + coin(1_000_000_000_000, "nbtc"), + coin(1_000_000_000_000, "stbtc"), ]; - deps.querier.update_balance("someone", liquidity.clone()); + deps.querier + .bank + .update_balance("someone", liquidity.clone()); - let info = mock_info("someone", &liquidity); + let info = message_info(&someone, &liquidity); let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); execute(deps.as_mut(), env.clone(), info.clone(), join_pool_msg).unwrap(); @@ -1309,12 +1482,12 @@ mod tests { // set limiters for denom in ["wbtc", "tbtc", "nbtc", "stbtc"] { let register_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: denom.to_string(), + scope: Scope::Denom(denom.to_string()), label: "change_limiter".to_string(), limiter_params: change_limiter_params.clone(), }); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); execute( deps.as_mut(), env.clone(), @@ -1324,7 +1497,7 @@ mod tests { .unwrap(); let register_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: denom.to_string(), + scope: Scope::Denom(denom.to_string()), label: "static_limiter".to_string(), limiter_params: static_limiter_params.clone(), }); @@ -1337,23 +1510,54 @@ mod tests { .unwrap(); } + // Create asset group + let create_asset_group_msg = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "btc_group1".to_string(), + denoms: vec!["nbtc".to_string(), "stbtc".to_string()], + }); + + let info = message_info(&admin, &[]); + execute( + deps.as_mut(), + env.clone(), + info.clone(), + create_asset_group_msg, + ) + .unwrap(); + + // Register change limiter for the asset group + let register_group_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { + scope: Scope::AssetGroup("btc_group1".to_string()), + label: "group_change_limiter".to_string(), + limiter_params: change_limiter_params.clone(), + }); + + execute( + deps.as_mut(), + env.clone(), + info.clone(), + register_group_limiter_msg, + ) + .unwrap(); + // exit pool a bit to make sure the limiters are dirty deps.querier - .update_balance("someone", vec![Coin::new(1_000, alloyed_denom.clone())]); + .bank + .update_balance(&someone, vec![coin(1_000, alloyed_denom.clone())]); let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_000, "nbtc")], + tokens_out: vec![coin(1_000, "nbtc")], }); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); execute(deps.as_mut(), env.clone(), info.clone(), exit_pool_msg).unwrap(); // Mark corrupted assets by moderator - let corrupted_denoms = vec!["wbtc".to_string(), "tbtc".to_string()]; - let mark_corrupted_assets_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedAssets { - denoms: corrupted_denoms.clone(), + let corrupted_scopes = vec![Scope::denom("wbtc"), Scope::denom("tbtc")]; + let mark_corrupted_assets_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedScopes { + scopes: corrupted_scopes.clone(), }); - let info = mock_info(moderator, &[]); + let info = message_info(&moderator, &[]); let res = execute( deps.as_mut(), env.clone(), @@ -1368,12 +1572,15 @@ mod tests { let res = query( deps.as_ref(), env.clone(), - ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedDenoms {}), + ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}), ) .unwrap(); - let res: GetCorrruptedDenomsResponse = from_json(res).unwrap(); + let get_corrupted_scopes_response: GetCorrruptedScopesResponse = from_json(res).unwrap(); - assert_eq!(res.corrupted_denoms, corrupted_denoms); + assert_eq!( + get_corrupted_scopes_response.corrupted_scopes, + corrupted_scopes + ); // Check if the assets were removed let res = query( @@ -1389,66 +1596,89 @@ mod tests { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1_000_000_000_000, "wbtc"), - Coin::new(1_000_000_000_000, "tbtc"), - Coin::new(999_999_999_000, "nbtc"), - Coin::new(1_000_000_000_000, "stbtc"), + coin(1_000_000_000_000, "wbtc"), + coin(1_000_000_000_000, "tbtc"), + coin(999_999_999_000, "nbtc"), + coin(1_000_000_000_000, "stbtc"), ] ); // warm up the limiters let env = increase_block_height(&env, 1); deps.querier - .update_balance("someone", vec![Coin::new(4, alloyed_denom.clone())]); + .bank + .update_balance(&someone, vec![coin(4, alloyed_denom.clone())]); let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { tokens_out: vec![ - Coin::new(1, "wbtc"), - Coin::new(1, "tbtc"), - Coin::new(1, "nbtc"), - Coin::new(1, "stbtc"), + coin(1, "wbtc"), + coin(1, "tbtc"), + coin(1, "nbtc"), + coin(1, "stbtc"), ], }); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); execute(deps.as_mut(), env.clone(), info.clone(), exit_pool_msg).unwrap(); + for denom in ["wbtc", "tbtc", "nbtc", "stbtc"] { + assert_dirty_change_limiters_by_scope!( + &Scope::denom(denom), + Transmuter::new().limiters, + deps.as_ref().storage + ); + } + + assert_dirty_change_limiters_by_scope!( + &Scope::asset_group("btc_group1"), + Transmuter::new().limiters, + deps.as_ref().storage + ); + let env = increase_block_height(&env, 1); deps.querier - .update_balance("someone", vec![Coin::new(4, alloyed_denom.clone())]); + .bank + .update_balance("someone", vec![coin(4, alloyed_denom.clone())]); let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { tokens_out: vec![ - Coin::new(1, "wbtc"), - Coin::new(1, "tbtc"), - Coin::new(1, "nbtc"), - Coin::new(1, "stbtc"), + coin(1, "wbtc"), + coin(1, "tbtc"), + coin(1, "nbtc"), + coin(1, "stbtc"), ], }); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); execute(deps.as_mut(), env.clone(), info.clone(), exit_pool_msg).unwrap(); let env = increase_block_height(&env, 1); - for denom in corrupted_denoms { - let expected_err = ContractError::CorruptedAssetRelativelyIncreased { - denom: denom.clone(), + for scope in corrupted_scopes { + let expected_err = ContractError::CorruptedScopeRelativelyIncreased { + scope: scope.clone(), + }; + + let denom = match scope { + Scope::Denom(denom) => denom, + _ => unreachable!(), }; // join with corrupted denom should fail + let user = deps.api.addr_make("user"); let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); let err = execute( deps.as_mut(), env.clone(), - mock_info("user", &[Coin::new(1000, denom.clone())]), + message_info(&user, &[coin(1000, denom.clone())]), join_pool_msg, ) .unwrap_err(); assert_eq!(expected_err, err); + let mock_sender = deps.api.addr_make("mock_sender"); // swap exact in with corrupted denom as token in should fail let swap_msg = SudoMsg::SwapExactAmountIn { - token_in: Coin::new(1000, denom.clone()), + token_in: coin(1000, denom.clone()), swap_fee: Decimal::zero(), - sender: "mock_sender".to_string(), + sender: mock_sender.to_string(), token_out_denom: "nbtc".to_string(), token_out_min_amount: Uint128::new(500), }; @@ -1458,9 +1688,9 @@ mod tests { // swap exact in with corrupted denom as token out should be ok since it decreases the corrupted asset let swap_msg = SudoMsg::SwapExactAmountIn { - token_in: Coin::new(1000, "nbtc"), + token_in: coin(1000, "nbtc"), swap_fee: Decimal::zero(), - sender: "mock_sender".to_string(), + sender: mock_sender.to_string(), token_out_denom: denom.clone(), token_out_min_amount: Uint128::new(500), }; @@ -1469,8 +1699,8 @@ mod tests { // swap exact out with corrupted denom as token out should be ok since it decreases the corrupted asset let swap_msg = SudoMsg::SwapExactAmountOut { - sender: "mock_sender".to_string(), - token_out: Coin::new(500, denom.clone()), + sender: mock_sender.to_string(), + token_out: coin(500, denom.clone()), swap_fee: Decimal::zero(), token_in_denom: "nbtc".to_string(), token_in_max_amount: Uint128::new(1000), @@ -1480,8 +1710,8 @@ mod tests { // swap exact out with corrupted denom as token in should fail let swap_msg = SudoMsg::SwapExactAmountOut { - sender: "mock_sender".to_string(), - token_out: Coin::new(500, "nbtc"), + sender: mock_sender.to_string(), + token_out: coin(500, "nbtc"), swap_fee: Decimal::zero(), token_in_denom: denom.clone(), token_in_max_amount: Uint128::new(1000), @@ -1492,68 +1722,66 @@ mod tests { // exit with by any denom requires corrupted denom to not increase in weight // (this case increase other remaining corrupted denom weight) - deps.querier.update_balance( - "someone", - vec![Coin::new(4_000_000_000, alloyed_denom.clone())], - ); + deps.querier + .bank + .update_balance(&someone, vec![coin(4_000_000_000, alloyed_denom.clone())]); let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_000_000_000, "stbtc")], + tokens_out: vec![coin(1_000_000_000, "stbtc")], }); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); // this causes all corrupted denoms to be increased in weight let err = execute(deps.as_mut(), env.clone(), info, exit_pool_msg).unwrap_err(); assert!(matches!( err, - ContractError::CorruptedAssetRelativelyIncreased { .. } + ContractError::CorruptedScopeRelativelyIncreased { .. } )); let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { tokens_out: vec![ - Coin::new(1_000_000_000, "nbtc"), - Coin::new(1_000_000_000, denom.clone()), + coin(1_000_000_000, "nbtc"), + coin(1_000_000_000, denom.clone()), ], }); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); // this causes other corrupted denom to be increased relatively let err = execute(deps.as_mut(), env.clone(), info, exit_pool_msg).unwrap_err(); assert!(matches!( err, - ContractError::CorruptedAssetRelativelyIncreased { .. } + ContractError::CorruptedScopeRelativelyIncreased { .. } )); } // exit with corrupted denom requires all corrupted denom exit with the same value - deps.querier.update_balance( - "someone", - vec![Coin::new(4_000_000_000, alloyed_denom.clone())], - ); - let info = mock_info("someone", &[]); + deps.querier + .bank + .update_balance(&someone, vec![coin(4_000_000_000, alloyed_denom.clone())]); + let info = message_info(&someone, &[]); let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { tokens_out: vec![ - Coin::new(2_000_000_000, "nbtc"), - Coin::new(1_000_000_000, "wbtc"), - Coin::new(1_000_000_000, "tbtc"), + coin(2_000_000_000, "nbtc"), + coin(1_000_000_000, "wbtc"), + coin(1_000_000_000, "tbtc"), ], }); execute(deps.as_mut(), env.clone(), info, exit_pool_msg).unwrap(); // force redeem corrupted assets - deps.querier.update_balance( - "someone", - vec![Coin::new(1_000_000_000_000, alloyed_denom.clone())], // TODO: increase shares + deps.querier.bank.update_balance( + &someone, + vec![coin(1_000_000_000_000, alloyed_denom.clone())], ); let all_nbtc = total_liquidity_of("nbtc", &deps.storage); let force_redeem_corrupted_assets_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { tokens_out: vec![all_nbtc], }); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); let err = execute( deps.as_mut(), env.clone(), @@ -1564,8 +1792,8 @@ mod tests { assert_eq!( err, - ContractError::CorruptedAssetRelativelyIncreased { - denom: "wbtc".to_string() + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::denom("wbtc") } ); @@ -1574,12 +1802,12 @@ mod tests { tokens_out: vec![all_wbtc], }); - deps.querier.update_balance( - "someone", - vec![Coin::new(1_000_000_000_000, alloyed_denom.clone())], + deps.querier.bank.update_balance( + &someone, + vec![coin(1_000_000_000_000, alloyed_denom.clone())], ); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); execute( deps.as_mut(), env.clone(), @@ -1604,36 +1832,43 @@ mod tests { assert_eq!( total_pool_liquidity, vec![ - Coin::new(998999998498, "tbtc"), - Coin::new(998000001998, "nbtc"), - Coin::new(999999999998, "stbtc"), + coin(998999998498, "tbtc"), + coin(998000001998, "nbtc"), + coin(999999999998, "stbtc"), ] ); assert_eq!( - Transmuter::default() + Transmuter::new() .limiters - .list_limiters_by_denom(&deps.storage, "wbtc") + .list_limiters_by_scope(&deps.storage, &Scope::denom("wbtc")) .unwrap(), vec![] ); for denom in ["tbtc", "nbtc", "stbtc"] { - assert_reset_change_limiters_by_denom!( - denom, + assert_reset_change_limiters_by_scope!( + &Scope::denom(denom), env.block.time, - Transmuter::default(), + Transmuter::new(), deps.as_ref().storage ); } + assert_reset_change_limiters_by_scope!( + &Scope::asset_group("btc_group1"), + env.block.time, + Transmuter::new(), + deps.as_ref().storage + ); + // try unmark nbtc should fail let unmark_corrupted_assets_msg = - ContractExecMsg::Transmuter(ExecMsg::UnmarkCorruptedAssets { - denoms: vec!["nbtc".to_string()], + ContractExecMsg::Transmuter(ExecMsg::UnmarkCorruptedScopes { + scopes: vec![Scope::denom("nbtc")], }); - let info = mock_info(moderator, &[]); + let info = message_info(&moderator, &[]); let err = execute( deps.as_mut(), env.clone(), @@ -1651,11 +1886,11 @@ mod tests { // unmark tbtc by non moderator should fail let unmark_corrupted_assets_msg = - ContractExecMsg::Transmuter(ExecMsg::UnmarkCorruptedAssets { - denoms: vec!["tbtc".to_string()], + ContractExecMsg::Transmuter(ExecMsg::UnmarkCorruptedScopes { + scopes: vec![Scope::denom("tbtc")], }); - let info = mock_info("someone", &[]); + let info = message_info(&someone, &[]); let err = execute( deps.as_mut(), env.clone(), @@ -1668,11 +1903,11 @@ mod tests { // unmark tbtc let unmark_corrupted_assets_msg = - ContractExecMsg::Transmuter(ExecMsg::UnmarkCorruptedAssets { - denoms: vec!["tbtc".to_string()], + ContractExecMsg::Transmuter(ExecMsg::UnmarkCorruptedScopes { + scopes: vec![Scope::denom("tbtc")], }); - let info = mock_info(moderator, &[]); + let info = message_info(&moderator, &[]); execute( deps.as_mut(), env.clone(), @@ -1685,13 +1920,13 @@ mod tests { let res = query( deps.as_ref(), env.clone(), - ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedDenoms {}), + ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}), ) .unwrap(); - let GetCorrruptedDenomsResponse { corrupted_denoms } = from_json(res).unwrap(); + let GetCorrruptedScopesResponse { corrupted_scopes } = from_json(res).unwrap(); - assert_eq!(corrupted_denoms, Vec::::new()); + assert_eq!(corrupted_scopes, Vec::::new()); // no liquidity or pool assets changes let GetTotalPoolLiquidityResponse { @@ -1709,23 +1944,220 @@ mod tests { assert_eq!( total_pool_liquidity, vec![ - Coin::new(998999998498, "tbtc"), - Coin::new(998000001998, "nbtc"), - Coin::new(999999999998, "stbtc"), + coin(998999998498, "tbtc"), + coin(998000001998, "nbtc"), + coin(999999999998, "stbtc"), ] ); // still has all the limiters assert_eq!( - Transmuter::default() + Transmuter::new() .limiters - .list_limiters_by_denom(&deps.storage, "tbtc") + .list_limiters_by_scope(&deps.storage, &Scope::denom("tbtc")) .unwrap() .len(), 2 ); } + #[test] + fn test_corrupted_asset_group() { + let mut deps = mock_dependencies(); + let admin = deps.api.addr_make("admin"); + let user = deps.api.addr_make("user"); + let moderator = deps.api.addr_make("moderator"); + + let info = message_info(&admin, &[]); + + deps.querier.bank.update_balance( + &admin, + vec![ + coin(1_000_000_000_000, "tbtc"), + coin(1_000_000_000_000, "nbtc"), + coin(1_000_000_000_000, "stbtc"), + ], + ); + + let env = mock_env(); + + // Initialize contract with asset group + let init_msg = InstantiateMsg { + pool_asset_configs: vec![ + AssetConfig::from_denom_str("tbtc"), + AssetConfig::from_denom_str("nbtc"), + AssetConfig::from_denom_str("stbtc"), + ], + alloyed_asset_subdenom: "btc".to_string(), + alloyed_asset_normalization_factor: Uint128::one(), + admin: Some(admin.to_string()), + moderator: moderator.to_string(), + }; + + instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); + + // Manually reply + let res = reply( + deps.as_mut(), + env.clone(), + reply_create_denom_response("btc"), + ) + .unwrap(); + + let alloyed_denom = res + .attributes + .into_iter() + .find(|attr| attr.key == "alloyed_denom") + .unwrap() + .value; + + deps.querier + .bank + .update_balance(&user, vec![coin(3_000_000_000_000, alloyed_denom.clone())]); + + // Create asset group + let create_group_msg = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "group1".to_string(), + denoms: vec!["tbtc".to_string(), "nbtc".to_string()], + }); + execute(deps.as_mut(), env.clone(), info.clone(), create_group_msg).unwrap(); + + // Set change limiter for btc group + let info = message_info(&admin, &[]); + let set_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { + scope: Scope::asset_group("group1"), + label: "big_change_limiter".to_string(), + limiter_params: LimiterParams::ChangeLimiter { + window_config: WindowConfig { + window_size: Uint64::from(3600000000000u64), // 1 hour in nanoseconds + division_count: Uint64::from(6u64), + }, + boundary_offset: Decimal::percent(20), + }, + }); + execute(deps.as_mut(), env.clone(), info.clone(), set_limiter_msg).unwrap(); + + // set change limiter for stbtc + let set_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { + scope: Scope::denom("stbtc"), + label: "big_change_limiter".to_string(), + limiter_params: LimiterParams::ChangeLimiter { + window_config: WindowConfig { + window_size: Uint64::from(3600000000000u64), // 1 hour in nanoseconds + division_count: Uint64::from(6u64), + }, + boundary_offset: Decimal::percent(20), + }, + }); + execute(deps.as_mut(), env.clone(), info.clone(), set_limiter_msg).unwrap(); + + // Add some liquidity + let add_liquidity_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); + execute( + deps.as_mut(), + env.clone(), + message_info( + &user, + &[ + coin(1_000_000_000_000, "tbtc"), + coin(1_000_000_000_000, "nbtc"), + coin(1_000_000_000_000, "stbtc"), + ], + ), + add_liquidity_msg, + ) + .unwrap(); + + // Assert dirty change limiters for the asset group + assert_dirty_change_limiters_by_scope!( + &Scope::asset_group("group1"), + &Transmuter::new().limiters, + &deps.storage + ); + + // Mark asset group as corrupted + let info = message_info(&moderator, &[]); + let mark_corrupted_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedScopes { + scopes: vec![Scope::asset_group("group1")], + }); + execute(deps.as_mut(), env.clone(), info.clone(), mark_corrupted_msg).unwrap(); + + // Query corrupted scopes + let res = query( + deps.as_ref(), + env.clone(), + ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}), + ) + .unwrap(); + + let GetCorrruptedScopesResponse { corrupted_scopes } = from_json(res).unwrap(); + + assert_eq!(corrupted_scopes, vec![Scope::asset_group("group1")]); + + // Exit pool with all corrupted assets + let env = increase_block_height(&env, 1); + let info = message_info(&user, &[]); + let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { + tokens_out: vec![ + coin(1_000_000_000_000, "tbtc"), + coin(1_000_000_000_000, "nbtc"), + ], + }); + execute(deps.as_mut(), env.clone(), info.clone(), exit_pool_msg).unwrap(); + + // Assert reset change limiters for the asset group + assert_reset_change_limiters_by_scope!( + &Scope::asset_group("group1"), + env.block.time, + Transmuter::new(), + &deps.storage + ); + + // Query corrupted scopes again to ensure the asset group is no longer corrupted + let res = query( + deps.as_ref(), + env.clone(), + ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}), + ) + .unwrap(); + + let GetCorrruptedScopesResponse { corrupted_scopes } = from_json(res).unwrap(); + + assert!( + corrupted_scopes.is_empty(), + "Corrupted scopes should be empty after exiting pool" + ); + + let msg = ContractQueryMsg::Transmuter(QueryMsg::GetTotalPoolLiquidity {}); + let res = query(deps.as_ref(), env.clone(), msg).unwrap(); + let GetTotalPoolLiquidityResponse { + total_pool_liquidity, + } = from_json(res).unwrap(); + + assert_eq!(total_pool_liquidity, vec![coin(1_000_000_000_000, "stbtc")]); + + // Assert that only one limiter remains for stbtc + let limiters = Transmuter::new() + .limiters + .list_limiters(&deps.storage) + .unwrap() + .into_iter() + .map(|(k, _)| k) + .collect::>(); + assert_eq!( + limiters, + vec![("denom::stbtc".to_string(), "big_change_limiter".to_string())] + ); + + // Assert reset change limiters for the individual assets + assert_reset_change_limiters_by_scope!( + &Scope::denom("stbtc"), + env.block.time, + Transmuter::new(), + &deps.storage + ); + } + fn increase_block_height(env: &Env, height: u64) -> Env { let block_time = 5; // hypothetical block time Env { @@ -1739,7 +2171,7 @@ mod tests { } fn total_liquidity_of(denom: &str, storage: &dyn Storage) -> Coin { - Transmuter::default() + Transmuter::new() .pool .load(storage) .unwrap() @@ -1754,12 +2186,16 @@ mod tests { fn test_set_active_status() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + let user = deps.api.addr_make("user"); + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); + let non_moderator = deps.api.addr_make("non_moderator"); // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance(&someone, vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; - let moderator = "moderator"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -1771,7 +2207,7 @@ mod tests { moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -1779,7 +2215,7 @@ mod tests { // Manually set alloyed denom let alloyed_denom = "uosmo".to_string(); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &alloyed_denom) @@ -1796,18 +2232,25 @@ mod tests { assert!(active_status.is_active); // Attempt to set the active status by a non-admin user. - let non_admin_info = mock_info("non_moderator", &[]); - let non_admin_msg = ContractExecMsg::Transmuter(ExecMsg::SetActiveStatus { active: false }); - let err = execute(deps.as_mut(), env.clone(), non_admin_info, non_admin_msg).unwrap_err(); - - assert_eq!(err, ContractError::Unauthorized {}); + let non_moderator_info = message_info(&non_moderator, &[]); + let non_moderator_msg = + ContractExecMsg::Transmuter(ExecMsg::SetActiveStatus { active: false }); + let err = execute( + deps.as_mut(), + env.clone(), + non_moderator_info, + non_moderator_msg, + ) + .unwrap_err(); + + assert_eq!(err, ContractError::Unauthorized {}); // Set the active status to false. let msg = ContractExecMsg::Transmuter(ExecMsg::SetActiveStatus { active: false }); execute( deps.as_mut(), env.clone(), - mock_info(moderator, &[]), + message_info(&moderator, &[]), msg.clone(), ) .unwrap(); @@ -1823,7 +2266,13 @@ mod tests { assert!(!active_status.is_active); // try to set the active status to false again - let err = execute(deps.as_mut(), env.clone(), mock_info(moderator, &[]), msg).unwrap_err(); + let err = execute( + deps.as_mut(), + env.clone(), + message_info(&moderator, &[]), + msg, + ) + .unwrap_err(); assert_eq!(err, ContractError::UnchangedActiveStatus { status: false }); // Test that JoinPool is blocked when active status is false @@ -1831,7 +2280,7 @@ mod tests { let err = execute( deps.as_mut(), env.clone(), - mock_info("user", &[Coin::new(1000, "uion"), Coin::new(1000, "uosmo")]), + message_info(&user, &[coin(1000, "uion"), coin(1000, "uosmo")]), join_pool_msg, ) .unwrap_err(); @@ -1839,7 +2288,7 @@ mod tests { // Test that SwapExactAmountIn is blocked when active status is false let swap_exact_amount_in_msg = SudoMsg::SwapExactAmountIn { - token_in: Coin::new(1000, "uion"), + token_in: coin(1000, "uion"), swap_fee: Decimal::zero(), sender: "mock_sender".to_string(), token_out_denom: "uosmo".to_string(), @@ -1851,7 +2300,7 @@ mod tests { // Test that SwapExactAmountOut is blocked when active status is false let swap_exact_amount_out_msg = SudoMsg::SwapExactAmountOut { sender: "mock_sender".to_string(), - token_out: Coin::new(500, "uosmo"), + token_out: coin(500, "uosmo"), swap_fee: Decimal::zero(), token_in_denom: "uion".to_string(), token_in_max_amount: Uint128::new(1000), @@ -1861,12 +2310,12 @@ mod tests { // Test that ExitPool is blocked when active status is false let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1000, "uion"), Coin::new(1000, "uosmo")], + tokens_out: vec![coin(1000, "uion"), coin(1000, "uosmo")], }); let err = execute( deps.as_mut(), env.clone(), - mock_info("user", &[Coin::new(1000, "uion"), Coin::new(1000, "uosmo")]), + message_info(&user, &[coin(1000, "uion"), coin(1000, "uosmo")]), exit_pool_msg, ) .unwrap_err(); @@ -1874,7 +2323,13 @@ mod tests { // Set the active status back to true let msg = ContractExecMsg::Transmuter(ExecMsg::SetActiveStatus { active: true }); - execute(deps.as_mut(), env.clone(), mock_info(moderator, &[]), msg).unwrap(); + execute( + deps.as_mut(), + env.clone(), + message_info(&moderator, &[]), + msg, + ) + .unwrap(); // Check the active status again. let res = query( @@ -1891,26 +2346,29 @@ mod tests { let res = execute( deps.as_mut(), env.clone(), - mock_info("user", &[Coin::new(1000, "uion"), Coin::new(1000, "uosmo")]), + message_info(&user, &[coin(1000, "uion"), coin(1000, "uosmo")]), join_pool_msg, ); assert!(res.is_ok()); + let mock_sender = deps.api.addr_make("mock_sender"); + // Test that SwapExactAmountIn is active when active status is true let swap_exact_amount_in_msg = SudoMsg::SwapExactAmountIn { - token_in: Coin::new(100, "uion"), + token_in: coin(100, "uion"), swap_fee: Decimal::zero(), - sender: "mock_sender".to_string(), + sender: mock_sender.to_string(), token_out_denom: "uosmo".to_string(), token_out_min_amount: Uint128::new(100), }; let res = sudo(deps.as_mut(), env.clone(), swap_exact_amount_in_msg); assert!(res.is_ok()); + let mock_sender = deps.api.addr_make("mock_sender"); // Test that SwapExactAmountOut is active when active status is true let swap_exact_amount_out_msg = SudoMsg::SwapExactAmountOut { - sender: "mock_sender".to_string(), - token_out: Coin::new(100, "uosmo"), + sender: mock_sender.into_string(), + token_out: coin(100, "uosmo"), swap_fee: Decimal::zero(), token_in_denom: "uion".to_string(), token_in_max_amount: Uint128::new(100), @@ -1963,13 +2421,14 @@ mod tests { // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); - - let admin = "admin"; - let moderator = "moderator"; - let canceling_candidate = "canceling_candidate"; - let rejecting_candidate = "rejecting_candidate"; - let candidate = "candidate"; + .bank + .update_balance("someone", vec![coin(1, "uosmo"), coin(1, "uion")]); + + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); + let canceling_candidate = deps.api.addr_make("canceling_candidate"); + let rejecting_candidate = deps.api.addr_make("rejecting_candidate"); + let candidate = deps.api.addr_make("candidate"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -1981,7 +2440,7 @@ mod tests { moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); @@ -2002,7 +2461,7 @@ mod tests { let admin_candidate: GetAdminCandidateResponse = from_json(res).unwrap(); assert_eq!( admin_candidate.admin_candidate.unwrap().as_str(), - canceling_candidate + canceling_candidate.as_str() ); // Cancel admin rights transfer @@ -2012,7 +2471,7 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info(admin, &[]), + message_info(&admin, &[]), cancel_admin_transfer_msg, ) .unwrap(); @@ -2043,7 +2502,7 @@ mod tests { let admin_candidate: GetAdminCandidateResponse = from_json(res).unwrap(); assert_eq!( admin_candidate.admin_candidate.unwrap().as_str(), - rejecting_candidate + rejecting_candidate.as_str() ); // Reject admin rights transfer @@ -2053,7 +2512,7 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info(rejecting_candidate, &[]), + message_info(&rejecting_candidate, &[]), reject_admin_transfer_msg, ) .unwrap(); @@ -2082,14 +2541,17 @@ mod tests { ) .unwrap(); let admin_candidate: GetAdminCandidateResponse = from_json(res).unwrap(); - assert_eq!(admin_candidate.admin_candidate.unwrap().as_str(), candidate); + assert_eq!( + admin_candidate.admin_candidate.unwrap().as_str(), + candidate.as_str() + ); // Claim admin rights by the candidate let claim_admin_msg = ContractExecMsg::Transmuter(ExecMsg::ClaimAdmin {}); execute( deps.as_mut(), env.clone(), - mock_info(candidate, &[]), + message_info(&candidate, &[]), claim_admin_msg, ) .unwrap(); @@ -2102,19 +2564,20 @@ mod tests { ) .unwrap(); let admin: GetAdminResponse = from_json(res).unwrap(); - assert_eq!(admin.admin.as_str(), candidate); + assert_eq!(admin.admin.as_str(), candidate.as_str()); } #[test] fn test_assign_and_remove_moderator() { - let admin = "admin"; - let moderator = "moderator"; - let mut deps = mock_dependencies(); + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); + let someone = deps.api.addr_make("someone"); // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance(someone, vec![coin(1, "uosmo"), coin(1, "uion")]); // Instantiate the contract. let init_msg = InstantiateMsg { @@ -2127,7 +2590,13 @@ mod tests { alloyed_asset_normalization_factor: Uint128::one(), moderator: moderator.to_string(), }; - instantiate(deps.as_mut(), mock_env(), mock_info(admin, &[]), init_msg).unwrap(); + instantiate( + deps.as_mut(), + mock_env(), + message_info(&admin, &[]), + init_msg, + ) + .unwrap(); // Check the current moderator let res = query( @@ -2137,15 +2606,19 @@ mod tests { ) .unwrap(); let moderator_response: GetModeratorResponse = from_json(res).unwrap(); - assert_eq!(moderator_response.moderator, moderator); + assert_eq!( + moderator_response.moderator.into_string(), + moderator.into_string() + ); - let new_moderator = "new_moderator"; + let new_moderator = deps.api.addr_make("new_moderator"); + let non_admin = deps.api.addr_make("non_admin"); // Try to assign new moderator by non admin let err = execute( deps.as_mut(), mock_env(), - mock_info("non_admin", &[]), + message_info(&non_admin, &[]), ContractExecMsg::Transmuter(ExecMsg::AssignModerator { address: new_moderator.to_string(), }), @@ -2158,7 +2631,7 @@ mod tests { execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::AssignModerator { address: new_moderator.to_string(), }), @@ -2173,7 +2646,10 @@ mod tests { ) .unwrap(); let moderator_response: GetModeratorResponse = from_json(res).unwrap(); - assert_eq!(moderator_response.moderator, new_moderator); + assert_eq!( + moderator_response.moderator.to_string(), + new_moderator.to_string() + ); } #[test] @@ -2183,30 +2659,38 @@ mod tests { // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance("someone", vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; - let user = "user"; + let admin = deps.api.addr_make("admin"); + let user = deps.api.addr_make("user"); + let moderator = deps.api.addr_make("moderator"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), AssetConfig::from_denom_str("uion"), ], admin: Some(admin.to_string()), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), alloyed_asset_subdenom: "usomoion".to_string(), alloyed_asset_normalization_factor: Uint128::one(), }; - instantiate(deps.as_mut(), mock_env(), mock_info(admin, &[]), init_msg).unwrap(); + instantiate( + deps.as_mut(), + mock_env(), + message_info(&admin, &[]), + init_msg, + ) + .unwrap(); // normal user can't register limiter let err = execute( deps.as_mut(), mock_env(), - mock_info(user, &[]), + message_info(&user, &[]), ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1h".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -2224,9 +2708,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(user, &[]), + message_info(&user, &[]), ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1h".to_string(), limiter_params: LimiterParams::StaticLimiter { upper_limit: Decimal::percent(60), @@ -2245,9 +2729,9 @@ mod tests { let res = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1h".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: window_config_1h.clone(), @@ -2259,8 +2743,8 @@ mod tests { let attrs = vec![ attr("method", "register_limiter"), - attr("denom", "uosmo"), attr("label", "1h"), + attr("scope", "denom::uosmo"), attr("limiter_type", "change_limiter"), attr("window_size", "3600000000000"), attr("division_count", "5"), @@ -2273,9 +2757,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: "invalid_denom".to_string(), + scope: Scope::Denom("invalid_denom".to_string()), label: "1h".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: window_config_1h.clone(), @@ -2300,7 +2784,7 @@ mod tests { assert_eq!( limiters.limiters, vec![( - (String::from("uosmo"), String::from("1h")), + (Scope::denom("uosmo").key(), String::from("1h")), Limiter::ChangeLimiter( ChangeLimiter::new(window_config_1h.clone(), Decimal::percent(1)).unwrap() ) @@ -2314,9 +2798,9 @@ mod tests { let res = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1w".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: window_config_1w.clone(), @@ -2328,8 +2812,8 @@ mod tests { let attrs_1w = vec![ attr("method", "register_limiter"), - attr("denom", "uosmo"), attr("label", "1w"), + attr("scope", "denom::uosmo"), attr("limiter_type", "change_limiter"), attr("window_size", "604800000000"), attr("division_count", "5"), @@ -2347,13 +2831,13 @@ mod tests { limiters.limiters, vec![ ( - (String::from("uosmo"), String::from("1h")), + (Scope::denom("uosmo").key(), String::from("1h")), Limiter::ChangeLimiter( ChangeLimiter::new(window_config_1h, Decimal::percent(1)).unwrap() ) ), ( - (String::from("uosmo"), String::from("1w")), + (Scope::denom("uosmo").key(), String::from("1w")), Limiter::ChangeLimiter( ChangeLimiter::new(window_config_1w.clone(), Decimal::percent(1)).unwrap() ) @@ -2365,9 +2849,9 @@ mod tests { let res = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "static".to_string(), limiter_params: LimiterParams::StaticLimiter { upper_limit: Decimal::percent(60), @@ -2378,8 +2862,8 @@ mod tests { let attrs = vec![ attr("method", "register_limiter"), - attr("denom", "uosmo"), attr("label", "static"), + attr("scope", "denom::uosmo"), attr("limiter_type", "static_limiter"), attr("upper_limit", "0.6"), ]; @@ -2390,9 +2874,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(user, &[]), + message_info(&user, &[]), ContractExecMsg::Transmuter(ExecMsg::DeregisterLimiter { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1h".to_string(), }), ) @@ -2404,9 +2888,9 @@ mod tests { let res = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::DeregisterLimiter { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1h".to_string(), }), ) @@ -2414,7 +2898,7 @@ mod tests { let attrs = vec![ attr("method", "deregister_limiter"), - attr("denom", "uosmo"), + attr("scope", "denom::uosmo"), attr("label", "1h"), ]; @@ -2429,13 +2913,13 @@ mod tests { limiters.limiters, vec![ ( - (String::from("uosmo"), String::from("1w")), + (Scope::denom("uosmo").key(), String::from("1w")), Limiter::ChangeLimiter( ChangeLimiter::new(window_config_1w.clone(), Decimal::percent(1)).unwrap() ) ), ( - (String::from("uosmo"), String::from("static")), + (Scope::denom("uosmo").key(), String::from("static")), Limiter::StaticLimiter(StaticLimiter::new(Decimal::percent(60)).unwrap()) ) ] @@ -2445,9 +2929,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(user, &[]), + message_info(&user, &[]), ContractExecMsg::Transmuter(ExecMsg::SetChangeLimiterBoundaryOffset { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1w".to_string(), boundary_offset: Decimal::zero(), }), @@ -2460,9 +2944,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::SetChangeLimiterBoundaryOffset { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1h".to_string(), boundary_offset: Decimal::zero(), }), @@ -2472,7 +2956,7 @@ mod tests { assert_eq!( err, ContractError::LimiterDoesNotExist { - denom: "uosmo".to_string(), + scope: Scope::denom("uosmo"), label: "1h".to_string() } ); @@ -2481,9 +2965,9 @@ mod tests { let res = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::SetChangeLimiterBoundaryOffset { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1w".to_string(), boundary_offset: Decimal::percent(10), }), @@ -2492,7 +2976,7 @@ mod tests { let attrs = vec![ attr("method", "set_change_limiter_boundary_offset"), - attr("denom", "uosmo"), + attr("scope", "denom::uosmo"), attr("label", "1w"), attr("boundary_offset", "0.1"), ]; @@ -2508,13 +2992,13 @@ mod tests { limiters.limiters, vec![ ( - (String::from("uosmo"), String::from("1w")), + (Scope::denom("uosmo").key(), String::from("1w")), Limiter::ChangeLimiter( ChangeLimiter::new(window_config_1w.clone(), Decimal::percent(10)).unwrap() ) ), ( - (String::from("uosmo"), String::from("static")), + (Scope::denom("uosmo").key(), String::from("static")), Limiter::StaticLimiter(StaticLimiter::new(Decimal::percent(60)).unwrap()) ) ] @@ -2524,9 +3008,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(user, &[]), + message_info(&user, &[]), ContractExecMsg::Transmuter(ExecMsg::SetStaticLimiterUpperLimit { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "static".to_string(), upper_limit: Decimal::percent(50), }), @@ -2539,9 +3023,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::SetStaticLimiterUpperLimit { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "1h".to_string(), upper_limit: Decimal::percent(50), }), @@ -2551,7 +3035,7 @@ mod tests { assert_eq!( err, ContractError::LimiterDoesNotExist { - denom: "uosmo".to_string(), + scope: Scope::denom("uosmo"), label: "1h".to_string() } ); @@ -2560,9 +3044,9 @@ mod tests { let err = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::SetStaticLimiterUpperLimit { - denom: "uosmo".to_string(), + scope: Scope::denom("uosmo"), label: "1w".to_string(), upper_limit: Decimal::percent(50), }), @@ -2581,9 +3065,9 @@ mod tests { let res = execute( deps.as_mut(), mock_env(), - mock_info(admin, &[]), + message_info(&admin, &[]), ContractExecMsg::Transmuter(ExecMsg::SetStaticLimiterUpperLimit { - denom: "uosmo".to_string(), + scope: Scope::Denom("uosmo".to_string()), label: "static".to_string(), upper_limit: Decimal::percent(50), }), @@ -2592,7 +3076,7 @@ mod tests { let attrs = vec![ attr("method", "set_static_limiter_upper_limit"), - attr("denom", "uosmo"), + attr("scope", "denom::uosmo"), attr("label", "static"), attr("upper_limit", "0.5"), ]; @@ -2608,13 +3092,13 @@ mod tests { limiters.limiters, vec![ ( - (String::from("uosmo"), String::from("1w")), + (Scope::denom("uosmo").key(), String::from("1w")), Limiter::ChangeLimiter( ChangeLimiter::new(window_config_1w, Decimal::percent(10)).unwrap() ) ), ( - (String::from("uosmo"), String::from("static")), + (Scope::denom("uosmo").key(), String::from("static")), Limiter::StaticLimiter(StaticLimiter::new(Decimal::percent(50)).unwrap()) ) ] @@ -2627,10 +3111,12 @@ mod tests { // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance("someone", vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; - let non_admin = "non_admin"; + let admin = deps.api.addr_make("admin"); + let non_admin = deps.api.addr_make("non_admin"); + let moderator = deps.api.addr_make("moderator"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -2638,11 +3124,11 @@ mod tests { ], alloyed_asset_subdenom: "uosmouion".to_string(), admin: Some(admin.to_string()), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), alloyed_asset_normalization_factor: Uint128::one(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); @@ -2659,7 +3145,7 @@ mod tests { }; // Attempt to set alloyed denom metadata by a non-admin user. - let non_admin_info = mock_info(non_admin, &[]); + let non_admin_info = message_info(&non_admin, &[]); let non_admin_msg = ContractExecMsg::Transmuter(ExecMsg::SetAlloyedDenomMetadata { metadata: metadata.clone(), }); @@ -2693,10 +3179,12 @@ mod tests { // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance("someone", vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; - let user = "user"; + let admin = deps.api.addr_make("admin"); + let user = deps.api.addr_make("user"); + let moderator = deps.api.addr_make("moderator"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -2705,10 +3193,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "usomoion".to_string(), alloyed_asset_normalization_factor: Uint128::one(), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -2719,31 +3207,20 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); // join pool with amount 0 coin should error let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); - let info = mock_info(user, &[Coin::new(1000, "uion"), Coin::new(0, "uosmo")]); + let info = message_info(&user, &[coin(1000, "uion"), coin(0, "uosmo")]); let err = execute(deps.as_mut(), env.clone(), info, join_pool_msg).unwrap_err(); assert_eq!(err, ContractError::ZeroValueOperation {}); // join pool properly works let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); - let info = mock_info(user, &[Coin::new(1000, "uion"), Coin::new(1000, "uosmo")]); + let info = message_info(&user, &[coin(1000, "uion"), coin(1000, "uosmo")]); execute(deps.as_mut(), env.clone(), info, join_pool_msg).unwrap(); // Check pool asset @@ -2760,20 +3237,22 @@ mod tests { .unwrap(); assert_eq!( total_pool_liquidity, - vec![Coin::new(1000, "uosmo"), Coin::new(1000, "uion")] + vec![coin(1000, "uosmo"), coin(1000, "uion")] ); } #[test] fn test_exit_pool() { let mut deps = mock_dependencies(); - + let someone = deps.api.addr_make("someone"); + let admin = deps.api.addr_make("admin"); + let user = deps.api.addr_make("user"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance(someone, vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; - let user = "user"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -2782,10 +3261,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "usomoion".to_string(), alloyed_asset_normalization_factor: Uint128::one(), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -2796,34 +3275,23 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); // join pool by others for sufficient amount let join_pool_msg = ContractExecMsg::Transmuter(ExecMsg::JoinPool {}); - let info = mock_info(admin, &[Coin::new(1000, "uion"), Coin::new(1000, "uosmo")]); + let info = message_info(&admin, &[coin(1000, "uion"), coin(1000, "uosmo")]); execute(deps.as_mut(), env.clone(), info, join_pool_msg).unwrap(); // User tries to exit pool let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1000, "uion"), Coin::new(1000, "uosmo")], + tokens_out: vec![coin(1000, "uion"), coin(1000, "uosmo")], }); let err = execute( deps.as_mut(), env.clone(), - mock_info(user, &[]), + message_info(&user, &[]), exit_pool_msg, ) .unwrap_err(); @@ -2840,22 +3308,23 @@ mod tests { let res = execute( deps.as_mut(), env.clone(), - mock_info(user, &[Coin::new(1000, "uion"), Coin::new(1000, "uosmo")]), + message_info(&user, &[coin(1000, "uion"), coin(1000, "uosmo")]), join_pool_msg, ); assert!(res.is_ok()); deps.querier - .update_balance(user, vec![Coin::new(2000, alloyed_denom)]); + .bank + .update_balance(&user, vec![coin(2000, alloyed_denom)]); // User tries to exit pool with zero amount let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { - tokens_out: vec![Coin::new(0, "uion"), Coin::new(1, "uosmo")], + tokens_out: vec![coin(0, "uion"), coin(1, "uosmo")], }); let err = execute( deps.as_mut(), env.clone(), - mock_info(user, &[]), + message_info(&user, &[]), exit_pool_msg, ) .unwrap_err(); @@ -2863,12 +3332,12 @@ mod tests { // User tries to exit pool again let exit_pool_msg = ContractExecMsg::Transmuter(ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1000, "uion"), Coin::new(1000, "uosmo")], + tokens_out: vec![coin(1000, "uion"), coin(1000, "uosmo")], }); let res = execute( deps.as_mut(), env.clone(), - mock_info(user, &[]), + message_info(&user, &[]), exit_pool_msg, ) .unwrap(); @@ -2877,12 +3346,12 @@ mod tests { .add_attribute("method", "exit_pool") .add_message(MsgBurn { sender: env.contract.address.to_string(), - amount: Some(Coin::new(2000u128, alloyed_denom).into()), + amount: Some(coin(2000u128, alloyed_denom).into()), burn_from_address: user.to_string(), }) .add_message(BankMsg::Send { to_address: user.to_string(), - amount: vec![Coin::new(1000, "uion"), Coin::new(1000, "uosmo")], + amount: vec![coin(1000, "uion"), coin(1000, "uosmo")], }); assert_eq!(res, expected); @@ -2891,14 +3360,17 @@ mod tests { #[test] fn test_shares_and_liquidity() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance(&someone, vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; - let user_1 = "user_1"; - let user_2 = "user_2"; + let admin = deps.api.addr_make("admin"); + let user_1 = deps.api.addr_make("user_1"); + let user_2 = deps.api.addr_make("user_2"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -2907,10 +3379,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "usomoion".to_string(), alloyed_asset_normalization_factor: Uint128::one(), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -2921,18 +3393,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -2941,14 +3402,15 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info(user_1, &[Coin::new(1000, "uion"), Coin::new(1000, "uosmo")]), + message_info(&user_1, &[coin(1000, "uion"), coin(1000, "uosmo")]), join_pool_msg, ) .unwrap(); // Update alloyed asset denom balance for user deps.querier - .update_balance(user_1, vec![Coin::new(2000, "usomoion")]); + .bank + .update_balance(&user_1, vec![coin(2000, "usomoion")]); // Query the shares of the user let res = query( @@ -2982,7 +3444,7 @@ mod tests { let total_pool_liquidity: GetTotalPoolLiquidityResponse = from_json(res).unwrap(); assert_eq!( total_pool_liquidity.total_pool_liquidity, - vec![Coin::new(1000, "uosmo"), Coin::new(1000, "uion")] + vec![coin(1000, "uosmo"), coin(1000, "uion")] ); // Join pool @@ -2990,14 +3452,15 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info(user_2, &[Coin::new(1000, "uion")]), + message_info(&user_2, &[coin(1000, "uion")]), join_pool_msg, ) .unwrap(); // Update balance for user 2 deps.querier - .update_balance(user_2, vec![Coin::new(1000, "usomoion")]); + .bank + .update_balance(user_2, vec![coin(1000, "usomoion")]); // Query the total shares let res = query( @@ -3023,7 +3486,7 @@ mod tests { assert_eq!( total_pool_liquidity.total_pool_liquidity, - vec![Coin::new(1000, "uosmo"), Coin::new(2000, "uion")] + vec![coin(1000, "uosmo"), coin(2000, "uion")] ); } @@ -3033,9 +3496,11 @@ mod tests { // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance("someone", vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -3044,10 +3509,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "usomoion".to_string(), alloyed_asset_normalization_factor: Uint128::one(), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -3058,18 +3523,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -3089,11 +3543,15 @@ mod tests { fn test_spot_price() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + let moderator = deps.api.addr_make("moderator"); + // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]); + .bank + .update_balance(&someone, vec![coin(1, "uosmo"), coin(1, "uion")]); - let admin = "admin"; + let admin = deps.api.addr_make("admin"); let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("uosmo"), @@ -3102,10 +3560,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "uosmoion".to_string(), alloyed_asset_normalization_factor: Uint128::one(), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -3116,18 +3574,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -3229,12 +3676,15 @@ mod tests { #[test] fn test_spot_price_with_different_norm_factor() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply deps.querier - .update_balance("someone", vec![Coin::new(1, "tbtc"), Coin::new(1, "nbtc")]); + .bank + .update_balance(&someone, vec![coin(1, "tbtc"), coin(1, "nbtc")]); - let admin = "admin"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig { @@ -3249,10 +3699,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "allbtc".to_string(), alloyed_asset_normalization_factor: Uint128::from(100u128), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -3263,18 +3713,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -3344,14 +3783,15 @@ mod tests { #[test] fn test_calc_out_amt_given_in() { let mut deps = mock_dependencies(); + let admin = deps.api.addr_make("admin"); + let someone = deps.api.addr_make("someone"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply - deps.querier.update_balance( - "someone", - vec![Coin::new(1, "axlusdc"), Coin::new(1, "whusdc")], - ); + deps.querier + .bank + .update_balance(&someone, vec![coin(1, "axlusdc"), coin(1, "whusdc")]); - let admin = "admin"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("axlusdc"), @@ -3360,10 +3800,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "alloyedusdc".to_string(), alloyed_asset_normalization_factor: Uint128::one(), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -3374,18 +3814,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -3394,10 +3823,7 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info( - admin, - &[Coin::new(1000, "axlusdc"), Coin::new(2000, "whusdc")], - ), + message_info(&admin, &[coin(1000, "axlusdc"), coin(2000, "whusdc")]), join_pool_msg, ) .unwrap(); @@ -3419,35 +3845,35 @@ mod tests { } in vec![ Case { name: String::from("axlusdc to whusdc - ok"), - token_in: Coin::new(1000, "axlusdc"), + token_in: coin(1000, "axlusdc"), token_out_denom: "whusdc".to_string(), swap_fee: Decimal::zero(), expected: Ok(CalcOutAmtGivenInResponse { - token_out: Coin::new(1000, "whusdc"), + token_out: coin(1000, "whusdc"), }), }, Case { name: String::from("whusdc to axlusdc - ok"), - token_in: Coin::new(1000, "whusdc"), + token_in: coin(1000, "whusdc"), token_out_denom: "axlusdc".to_string(), swap_fee: Decimal::zero(), expected: Ok(CalcOutAmtGivenInResponse { - token_out: Coin::new(1000, "axlusdc"), + token_out: coin(1000, "axlusdc"), }), }, Case { name: String::from("whusdc to axlusdc - token out not enough"), - token_in: Coin::new(1001, "whusdc"), + token_in: coin(1001, "whusdc"), token_out_denom: "axlusdc".to_string(), swap_fee: Decimal::zero(), expected: Err(ContractError::InsufficientPoolAsset { - required: Coin::new(1001, "axlusdc"), - available: Coin::new(1000, "axlusdc"), + required: coin(1001, "axlusdc"), + available: coin(1000, "axlusdc"), }), }, Case { name: String::from("same denom error (pool asset)"), - token_in: Coin::new(1000, "axlusdc"), + token_in: coin(1000, "axlusdc"), token_out_denom: "axlusdc".to_string(), swap_fee: Decimal::zero(), expected: Err(ContractError::SameDenomNotAllowed { @@ -3456,7 +3882,7 @@ mod tests { }, Case { name: String::from("same denom error (alloyed asset)"), - token_in: Coin::new(1000, "alloyedusdc"), + token_in: coin(1000, "alloyedusdc"), token_out_denom: "alloyedusdc".to_string(), swap_fee: Decimal::zero(), expected: Err(ContractError::SameDenomNotAllowed { @@ -3465,53 +3891,53 @@ mod tests { }, Case { name: String::from("alloyedusdc to axlusdc - ok"), - token_in: Coin::new(1000, "alloyedusdc"), + token_in: coin(1000, "alloyedusdc"), token_out_denom: "axlusdc".to_string(), swap_fee: Decimal::zero(), expected: Ok(CalcOutAmtGivenInResponse { - token_out: Coin::new(1000, "axlusdc"), + token_out: coin(1000, "axlusdc"), }), }, Case { name: String::from("alloyedusdc to whusdc - ok"), - token_in: Coin::new(1000, "alloyedusdc"), + token_in: coin(1000, "alloyedusdc"), token_out_denom: "whusdc".to_string(), swap_fee: Decimal::zero(), expected: Ok(CalcOutAmtGivenInResponse { - token_out: Coin::new(1000, "whusdc"), + token_out: coin(1000, "whusdc"), }), }, Case { name: String::from("alloyedusdc to axlusdc - token out not enough"), - token_in: Coin::new(1001, "alloyedusdc"), + token_in: coin(1001, "alloyedusdc"), token_out_denom: "axlusdc".to_string(), swap_fee: Decimal::zero(), expected: Err(ContractError::InsufficientPoolAsset { - required: Coin::new(1001, "axlusdc"), - available: Coin::new(1000, "axlusdc"), + required: coin(1001, "axlusdc"), + available: coin(1000, "axlusdc"), }), }, Case { name: String::from("axlusdc to alloyedusdc - ok"), - token_in: Coin::new(1000, "axlusdc"), + token_in: coin(1000, "axlusdc"), token_out_denom: "alloyedusdc".to_string(), swap_fee: Decimal::zero(), expected: Ok(CalcOutAmtGivenInResponse { - token_out: Coin::new(1000, "alloyedusdc"), + token_out: coin(1000, "alloyedusdc"), }), }, Case { name: String::from("whusdc to alloyedusdc - ok"), - token_in: Coin::new(1000, "whusdc"), + token_in: coin(1000, "whusdc"), token_out_denom: "alloyedusdc".to_string(), swap_fee: Decimal::zero(), expected: Ok(CalcOutAmtGivenInResponse { - token_out: Coin::new(1000, "alloyedusdc"), + token_out: coin(1000, "alloyedusdc"), }), }, Case { name: String::from("invalid swap fee"), - token_in: Coin::new(1000, "axlusdc"), + token_in: coin(1000, "axlusdc"), token_out_denom: "whusdc".to_string(), swap_fee: Decimal::percent(1), expected: Err(ContractError::InvalidSwapFee { @@ -3521,7 +3947,7 @@ mod tests { }, Case { name: String::from("invalid swap fee (alloyed asset as token in)"), - token_in: Coin::new(1000, "alloyedusdc"), + token_in: coin(1000, "alloyedusdc"), token_out_denom: "whusdc".to_string(), swap_fee: Decimal::percent(1), expected: Err(ContractError::InvalidSwapFee { @@ -3531,7 +3957,7 @@ mod tests { }, Case { name: String::from("invalid swap fee (alloyed asset as token out)"), - token_in: Coin::new(1000, "axlusdc"), + token_in: coin(1000, "axlusdc"), token_out_denom: "alloyedusdc".to_string(), swap_fee: Decimal::percent(2), expected: Err(ContractError::InvalidSwapFee { @@ -3558,14 +3984,15 @@ mod tests { #[test] fn test_calc_in_amt_given_out() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply - deps.querier.update_balance( - "someone", - vec![Coin::new(1, "axlusdc"), Coin::new(1, "whusdc")], - ); + deps.querier + .bank + .update_balance(&someone, vec![coin(1, "axlusdc"), coin(1, "whusdc")]); - let admin = "admin"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("axlusdc"), @@ -3574,10 +4001,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "alloyedusdc".to_string(), alloyed_asset_normalization_factor: Uint128::one(), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -3588,18 +4015,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -3608,10 +4024,7 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info( - admin, - &[Coin::new(1000, "axlusdc"), Coin::new(2000, "whusdc")], - ), + message_info(&admin, &[coin(1000, "axlusdc"), coin(2000, "whusdc")]), join_pool_msg, ) .unwrap(); @@ -3634,35 +4047,35 @@ mod tests { Case { name: String::from("axlusdc to whusdc - ok"), token_in_denom: "axlusdc".to_string(), - token_out: Coin::new(1000, "whusdc"), + token_out: coin(1000, "whusdc"), swap_fee: Decimal::zero(), expected: Ok(CalcInAmtGivenOutResponse { - token_in: Coin::new(1000, "axlusdc"), + token_in: coin(1000, "axlusdc"), }), }, Case { name: String::from("whusdc to axlusdc - ok"), token_in_denom: "whusdc".to_string(), - token_out: Coin::new(1000, "axlusdc"), + token_out: coin(1000, "axlusdc"), swap_fee: Decimal::zero(), expected: Ok(CalcInAmtGivenOutResponse { - token_in: Coin::new(1000, "whusdc"), + token_in: coin(1000, "whusdc"), }), }, Case { name: String::from("whusdc to axlusdc - token out not enough"), token_in_denom: "whusdc".to_string(), - token_out: Coin::new(1001, "axlusdc"), + token_out: coin(1001, "axlusdc"), swap_fee: Decimal::zero(), expected: Err(ContractError::InsufficientPoolAsset { - required: Coin::new(1001, "axlusdc"), - available: Coin::new(1000, "axlusdc"), + required: coin(1001, "axlusdc"), + available: coin(1000, "axlusdc"), }), }, Case { name: String::from("same denom error (pool asset)"), token_in_denom: "axlusdc".to_string(), - token_out: Coin::new(1000, "axlusdc"), + token_out: coin(1000, "axlusdc"), swap_fee: Decimal::zero(), expected: Err(ContractError::SameDenomNotAllowed { denom: "axlusdc".to_string(), @@ -3671,7 +4084,7 @@ mod tests { Case { name: String::from("same denom error (alloyed asset)"), token_in_denom: "alloyedusdc".to_string(), - token_out: Coin::new(1000, "alloyedusdc"), + token_out: coin(1000, "alloyedusdc"), swap_fee: Decimal::zero(), expected: Err(ContractError::SameDenomNotAllowed { denom: "alloyedusdc".to_string(), @@ -3680,44 +4093,44 @@ mod tests { Case { name: String::from("alloyedusdc to axlusdc - ok"), token_in_denom: "alloyedusdc".to_string(), - token_out: Coin::new(1000, "axlusdc"), + token_out: coin(1000, "axlusdc"), swap_fee: Decimal::zero(), expected: Ok(CalcInAmtGivenOutResponse { - token_in: Coin::new(1000, "alloyedusdc"), + token_in: coin(1000, "alloyedusdc"), }), }, Case { name: String::from("alloyedusdc to whusdc - ok"), token_in_denom: "alloyedusdc".to_string(), - token_out: Coin::new(1000, "whusdc"), + token_out: coin(1000, "whusdc"), swap_fee: Decimal::zero(), expected: Ok(CalcInAmtGivenOutResponse { - token_in: Coin::new(1000, "alloyedusdc"), + token_in: coin(1000, "alloyedusdc"), }), }, Case { name: String::from("alloyedusdc to axlusdc - token out not enough"), token_in_denom: "alloyedusdc".to_string(), - token_out: Coin::new(1001, "axlusdc"), + token_out: coin(1001, "axlusdc"), swap_fee: Decimal::zero(), expected: Err(ContractError::InsufficientPoolAsset { - required: Coin::new(1001, "axlusdc"), - available: Coin::new(1000, "axlusdc"), + required: coin(1001, "axlusdc"), + available: coin(1000, "axlusdc"), }), }, Case { name: String::from("pool asset to alloyed asset - ok"), token_in_denom: "axlusdc".to_string(), - token_out: Coin::new(1000, "alloyedusdc"), + token_out: coin(1000, "alloyedusdc"), swap_fee: Decimal::zero(), expected: Ok(CalcInAmtGivenOutResponse { - token_in: Coin::new(1000, "axlusdc"), + token_in: coin(1000, "axlusdc"), }), }, Case { name: String::from("invalid swap fee"), token_in_denom: "whusdc".to_string(), - token_out: Coin::new(1000, "axlusdc"), + token_out: coin(1000, "axlusdc"), swap_fee: Decimal::percent(1), expected: Err(ContractError::InvalidSwapFee { expected: Decimal::zero(), @@ -3727,7 +4140,7 @@ mod tests { Case { name: String::from("invalid swap fee (alloyed asset as token in)"), token_in_denom: "alloyedusdc".to_string(), - token_out: Coin::new(1000, "axlusdc"), + token_out: coin(1000, "axlusdc"), swap_fee: Decimal::percent(1), expected: Err(ContractError::InvalidSwapFee { expected: Decimal::zero(), @@ -3737,7 +4150,7 @@ mod tests { Case { name: String::from("invalid swap fee (alloyed asset as token out)"), token_in_denom: "whusdc".to_string(), - token_out: Coin::new(1000, "alloyedusdc"), + token_out: coin(1000, "alloyedusdc"), swap_fee: Decimal::percent(2), expected: Err(ContractError::InvalidSwapFee { expected: Decimal::zero(), @@ -3763,14 +4176,15 @@ mod tests { #[test] fn test_rescale_normalization_factor() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply - deps.querier.update_balance( - "someone", - vec![Coin::new(1, "axlusdc"), Coin::new(1, "whusdc")], - ); + deps.querier + .bank + .update_balance(&someone, vec![coin(1, "axlusdc"), coin(1, "whusdc")]); - let admin = "admin"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("axlusdc"), @@ -3779,10 +4193,10 @@ mod tests { admin: Some(admin.to_string()), alloyed_asset_subdenom: "alloyedusdc".to_string(), alloyed_asset_normalization_factor: Uint128::from(100u128), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -3793,18 +4207,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -3839,7 +4242,7 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info(admin, &[]), + message_info(&admin, &[]), rescale_msg, ) .unwrap(); @@ -3881,7 +4284,7 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info(admin, &[]), + message_info(&admin, &[]), rescale_msg, ) .unwrap(); @@ -3914,4 +4317,499 @@ mod tests { }) ); } + + #[test] + fn test_asset_group() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); + + // Setup balance for each asset + deps.querier.bank.update_balance( + env.contract.address.clone(), + vec![ + coin(1000000, "asset1"), + coin(1000000, "asset2"), + coin(1000000, "asset3"), + ], + ); + + // Initialize the contract + let instantiate_msg = InstantiateMsg { + admin: Some(admin.to_string()), + moderator: moderator.to_string(), + pool_asset_configs: vec![ + AssetConfig { + denom: "asset1".to_string(), + normalization_factor: Uint128::from(1000000u128), + }, + AssetConfig { + denom: "asset2".to_string(), + normalization_factor: Uint128::from(1000000u128), + }, + AssetConfig { + denom: "asset3".to_string(), + normalization_factor: Uint128::from(1000000u128), + }, + ], + alloyed_asset_subdenom: "alloyed".to_string(), + alloyed_asset_normalization_factor: Uint128::from(1000000u128), + }; + + let info = message_info(&admin, &[]); + instantiate(deps.as_mut(), env.clone(), info.clone(), instantiate_msg).unwrap(); + + // Create asset group + let create_asset_group_msg = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "group1".to_string(), + denoms: vec!["asset1".to_string(), "asset2".to_string()], + }); + + // Test non-admin trying to create asset group + let non_admin = deps.api.addr_make("non_admin"); + let non_admin_info = message_info(&non_admin, &[]); + let non_admin_create_msg = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "group1".to_string(), + denoms: vec!["asset1".to_string(), "asset2".to_string()], + }); + let err = execute( + deps.as_mut(), + env.clone(), + non_admin_info, + non_admin_create_msg, + ) + .unwrap_err(); + assert_eq!(err, ContractError::Unauthorized {}); + + // Test admin creating asset group + let res = execute(deps.as_mut(), env.clone(), info, create_asset_group_msg).unwrap(); + + assert_eq!( + res.attributes, + vec![ + attr("method", "create_asset_group"), + attr("label", "group1"), + ] + ); + + // List asset groups + let list_asset_groups_msg = ContractQueryMsg::Transmuter(QueryMsg::ListAssetGroups {}); + let list_asset_groups_res: Result = + query(deps.as_ref(), env.clone(), list_asset_groups_msg) + .map(|value| from_json(value).unwrap()); + + assert_eq!( + list_asset_groups_res, + Ok(ListAssetGroupsResponse { + asset_groups: BTreeMap::from([( + "group1".to_string(), + AssetGroup::new(vec!["asset1".to_string(), "asset2".to_string()]), + )]), + }) + ); + + // Try setting limiter with non-existent group + let register_limiter_msg = ContractExecMsg::Transmuter(ExecMsg::RegisterLimiter { + label: "limiter1".to_string(), + scope: Scope::asset_group("group2"), + limiter_params: LimiterParams::ChangeLimiter { + window_config: WindowConfig { + window_size: 86400u64.into(), + division_count: 10u64.into(), + }, + boundary_offset: Decimal::percent(10), + }, + }); + + let register_limiter_info = message_info(&admin, &[]); + let err = execute( + deps.as_mut(), + env.clone(), + register_limiter_info.clone(), + register_limiter_msg.clone(), + ) + .unwrap_err(); + + assert!(matches!(err, ContractError::AssetGroupNotFound { .. })); + + // Create group2 + let create_asset_group_msg2 = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "group2".to_string(), + denoms: vec!["asset3".to_string()], + }); + + let create_asset_group_info2 = message_info(&admin, &[]); + let res2 = execute( + deps.as_mut(), + env.clone(), + create_asset_group_info2, + create_asset_group_msg2, + ) + .unwrap(); + + assert_eq!( + res2.attributes, + vec![ + attr("method", "create_asset_group"), + attr("label", "group2"), + ] + ); + + // Verify group2 was created + let list_asset_groups_msg2 = ContractQueryMsg::Transmuter(QueryMsg::ListAssetGroups {}); + let list_asset_groups_res2: Result = + query(deps.as_ref(), env.clone(), list_asset_groups_msg2) + .map(|value| from_json(value).unwrap()); + + assert_eq!( + list_asset_groups_res2, + Ok(ListAssetGroupsResponse { + asset_groups: BTreeMap::from([ + ( + "group1".to_string(), + AssetGroup::new(vec!["asset1".to_string(), "asset2".to_string()]) + ), + ( + "group2".to_string(), + AssetGroup::new(vec!["asset3".to_string()]) + ), + ]), + }) + ); + + // Try to register limiter for group2 + let res3 = execute( + deps.as_mut(), + env.clone(), + register_limiter_info, + register_limiter_msg, + ) + .unwrap(); + + assert_eq!( + res3.attributes, + vec![ + attr("method", "register_limiter"), + attr("label", "limiter1"), + attr("scope", "asset_group::group2"), + attr("limiter_type", "change_limiter"), + attr("window_size", "86400"), + attr("division_count", "10"), + attr("boundary_offset", "0.1"), + ] + ); + + // Verify limiter was registered + let list_limiters_msg = ContractQueryMsg::Transmuter(QueryMsg::ListLimiters {}); + let list_limiters_res: ListLimitersResponse = + from_json(query(deps.as_ref(), env.clone(), list_limiters_msg).unwrap()).unwrap(); + + assert_eq!( + list_limiters_res.limiters, + vec![( + ( + Scope::asset_group("group2").to_string(), + "limiter1".to_string() + ), + Limiter::ChangeLimiter( + ChangeLimiter::new( + WindowConfig { + window_size: 86400u64.into(), + division_count: 10u64.into(), + }, + Decimal::percent(10), + ) + .unwrap() + ) + )] + ); + + // Try to create a group with a non-existing asset + let create_invalid_group_msg = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "invalid_group".to_string(), + denoms: vec!["asset1".to_string(), "non_existing_asset".to_string()], + }); + + let admin_info = message_info(&admin, &[]); + let err = execute( + deps.as_mut(), + env.clone(), + admin_info, + create_invalid_group_msg, + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::InvalidPoolAssetDenom { + denom: "non_existing_asset".to_string() + } + ); + + // Verify that the invalid group was not created + let list_asset_groups_msg = ContractQueryMsg::Transmuter(QueryMsg::ListAssetGroups {}); + let list_asset_groups_res: ListAssetGroupsResponse = + from_json(query(deps.as_ref(), env.clone(), list_asset_groups_msg).unwrap()).unwrap(); + + assert_eq!( + list_asset_groups_res.asset_groups, + BTreeMap::from([ + ( + "group1".to_string(), + AssetGroup::new(vec!["asset1".to_string(), "asset2".to_string()]) + ), + ( + "group2".to_string(), + AssetGroup::new(vec!["asset3".to_string()]) + ), + ]) + ); + + // Test removing an asset group + let remove_group_msg = ContractExecMsg::Transmuter(ExecMsg::RemoveAssetGroup { + label: "group2".to_string(), + }); + + // Try to remove the group with a non-admin account + let non_admin_info = message_info(&non_admin, &[]); + let err = execute( + deps.as_mut(), + env.clone(), + non_admin_info, + remove_group_msg.clone(), + ) + .unwrap_err(); + + assert_eq!(err, ContractError::Unauthorized {}); + + // Remove the group with the admin account + let admin_info = message_info(&admin, &[]); + let res = execute(deps.as_mut(), env.clone(), admin_info, remove_group_msg).unwrap(); + + assert_eq!( + res.attributes, + vec![ + attr("method", "remove_asset_group"), + attr("label", "group2"), + ] + ); + + // Verify that the group was removed + let list_asset_groups_msg = ContractQueryMsg::Transmuter(QueryMsg::ListAssetGroups {}); + let list_asset_groups_res: ListAssetGroupsResponse = + from_json(query(deps.as_ref(), env.clone(), list_asset_groups_msg).unwrap()).unwrap(); + + assert_eq!( + list_asset_groups_res.asset_groups, + BTreeMap::from([( + "group1".to_string(), + AssetGroup::new(vec!["asset1".to_string(), "asset2".to_string()]) + )]) + ); + + // Test that limiter1 is removed along with the asset group + let list_limiters_msg = ContractQueryMsg::Transmuter(QueryMsg::ListLimiters {}); + let list_limiters_res: ListLimitersResponse = + from_json(query(deps.as_ref(), env.clone(), list_limiters_msg).unwrap()).unwrap(); + + // Check that limiter1 is not in the list of limiters + assert_eq!(list_limiters_res.limiters, vec![]); + + // Test removing a non-existing asset group + let remove_nonexistent_group_msg = ContractExecMsg::Transmuter(ExecMsg::RemoveAssetGroup { + label: "non_existent_group".to_string(), + }); + + let admin_info = message_info(&admin, &[]); + let err = execute( + deps.as_mut(), + env.clone(), + admin_info, + remove_nonexistent_group_msg, + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::AssetGroupNotFound { + label: "non_existent_group".to_string() + } + ); + } + + #[test] + fn test_mark_corrupted_scopes() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let admin = deps.api.addr_make("admin"); + let moderator = deps.api.addr_make("moderator"); + let user = deps.api.addr_make("user"); + + // Add supply for denoms using deps.querier.bank.update_balance + deps.querier.bank.update_balance( + env.contract.address.clone(), + vec![coin(1000000, "asset1"), coin(2000000, "asset2")], + ); + + // Initialize the contract + let init_msg = InstantiateMsg { + admin: Some(admin.to_string()), + moderator: moderator.to_string(), + pool_asset_configs: vec![ + AssetConfig { + denom: "asset1".to_string(), + normalization_factor: Uint128::new(1), + }, + AssetConfig { + denom: "asset2".to_string(), + normalization_factor: Uint128::new(1), + }, + ], + alloyed_asset_subdenom: "alloyed".to_string(), + alloyed_asset_normalization_factor: Uint128::new(1), + }; + let info = message_info(&admin, &[]); + instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); + + // Mark corrupted scopes + let mark_corrupted_scopes_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedScopes { + scopes: vec![Scope::Denom("asset1".to_string())], + }); + let moderator_info = message_info(&moderator, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + moderator_info.clone(), + mark_corrupted_scopes_msg, + ) + .unwrap(); + + // Check the response + assert_eq!( + res.attributes, + vec![attr("method", "mark_corrupted_scopes")] + ); + + // Verify that the scope is marked as corrupted + // Query the contract to get the corrupted denoms + let query_msg = ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}); + let query_res: GetCorrruptedScopesResponse = + from_json(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + // Check that "asset1" is in the corrupted denoms list + assert_eq!(query_res.corrupted_scopes, vec![Scope::denom("asset1")]); + + // Try to mark corrupted scopes as a non-moderator (should fail) + let user_info = message_info(&user, &[]); + let unauthorized_mark_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedScopes { + scopes: vec![Scope::denom("asset2")], + }); + let err = + execute(deps.as_mut(), env.clone(), user_info, unauthorized_mark_msg).unwrap_err(); + assert_eq!(err, ContractError::Unauthorized {}); + + // Test create_asset_group + let create_asset_group_msg = ContractExecMsg::Transmuter(ExecMsg::CreateAssetGroup { + label: "group1".to_string(), + denoms: vec!["asset1".to_string(), "asset2".to_string()], + }); + let admin_info = message_info(&admin, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + admin_info.clone(), + create_asset_group_msg, + ) + .unwrap(); + + // Check the response + assert_eq!( + res.attributes, + vec![ + attr("method", "create_asset_group"), + attr("label", "group1"), + ] + ); + + // Test mark_asset_group_as_corrupted + let moderator_info = message_info(&moderator, &[]); + let mark_group_corrupted_msg = ContractExecMsg::Transmuter(ExecMsg::MarkCorruptedScopes { + scopes: vec![Scope::asset_group("group1")], + }); + let res = execute( + deps.as_mut(), + env.clone(), + moderator_info.clone(), + mark_group_corrupted_msg, + ) + .unwrap(); + + // Check the response + assert_eq!( + res.attributes, + vec![attr("method", "mark_corrupted_scopes"),] + ); + + // Verify that the asset group is marked as corrupted + let query_msg = ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}); + let query_res: GetCorrruptedScopesResponse = + from_json(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + // Check that both "asset1" and "asset2" are in the corrupted scopes list + assert_eq!( + query_res.corrupted_scopes, + vec![Scope::denom("asset1"), Scope::asset_group("group1")] + ); + + // Test unmark_corrupted_scopes for the asset group + let unmark_group_corrupted_msg = + ContractExecMsg::Transmuter(ExecMsg::UnmarkCorruptedScopes { + scopes: vec![Scope::asset_group("group1")], + }); + let res = execute( + deps.as_mut(), + env.clone(), + moderator_info.clone(), + unmark_group_corrupted_msg, + ) + .unwrap(); + + // Check the response + assert_eq!( + res.attributes, + vec![attr("method", "unmark_corrupted_scopes"),] + ); + + // Verify that the asset group is no longer marked as corrupted + let query_msg = ContractQueryMsg::Transmuter(QueryMsg::GetCorruptedScopes {}); + let query_res: GetCorrruptedScopesResponse = + from_json(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + // Check that the asset group is no longer in the corrupted scopes list + assert_eq!(query_res.corrupted_scopes, vec![Scope::denom("asset1")]); + } + + fn reply_create_denom_response(alloyed_denom: &str) -> Reply { + let msg_create_denom_response = MsgCreateDenomResponse { + new_token_denom: alloyed_denom.to_string(), + }; + + Reply { + id: 1, + result: SubMsgResult::Ok( + #[allow(deprecated)] + SubMsgResponse { + events: vec![], + data: Some(msg_create_denom_response.clone().into()), // DEPRECATED + msg_responses: vec![MsgResponse { + type_url: MsgCreateDenomResponse::TYPE_URL.to_string(), + value: msg_create_denom_response.into(), + }], + }, + ), + payload: Binary::new(vec![]), + gas_used: 0, + } + } } diff --git a/contracts/transmuter/src/corruptable.rs b/contracts/transmuter/src/corruptable.rs new file mode 100644 index 0000000..de16d2e --- /dev/null +++ b/contracts/transmuter/src/corruptable.rs @@ -0,0 +1,5 @@ +pub trait Corruptable { + fn is_corrupted(&self) -> bool; + fn mark_as_corrupted(&mut self) -> &mut Self; + fn unmark_as_corrupted(&mut self) -> &mut Self; +} diff --git a/contracts/transmuter/src/error.rs b/contracts/transmuter/src/error.rs index 42153c4..dbe932b 100644 --- a/contracts/transmuter/src/error.rs +++ b/contracts/transmuter/src/error.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ }; use thiserror::Error; -use crate::math::MathError; +use crate::{math::MathError, scope::Scope}; #[derive(Error, Debug, PartialEq)] pub enum ContractError { @@ -48,7 +48,7 @@ pub enum ContractError { InvalidCorruptedAssetDenom { denom: String }, #[error("Only corrupted asset with 0 amount can be removed")] - InvalidCorruptedAssetRemoval {}, + InvalidAssetRemoval {}, #[error("Pool asset denom count must be within {min} - {max} inclusive, but got: {actual}")] PoolAssetDenomCountOutOfRange { @@ -57,6 +57,9 @@ pub enum ContractError { actual: Uint64, }, + #[error("Asset group count must be within {max} inclusive, but got: {actual}")] + AssetGroupCountOutOfRange { max: Uint64, actual: Uint64 }, + #[error("Insufficient pool asset: required: {required}, available: {available}")] InsufficientPoolAsset { required: Coin, available: Coin }, @@ -114,11 +117,11 @@ pub enum ContractError { #[error("Admin transferring state is inoperable for the requested operation")] InoperableAdminTransferringState {}, - #[error("Limiter count for {denom} exceed maximum per denom: {max}")] - MaxLimiterCountPerDenomExceeded { denom: String, max: Uint64 }, + #[error("Limiter count for {scope} exceed maximum per denom: {max}")] + MaxLimiterCountPerDenomExceeded { scope: Scope, max: Uint64 }, - #[error("Denom: {denom} cannot have an empty limiter after it has been registered")] - EmptyLimiterNotAllowed { denom: String }, + #[error("Denom: {scope} cannot have an empty limiter after it has been registered")] + EmptyLimiterNotAllowed { scope: Scope }, #[error("Limiter label must not be empty")] EmptyLimiterLabel {}, @@ -158,17 +161,17 @@ pub enum ContractError { ended_at: Timestamp, }, - #[error("Limiter does not exist for denom: {denom}, label: {label}")] - LimiterDoesNotExist { denom: String, label: String }, + #[error("Limiter does not exist for scope: {scope}, label: {label}")] + LimiterDoesNotExist { scope: Scope, label: String }, - #[error("Limiter already exists for denom: {denom}, label: {label}")] - LimiterAlreadyExists { denom: String, label: String }, + #[error("Limiter already exists for scope: {scope}, label: {label}")] + LimiterAlreadyExists { scope: Scope, label: String }, #[error( - "Upper limit exceeded for `{denom}`, upper limit is {upper_limit}, but the resulted weight is {value}" + "Upper limit exceeded for `{scope}`, upper limit is {upper_limit}, but the resulted weight is {value}" )] UpperLimitExceeded { - denom: String, + scope: Scope, upper_limit: Decimal, value: Decimal, }, @@ -179,8 +182,17 @@ pub enum ContractError { #[error("Normalization factor must be positive")] NormalizationFactorMustBePositive {}, - #[error("Corrupted asset: {denom} must not increase in amount or weight")] - CorruptedAssetRelativelyIncreased { denom: String }, + #[error("Corrupted scope: {scope} must not increase in amount or weight")] + CorruptedScopeRelativelyIncreased { scope: Scope }, + + #[error("Asset group {label} not found")] + AssetGroupNotFound { label: String }, + + #[error("Asset group {label} already exists")] + AssetGroupAlreadyExists { label: String }, + + #[error("Asset group label must not be empty")] + EmptyAssetGroupLabel {}, #[error("{0}")] OverflowError(#[from] OverflowError), diff --git a/contracts/transmuter/src/lib.rs b/contracts/transmuter/src/lib.rs index 68862f3..e11a440 100644 --- a/contracts/transmuter/src/lib.rs +++ b/contracts/transmuter/src/lib.rs @@ -1,11 +1,13 @@ mod alloyed_asset; mod asset; pub mod contract; +mod corruptable; mod error; mod limiter; mod math; mod migrations; mod role; +mod scope; mod sudo; mod swap; mod transmuter_pool; @@ -26,7 +28,7 @@ mod entry_points { use crate::migrations; use crate::sudo::SudoMsg; - const CONTRACT: Transmuter = Transmuter::default(); + const CONTRACT: Transmuter = Transmuter::new(); macro_rules! ensure_active_status { ($msg:expr, $deps:expr, $env:expr, except: $pattern:pat) => { @@ -100,9 +102,9 @@ mod entry_points { pub fn migrate( deps: DepsMut, _env: Env, - _msg: migrations::v3_2_0::MigrateMsg, + _msg: migrations::v4_0_0::MigrateMsg, ) -> Result { - migrations::v3_2_0::execute_migration(deps) + migrations::v4_0_0::execute_migration(deps) } } diff --git a/contracts/transmuter/src/limiter.rs b/contracts/transmuter/src/limiter.rs index 1cd1ed8..dcd9041 100644 --- a/contracts/transmuter/src/limiter.rs +++ b/contracts/transmuter/src/limiter.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::ContractError; +use crate::{scope::Scope, ContractError}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Decimal, StdError, Storage, Timestamp, Uint64}; use cw_storage_plus::Map; @@ -136,7 +136,7 @@ impl ChangeLimiter { fn ensure_upper_limit( self, block_time: Timestamp, - denom: &str, + scope: &Scope, value: Decimal, ) -> Result { let (latest_removed_division, updated_limiter) = @@ -161,7 +161,7 @@ impl ChangeLimiter { ensure!( value <= upper_limit, ContractError::UpperLimitExceeded { - denom: denom.to_string(), + scope: scope.clone(), upper_limit, value, } @@ -285,11 +285,11 @@ impl StaticLimiter { Ok(self) } - fn ensure_upper_limit(self, denom: &str, value: Decimal) -> Result { + fn ensure_upper_limit(self, scope: &Scope, value: Decimal) -> Result { ensure!( value <= self.upper_limit, ContractError::UpperLimitExceeded { - denom: denom.to_string(), + scope: scope.clone(), upper_limit: self.upper_limit, value, } @@ -320,13 +320,13 @@ pub enum LimiterParams { }, } -pub struct Limiters<'a> { - /// Map of (denom, label) -> Limiter - limiters: Map<'a, (&'a str, &'a str), Limiter>, +pub struct Limiters { + /// Map of (scope, label) -> Limiter + limiters: Map<(&'static str, &'static str), Limiter>, } -impl<'a> Limiters<'a> { - pub const fn new(limiters_namespace: &'a str) -> Self { +impl Limiters { + pub const fn new(limiters_namespace: &'static str) -> Self { Self { limiters: Map::new(limiters_namespace), } @@ -335,19 +335,21 @@ impl<'a> Limiters<'a> { pub fn register( &self, storage: &mut dyn Storage, - denom: &str, + scope: Scope, label: &str, limiter_params: LimiterParams, ) -> Result<(), ContractError> { - let is_registering_limiter_exists = - self.limiters.may_load(storage, (denom, label))?.is_some(); + let is_registering_limiter_exists = self + .limiters + .may_load(storage, (&scope.key(), label))? + .is_some(); ensure!(!label.is_empty(), ContractError::EmptyLimiterLabel {}); ensure!( !is_registering_limiter_exists, ContractError::LimiterAlreadyExists { - denom: denom.to_string(), + scope, label: label.to_string() } ); @@ -363,59 +365,79 @@ impl<'a> Limiters<'a> { }; // ensure limiters for the denom has not yet reached the maximum - let limiter_count_for_denom = self.list_limiters_by_denom(storage, denom)?.len() as u64; + let limiter_count_for_denom = self.list_limiters_by_scope(storage, &scope)?.len() as u64; ensure!( limiter_count_for_denom < MAX_LIMITER_COUNT_PER_DENOM.u64(), ContractError::MaxLimiterCountPerDenomExceeded { - denom: denom.to_string(), + scope, max: MAX_LIMITER_COUNT_PER_DENOM } ); self.limiters - .save(storage, (denom, label), &limiter) + .save(storage, (&scope.key(), label), &limiter) .map_err(Into::into) } /// Deregsiter all limiters for the denom without checking if it will be empty. /// This is useful when the asset is being removed, so that limiters for the asset are no longer needed. - pub fn uncheck_deregister_all_for_denom( + pub fn uncheck_deregister_all_for_scope( &self, storage: &mut dyn Storage, - denom: &str, + scope: Scope, ) -> Result<(), ContractError> { - let limiters = self.list_limiters_by_denom(storage, denom)?; + let limiters = self.list_limiters_by_scope(storage, &scope)?; for (label, _) in limiters { - self.limiters.remove(storage, (denom, &label)); + self.limiters.remove(storage, (&scope.key(), &label)); } Ok(()) } + /// Deregister a limiter without checking if it will be empty. + /// This is useful when the scope is being removed, so that limiters for the scope are no longer needed. + pub fn unchecked_deregister( + &self, + storage: &mut dyn Storage, + scope: Scope, + label: &str, + ) -> Result { + let scope_key = scope.key(); + match self.limiters.may_load(storage, (&scope_key, label))? { + Some(limiter) => { + self.limiters.remove(storage, (&scope_key, label)); + Ok(limiter) + } + None => Err(ContractError::LimiterDoesNotExist { + scope, + label: label.to_string(), + }), + } + } + pub fn deregister( &self, storage: &mut dyn Storage, - denom: &str, + scope: Scope, label: &str, ) -> Result { - match self.limiters.may_load(storage, (denom, label))? { + let scope_key = scope.key(); + match self.limiters.may_load(storage, (&scope_key, label))? { Some(limiter) => { - let limiter_for_denom_will_not_be_empty = - self.list_limiters_by_denom(storage, denom)?.len() >= 2; + let limiter_for_scope_will_not_be_empty = + self.list_limiters_by_scope(storage, &scope)?.len() >= 2; ensure!( - limiter_for_denom_will_not_be_empty, - ContractError::EmptyLimiterNotAllowed { - denom: denom.to_string() - } + limiter_for_scope_will_not_be_empty, + ContractError::EmptyLimiterNotAllowed { scope } ); - self.limiters.remove(storage, (denom, label)); + self.limiters.remove(storage, (&scope_key, label)); Ok(limiter) } None => Err(ContractError::LimiterDoesNotExist { - denom: denom.to_string(), + scope, label: label.to_string(), }), } @@ -425,16 +447,16 @@ impl<'a> Limiters<'a> { pub fn set_change_limiter_boundary_offset( &self, storage: &mut dyn Storage, - denom: &str, + scope: Scope, label: &str, boundary_offset: Decimal, ) -> Result<(), ContractError> { self.limiters.update( storage, - (denom, label), + (&scope.key(), label), |limiter: Option| -> Result { let limiter = limiter.ok_or(ContractError::LimiterDoesNotExist { - denom: denom.to_string(), + scope, label: label.to_string(), })?; @@ -463,16 +485,16 @@ impl<'a> Limiters<'a> { pub fn set_static_limiter_upper_limit( &self, storage: &mut dyn Storage, - denom: &str, + scope: Scope, label: &str, upper_limit: Decimal, ) -> Result<(), ContractError> { self.limiters.update( storage, - (denom, label), + (&scope.key(), label), |limiter: Option| -> Result { let limiter = limiter.ok_or(ContractError::LimiterDoesNotExist { - denom: denom.to_string(), + scope, label: label.to_string(), })?; @@ -491,14 +513,14 @@ impl<'a> Limiters<'a> { Ok(()) } - pub fn list_limiters_by_denom( + pub fn list_limiters_by_scope( &self, storage: &dyn Storage, - denom: &str, + scope: &Scope, ) -> Result, ContractError> { // there is no need to limit, since the number of limiters is expected to be small self.limiters - .prefix(denom) + .prefix(&scope.key()) .range(storage, None, None, cosmwasm_std::Order::Ascending) .collect::, _>>() .map_err(Into::into) @@ -519,21 +541,21 @@ impl<'a> Limiters<'a> { pub fn check_limits_and_update( &self, storage: &mut dyn Storage, - denom_value_pairs: Vec<(String, (Decimal, Decimal))>, + scope_value_pairs: Vec<(Scope, (Decimal, Decimal))>, block_time: Timestamp, ) -> Result<(), ContractError> { - for (denom, (prev_value, value)) in denom_value_pairs { - let limiters = self.list_limiters_by_denom(storage, denom.as_str())?; + for (scope, (prev_value, value)) in scope_value_pairs { + let limiters = self.list_limiters_by_scope(storage, &scope)?; let is_not_decreasing = value >= prev_value; for (label, limiter) in limiters { // Enforce limiter only if value is increasing, because if the value is decreasing from the previous value, - // for the specific denom, it is a balancing act to move away from the limit. + // for the specific scope, it is a balancing act to move away from the limit. let limiter = match limiter { Limiter::ChangeLimiter(limiter) => Limiter::ChangeLimiter({ if is_not_decreasing { limiter - .ensure_upper_limit(block_time, denom.as_str(), value)? + .ensure_upper_limit(block_time, &scope, value)? .update(block_time, value)? } else { limiter.update(block_time, value)? @@ -541,7 +563,7 @@ impl<'a> Limiters<'a> { }), Limiter::StaticLimiter(limiter) => Limiter::StaticLimiter({ if is_not_decreasing { - limiter.ensure_upper_limit(denom.as_str(), value)? + limiter.ensure_upper_limit(&scope, value)? } else { limiter } @@ -550,7 +572,7 @@ impl<'a> Limiters<'a> { // save updated limiter self.limiters - .save(storage, (denom.as_str(), &label), &limiter)?; + .save(storage, (&scope.key(), &label), &limiter)?; } } @@ -567,19 +589,19 @@ impl<'a> Limiters<'a> { &self, storage: &mut dyn Storage, block_time: Timestamp, - weights: Vec<(String, Decimal)>, + weights: impl Iterator, ) -> Result<(), ContractError> { // there is no need to limit, since the number of limiters is expected to be small let limiters = self.list_limiters(storage)?; let weights: HashMap = weights.into_iter().collect(); - for ((denom, label), limiter) in limiters { + for ((scope, label), limiter) in limiters { match limiter { Limiter::ChangeLimiter(limiter) => { self.limiters - .save(storage, (denom.as_str(), label.as_str()), { - let value = weights.get(denom.as_str()).copied().ok_or_else(|| { - StdError::not_found(format!("weight for {}", denom)) + .save(storage, (scope.as_str(), label.as_str()), { + let value = weights.get(scope.as_str()).copied().ok_or_else(|| { + StdError::not_found(format!("weight for {}", scope)) })?; &Limiter::ChangeLimiter(limiter.reset().update(block_time, value)?) })? @@ -595,24 +617,29 @@ impl<'a> Limiters<'a> { /// This is used for testing if all change limiters has been newly created or reset. #[cfg(test)] #[macro_export] -macro_rules! assert_reset_change_limiters_by_denom { - ($denom:expr, $reset_at:expr, $transmuter:expr, $storage:expr) => { +macro_rules! assert_reset_change_limiters_by_scope { + ($scope:expr, $reset_at:expr, $transmuter:expr, $storage:expr) => { let pool = $transmuter.pool.load($storage).unwrap(); - let weights = pool - .weights() + let asset_weights = pool + .asset_weights() .unwrap() .unwrap_or_default() .into_iter() .collect::>(); + let asset_group_weights = pool.asset_group_weights().unwrap(); + let limiters = $transmuter .limiters - .list_limiters_by_denom($storage, $denom) + .list_limiters_by_scope($storage, $scope) .expect("failed to list limiters"); for (_label, limiter) in limiters { if let $crate::limiter::Limiter::ChangeLimiter(limiter) = limiter { - let value = *weights.get($denom).unwrap(); + let value = match $scope { + Scope::Denom(denom) => *asset_weights.get(denom.as_str()).unwrap(), + Scope::AssetGroup(label) => *asset_group_weights.get(label.as_str()).unwrap(), + }; assert_eq!( limiter.divisions(), &[transmuter_math::Division::new($reset_at, $reset_at, value, value).unwrap()] @@ -625,10 +652,10 @@ macro_rules! assert_reset_change_limiters_by_denom { /// This is used for testing if a change limiters for denom has been updated #[cfg(test)] #[macro_export] -macro_rules! assert_dirty_change_limiters_by_denom { - ($denom:expr, $lim:expr, $storage:expr) => { +macro_rules! assert_dirty_change_limiters_by_scope { + ($scope:expr, $lim:expr, $storage:expr) => { let limiters = $lim - .list_limiters_by_denom($storage, $denom) + .list_limiters_by_scope($storage, $scope) .expect("failed to list limiters"); for (label, limiter) in limiters { @@ -639,7 +666,7 @@ macro_rules! assert_dirty_change_limiters_by_denom { limiter, limiter.clone().reset(), "Change Limiter `{}/{}` is clean but expect dirty", - $denom, + $scope, label ); } @@ -668,7 +695,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -683,7 +710,7 @@ mod tests { assert_eq!( limiter.list_limiters(&deps.storage).unwrap(), vec![( - ("denoma".to_string(), "1m".to_string()), + (Scope::denom("denoma").key(), "1m".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -699,7 +726,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1h", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -715,7 +742,7 @@ mod tests { limiter.list_limiters(&deps.storage).unwrap(), vec![ ( - ("denoma".to_string(), "1h".to_string()), + (Scope::denom("denoma").key(), "1h".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -727,7 +754,7 @@ mod tests { }) ), ( - ("denoma".to_string(), "1m".to_string()), + (Scope::denom("denoma").key(), "1m".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -744,7 +771,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -760,7 +787,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(10), @@ -772,7 +799,7 @@ mod tests { limiter.list_limiters(&deps.storage).unwrap(), vec![ ( - ("denoma".to_string(), "1h".to_string()), + (Scope::denom("denoma").key(), "1h".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -784,7 +811,7 @@ mod tests { }) ), ( - ("denoma".to_string(), "1m".to_string()), + (Scope::denom("denoma").key(), "1m".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -796,13 +823,13 @@ mod tests { }) ), ( - ("denoma".to_string(), "static".to_string()), + (Scope::denom("denoma").key(), "static".to_string()), Limiter::StaticLimiter(StaticLimiter { upper_limit: Decimal::percent(10) }) ), ( - ("denomb".to_string(), "1m".to_string()), + (Scope::denom("denomb").key(), "1m".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -819,7 +846,7 @@ mod tests { // list limiters by denom assert_eq!( limiter - .list_limiters_by_denom(&deps.storage, "denoma") + .list_limiters_by_scope(&deps.storage, &Scope::denom("denoma")) .unwrap(), vec![ ( @@ -857,7 +884,7 @@ mod tests { assert_eq!( limiter - .list_limiters_by_denom(&deps.storage, "denomb") + .list_limiters_by_scope(&deps.storage, &Scope::denom("denomb")) .unwrap(), vec![( "1m".to_string(), @@ -882,7 +909,7 @@ mod tests { let err = limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -905,7 +932,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -920,7 +947,7 @@ mod tests { let err = limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -935,7 +962,7 @@ mod tests { assert_eq!( err, ContractError::LimiterAlreadyExists { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), label: "1m".to_string() } ); @@ -950,7 +977,7 @@ mod tests { let label = format!("{}h", h); let result = limiter.register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), &label, LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -967,7 +994,7 @@ mod tests { assert_eq!( result.unwrap_err(), ContractError::MaxLimiterCountPerDenomExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), max: MAX_LIMITER_COUNT_PER_DENOM } ); @@ -976,14 +1003,14 @@ mod tests { // deregister to register should work limiter - .deregister(&mut deps.storage, "denoma", "1h") + .deregister(&mut deps.storage, Scope::denom("denoma"), "1h") .unwrap(); // register static limiter limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(10), @@ -995,7 +1022,7 @@ mod tests { let err = limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "static2", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(9), @@ -1006,7 +1033,7 @@ mod tests { assert_eq!( err, ContractError::MaxLimiterCountPerDenomExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), max: MAX_LIMITER_COUNT_PER_DENOM } ); @@ -1020,7 +1047,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1035,7 +1062,7 @@ mod tests { assert_eq!( limiter.list_limiters(&deps.storage).unwrap(), vec![( - ("denoma".to_string(), "1m".to_string()), + (Scope::denom("denoma").key(), "1m".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1051,7 +1078,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1h", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1067,7 +1094,7 @@ mod tests { limiter.list_limiters(&deps.storage).unwrap(), vec![ ( - ("denoma".to_string(), "1h".to_string()), + (Scope::denom("denoma").key(), "1h".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1079,7 +1106,7 @@ mod tests { }) ), ( - ("denoma".to_string(), "1m".to_string()), + (Scope::denom("denoma").key(), "1m".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1094,25 +1121,25 @@ mod tests { ); let err = limiter - .deregister(&mut deps.storage, "denoma", "nonexistent") + .deregister(&mut deps.storage, Scope::denom("denoma"), "nonexistent") .unwrap_err(); assert_eq!( err, ContractError::LimiterDoesNotExist { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), label: "nonexistent".to_string(), } ); limiter - .deregister(&mut deps.storage, "denoma", "1m") + .deregister(&mut deps.storage, Scope::denom("denoma"), "1m") .unwrap(); assert_eq!( limiter.list_limiters(&deps.storage).unwrap(), vec![( - ("denoma".to_string(), "1h".to_string()), + (Scope::denom("denoma").key(), "1h".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1126,20 +1153,20 @@ mod tests { ); let err = limiter - .deregister(&mut deps.storage, "denoma", "1h") + .deregister(&mut deps.storage, Scope::denom("denoma"), "1h") .unwrap_err(); assert_eq!( err, ContractError::EmptyLimiterNotAllowed { - denom: "denoma".to_string() + scope: Scope::denom("denoma") } ); assert_eq!( limiter.list_limiters(&deps.storage).unwrap(), vec![( - ("denoma".to_string(), "1h".to_string()), + (Scope::denom("denoma").key(), "1h".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1154,7 +1181,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1167,20 +1194,20 @@ mod tests { .unwrap(); let err = limiter - .deregister(&mut deps.storage, "denomb", "1m") + .deregister(&mut deps.storage, Scope::denom("denomb"), "1m") .unwrap_err(); assert_eq!( err, ContractError::EmptyLimiterNotAllowed { - denom: "denomb".to_string() + scope: Scope::denom("denomb") } ); limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1h", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1193,25 +1220,25 @@ mod tests { .unwrap(); let err = limiter - .deregister(&mut deps.storage, "denoma", "1h") + .deregister(&mut deps.storage, Scope::denom("denoma"), "1h") .unwrap_err(); assert_eq!( err, ContractError::EmptyLimiterNotAllowed { - denom: "denoma".to_string() + scope: Scope::denom("denoma") } ); limiter - .deregister(&mut deps.storage, "denomb", "1m") + .deregister(&mut deps.storage, Scope::denom("denomb"), "1m") .unwrap(); assert_eq!( limiter.list_limiters(&deps.storage).unwrap(), vec![ ( - ("denoma".to_string(), "1h".to_string()), + (Scope::denom("denoma").key(), "1h".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1223,7 +1250,7 @@ mod tests { }) ), ( - ("denomb".to_string(), "1h".to_string()), + (Scope::denom("denomb").key(), "1h".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1237,6 +1264,50 @@ mod tests { ] ); } + + #[test] + fn test_unchecked_deregister() { + let mut deps = mock_dependencies(); + let limiter = Limiters::new("limiters"); + + // Register two limiters for denoma and one for denomb + limiter + .register( + &mut deps.storage, + Scope::denom("denoma"), + "1h", + LimiterParams::ChangeLimiter { + window_config: WindowConfig { + window_size: Uint64::from(3_600_000_000_000u64), + division_count: Uint64::from(2u64), + }, + boundary_offset: Decimal::percent(10), + }, + ) + .unwrap(); + + // Unchecked deregister one limiter from denoma + let removed_limiter = limiter + .unchecked_deregister(&mut deps.storage, Scope::denom("denoma"), "1h") + .unwrap(); + + // Check that the removed limiter is correct + assert_eq!( + removed_limiter, + Limiter::ChangeLimiter(ChangeLimiter { + divisions: vec![], + latest_value: Decimal::zero(), + window_config: WindowConfig { + window_size: Uint64::from(3_600_000_000_000u64), + division_count: Uint64::from(2u64), + }, + boundary_offset: Decimal::percent(10) + }) + ); + + // Check that the remaining limiters are correct + assert_eq!(limiter.list_limiters(&deps.storage).unwrap(), vec![]); + } } mod set_config { @@ -1253,7 +1324,7 @@ mod tests { let err = limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1277,7 +1348,7 @@ mod tests { let err = limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1291,9 +1362,7 @@ mod tests { assert_eq!( err, - ContractError::DivideByZeroError(DivideByZeroError::new(Uint64::from( - 604_800_000_000u64 - ))) + ContractError::DivideByZeroError(DivideByZeroError::new()) ); } @@ -1306,7 +1375,7 @@ mod tests { let err = limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1330,7 +1399,7 @@ mod tests { let err = limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1359,7 +1428,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1m", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -1376,7 +1445,7 @@ mod tests { assert_eq!( limiters, vec![( - ("denoma".to_string(), "1m".to_string()), + (Scope::denom("denoma").key(), "1m".to_string()), Limiter::ChangeLimiter(ChangeLimiter { divisions: vec![], latest_value: Decimal::zero(), @@ -1891,7 +1960,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1h", LimiterParams::ChangeLimiter { window_config: config, @@ -1906,14 +1975,14 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap(); // check divs count assert_eq!( - list_divisions(&limiter, "denoma", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denoma"), "1h", &deps.storage).len(), 1 ); @@ -1924,13 +1993,13 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap(); assert_eq!( - list_divisions(&limiter, "denoma", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denoma"), "1h", &deps.storage).len(), 1 ); @@ -1940,7 +2009,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -1948,14 +2017,14 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::percent(58), value: Decimal::from_str("0.580000000000000001").unwrap(), } ); assert_eq!( - list_divisions(&limiter, "denoma", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denoma"), "1h", &deps.storage).len(), 1 ); @@ -1966,7 +2035,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -1974,14 +2043,14 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::from_str("0.5875").unwrap(), value: Decimal::from_str("0.587500000000000001").unwrap(), } ); assert_eq!( - list_divisions(&limiter, "denoma", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denoma"), "1h", &deps.storage).len(), 1 ); @@ -1989,13 +2058,13 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap(); assert_eq!( - list_divisions(&limiter, "denoma", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denoma"), "1h", &deps.storage).len(), 2 ); @@ -2005,7 +2074,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2013,7 +2082,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::from_str("0.56").unwrap(), value: Decimal::from_str("0.560000000000000001").unwrap(), } @@ -2026,20 +2095,20 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap_err(); assert_eq!( - list_divisions(&limiter, "denoma", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denoma"), "1h", &deps.storage).len(), 2 ); assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::from_str("0.525").unwrap(), value: Decimal::from_str("0.525000000000000001").unwrap(), } @@ -2049,13 +2118,13 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap(); assert_eq!( - list_divisions(&limiter, "denoma", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denoma"), "1h", &deps.storage).len(), 3 ); } @@ -2071,7 +2140,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1h", LimiterParams::ChangeLimiter { window_config: config, @@ -2083,7 +2152,7 @@ mod tests { limiter .set_change_limiter_boundary_offset( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1h", Decimal::percent(5), ) @@ -2095,13 +2164,13 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap(); assert_eq!( - list_divisions(&limiter, "denomb", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denomb"), "1h", &deps.storage).len(), 1 ); @@ -2110,13 +2179,13 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap(); assert_eq!( - list_divisions(&limiter, "denomb", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denomb"), "1h", &deps.storage).len(), 1 ); @@ -2125,20 +2194,20 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap_err(); assert_eq!( - list_divisions(&limiter, "denomb", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denomb"), "1h", &deps.storage).len(), 1 ); assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denomb".to_string(), + scope: Scope::denom("denomb"), upper_limit: Decimal::from_str("0.5").unwrap(), value: Decimal::from_str("0.500000000000000001").unwrap(), } @@ -2148,14 +2217,14 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap(); // 1st division stiil there assert_eq!( - list_divisions(&limiter, "denomb", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denomb"), "1h", &deps.storage).len(), 2 ); @@ -2164,7 +2233,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2172,7 +2241,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denomb".to_string(), + scope: Scope::denom("denomb"), upper_limit: Decimal::from_str("0.491666666666666666").unwrap(), value: Decimal::from_str("0.491666666666666667").unwrap(), } @@ -2180,23 +2249,23 @@ mod tests { // 1st division is not removed yet since limit exceeded first assert_eq!( - list_divisions(&limiter, "denomb", "1h", &deps.storage).len(), + list_divisions(&limiter, &Scope::denom("denomb"), "1h", &deps.storage).len(), 2 ); - let old_divs = list_divisions(&limiter, "denomb", "1h", &deps.storage); + let old_divs = list_divisions(&limiter, &Scope::denom("denomb"), "1h", &deps.storage); let value = Decimal::from_str("0.491666666666666666").unwrap(); limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap(); // 1st division is removed, and add new division assert_eq!( - list_divisions(&limiter, "denomb", "1h", &deps.storage), + list_divisions(&limiter, &Scope::denom("denomb"), "1h", &deps.storage), [ old_divs[1..].to_vec(), vec![Division::new( @@ -2222,7 +2291,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1h", LimiterParams::ChangeLimiter { window_config: config, @@ -2234,7 +2303,7 @@ mod tests { limiter .set_change_limiter_boundary_offset( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1h", Decimal::percent(5), ) @@ -2245,7 +2314,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2255,7 +2324,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2265,7 +2334,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2276,7 +2345,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denomb"), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2284,7 +2353,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: String::from("denomb"), + scope: Scope::denom("denomb"), upper_limit: Decimal::percent(51), value } @@ -2299,7 +2368,7 @@ mod tests { limiter .register( &mut deps.storage, - "denom", + Scope::denom("denom"), "1h", LimiterParams::ChangeLimiter { window_config: WindowConfig { @@ -2318,7 +2387,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denom".to_string(), (Decimal::zero(), value))], + vec![(Scope::denom("denom"), (Decimal::zero(), value))], block_time, ) .unwrap(); @@ -2329,7 +2398,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denom".to_string(), (value, new_value))], + vec![(Scope::denom("denom"), (value, new_value))], new_block_time, ) .unwrap_err(); @@ -2337,7 +2406,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denom".to_string(), + scope: Scope::denom("denom"), upper_limit: Decimal::percent(56), value: new_value, } @@ -2349,7 +2418,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denom".to_string(), (value, new_value))], + vec![(Scope::denom("denom"), (value, new_value))], new_block_time, ) .unwrap(); @@ -2362,7 +2431,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denom".to_string(), (value, new_value))], + vec![(Scope::denom("denom"), (value, new_value))], new_block_time, ) .unwrap(); @@ -2375,7 +2444,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denom".to_string(), (value, final_value))], + vec![(Scope::denom("denom"), (value, final_value))], final_block_time, ) .unwrap(); @@ -2386,7 +2455,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denom".to_string(), (value, new_value))], + vec![(Scope::denom("denom"), (value, new_value))], final_block_time, ) .unwrap_err(); @@ -2394,7 +2463,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denom".to_string(), + scope: Scope::denom("denom"), upper_limit: Decimal::from_str("0.555").unwrap(), value: new_value, } @@ -2409,7 +2478,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1h", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(60), @@ -2420,7 +2489,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1h", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(70), @@ -2436,8 +2505,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a - EPSILON, value_a)), - ("denomb".to_string(), (value_b + EPSILON, value_b)), + (Scope::denom("denoma"), (value_a - EPSILON, value_a)), + (Scope::denom("denomb"), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2450,8 +2519,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a - EPSILON, value_a)), - ("denomb".to_string(), (value_b + EPSILON, value_b)), + (Scope::denom("denoma"), (value_a - EPSILON, value_a)), + (Scope::denom("denomb"), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2460,7 +2529,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::from_str("0.6").unwrap(), value: Decimal::from_str("0.600000000000000001").unwrap(), } @@ -2473,8 +2542,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a + EPSILON, value_a)), - ("denomb".to_string(), (value_b - EPSILON, value_b)), + (Scope::denom("denoma"), (value_a + EPSILON, value_a)), + (Scope::denom("denomb"), (value_b - EPSILON, value_b)), ], block_time, ) @@ -2483,7 +2552,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denomb".to_string(), + scope: Scope::denom("denomb"), upper_limit: Decimal::from_str("0.7").unwrap(), value: Decimal::from_str("0.700000000000000001").unwrap(), } @@ -2496,8 +2565,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a - EPSILON, value_a)), - ("denomb".to_string(), (value_b + EPSILON, value_b)), + (Scope::denom("denoma"), (value_a - EPSILON, value_a)), + (Scope::denom("denomb"), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2510,8 +2579,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a - EPSILON, value_a)), - ("denomb".to_string(), (value_b + EPSILON, value_b)), + (Scope::denom("denoma"), (value_a - EPSILON, value_a)), + (Scope::denom("denomb"), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2529,8 +2598,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a, new_value_a)), - ("denomb".to_string(), (value_b, new_value_b)), + (Scope::denom("denoma"), (value_a, new_value_a)), + (Scope::denom("denomb"), (value_b, new_value_b)), ], block_time, ) @@ -2548,8 +2617,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a, new_value_a)), - ("denomb".to_string(), (value_b, new_value_b)), + (Scope::denom("denoma"), (value_a, new_value_a)), + (Scope::denom("denomb"), (value_b, new_value_b)), ], block_time, ) @@ -2574,7 +2643,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1h", LimiterParams::ChangeLimiter { window_config: config_1h.clone(), @@ -2586,7 +2655,7 @@ mod tests { limiter .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1w", LimiterParams::ChangeLimiter { window_config: config_1w.clone(), @@ -2598,7 +2667,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1h", LimiterParams::ChangeLimiter { window_config: config_1h, @@ -2610,7 +2679,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "1w", LimiterParams::ChangeLimiter { window_config: config_1w, @@ -2622,7 +2691,7 @@ mod tests { limiter .register( &mut deps.storage, - "denomb", + Scope::denom("denomb"), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(55), @@ -2638,8 +2707,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a, value_a)), - ("denomb".to_string(), (value_b, value_b)), + (Scope::denom("denoma"), (value_a, value_a)), + (Scope::denom("denomb"), (value_b, value_b)), ], block_time, ) @@ -2652,9 +2721,9 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value - EPSILON, value)), + (Scope::denom("denoma"), (value - EPSILON, value)), ( - "denomb".to_string(), + Scope::denom("denomb"), (Decimal::one() - value + EPSILON, Decimal::one() - value), ), ], @@ -2665,7 +2734,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::from_str("0.6").unwrap(), value: Decimal::from_str("0.600000000000000001").unwrap(), } @@ -2678,8 +2747,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a + EPSILON, value_a)), - ("denomb".to_string(), (value_b - EPSILON, value_b)), + (Scope::denom("denoma"), (value_a + EPSILON, value_a)), + (Scope::denom("denomb"), (value_b - EPSILON, value_b)), ], block_time, ) @@ -2688,7 +2757,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denomb".to_string(), + scope: Scope::denom("denomb"), upper_limit: Decimal::from_str("0.55").unwrap(), value: Decimal::from_str("0.550000000000000001").unwrap(), } @@ -2701,8 +2770,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a, value_a)), - ("denomb".to_string(), (value_b, value_b)), + (Scope::denom("denoma"), (value_a, value_a)), + (Scope::denom("denomb"), (value_b, value_b)), ], block_time, ) @@ -2718,8 +2787,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a - EPSILON, value_a)), - ("denomb".to_string(), (value_b + EPSILON, value_b)), + (Scope::denom("denoma"), (value_a - EPSILON, value_a)), + (Scope::denom("denomb"), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2728,7 +2797,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::from_str("0.525").unwrap(), value: Decimal::from_str("0.525000000000000001").unwrap(), } @@ -2741,8 +2810,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), (value_a - EPSILON, value_a)), - ("denomb".to_string(), (value_b + EPSILON, value_b)), + (Scope::denom("denoma"), (value_a - EPSILON, value_a)), + (Scope::denom("denomb"), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2751,7 +2820,7 @@ mod tests { assert_eq!( err, ContractError::UpperLimitExceeded { - denom: "denoma".to_string(), + scope: Scope::denom("denoma"), upper_limit: Decimal::from_str("0.55").unwrap(), value: Decimal::from_str("0.550000000000000001").unwrap(), } @@ -2772,7 +2841,7 @@ mod tests { limiters .register( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "1h", LimiterParams::ChangeLimiter { window_config: config, @@ -2784,7 +2853,7 @@ mod tests { limiters .register( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(60), @@ -2795,7 +2864,7 @@ mod tests { limiters .set_change_limiter_boundary_offset( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "1h", Decimal::percent(20), ) @@ -2803,7 +2872,7 @@ mod tests { let limiter = match limiters .limiters - .load(&deps.storage, ("denomc", "1h")) + .load(&deps.storage, (&Scope::denom("denomc").key(), "1h")) .unwrap() { Limiter::ChangeLimiter(limiter) => limiter, @@ -2817,7 +2886,7 @@ mod tests { let err = limiters .set_change_limiter_boundary_offset( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "static", Decimal::percent(20), ) @@ -2834,7 +2903,7 @@ mod tests { let err = limiters .set_change_limiter_boundary_offset( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "1h", Decimal::zero(), ) @@ -2854,7 +2923,7 @@ mod tests { limiters .register( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "1h", LimiterParams::ChangeLimiter { window_config: config, @@ -2866,7 +2935,7 @@ mod tests { limiters .register( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(60), @@ -2878,7 +2947,7 @@ mod tests { limiters .set_static_limiter_upper_limit( &mut deps.storage, - "denomc", + Scope::denom("denomc"), "static", upper_limit, ) @@ -2886,7 +2955,7 @@ mod tests { let limiter = match limiters .limiters - .load(&deps.storage, ("denomc", "static")) + .load(&deps.storage, (&Scope::denom("denomc").key(), "static")) .unwrap() { Limiter::StaticLimiter(limiter) => limiter, @@ -2896,7 +2965,12 @@ mod tests { assert_eq!(limiter.upper_limit, upper_limit); let err = limiters - .set_static_limiter_upper_limit(&mut deps.storage, "denomc", "1h", upper_limit) + .set_static_limiter_upper_limit( + &mut deps.storage, + Scope::denom("denomc"), + "1h", + upper_limit, + ) .unwrap_err(); assert_eq!( @@ -2934,7 +3008,7 @@ mod tests { limiters .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1h", LimiterParams::ChangeLimiter { window_config: config_1h.clone(), @@ -2946,7 +3020,7 @@ mod tests { limiters .register( &mut deps.storage, - "denoma", + Scope::denom("denoma"), "1w", LimiterParams::ChangeLimiter { window_config: config_1w.clone(), @@ -2962,7 +3036,7 @@ mod tests { limiters .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), (value - EPSILON, value))], + vec![(Scope::denom("denoma"), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2974,8 +3048,12 @@ mod tests { .unwrap(); for (denom, window) in keys.iter() { - let divisions = - list_divisions(&limiters, denom.as_str(), window.as_str(), &deps.storage); + let divisions = list_divisions( + &limiters, + &denom.parse().unwrap(), + window.as_str(), + &deps.storage, + ); assert_eq!( divisions, @@ -2983,7 +3061,11 @@ mod tests { ) } - assert_dirty_change_limiters_by_denom!("denoma", &limiters, &deps.storage); + assert_dirty_change_limiters_by_scope!( + &Scope::denom("denoma"), + &limiters, + &deps.storage + ); // reset limiters let block_time = block_time.plus_hours(1); @@ -2992,13 +3074,17 @@ mod tests { .reset_change_limiter_states( &mut deps.storage, block_time, - vec![("denoma".to_string(), value)], + vec![(Scope::denom("denoma").key(), value)].into_iter(), ) .unwrap(); for (denom, window) in keys.iter() { - let divisions = - list_divisions(&limiters, denom.as_str(), window.as_str(), &deps.storage); + let divisions = list_divisions( + &limiters, + &denom.parse().unwrap(), + window.as_str(), + &deps.storage, + ); assert_eq!( divisions, @@ -3010,11 +3096,15 @@ mod tests { fn list_divisions( limiters: &Limiters, - denom: &str, + scope: &Scope, window: &str, storage: &dyn Storage, ) -> Vec { - match limiters.limiters.load(storage, (denom, window)).unwrap() { + match limiters + .limiters + .load(storage, (&scope.key(), window)) + .unwrap() + { Limiter::ChangeLimiter(limiter) => limiter.divisions, Limiter::StaticLimiter(_) => panic!("not a change limiter"), } diff --git a/contracts/transmuter/src/math.rs b/contracts/transmuter/src/math.rs index 90d2d73..fead19a 100644 --- a/contracts/transmuter/src/math.rs +++ b/contracts/transmuter/src/math.rs @@ -176,7 +176,7 @@ mod tests { 5u128, 20u128, 0u128, - Err(MathError::DivideByZeroError(DivideByZeroError { operand: String::from("100")})) + Err(MathError::DivideByZeroError(DivideByZeroError::new())) )] #[case( 1000u128, diff --git a/contracts/transmuter/src/migrations/mod.rs b/contracts/transmuter/src/migrations/mod.rs index 8d355b1..b0da2e1 100644 --- a/contracts/transmuter/src/migrations/mod.rs +++ b/contracts/transmuter/src/migrations/mod.rs @@ -1 +1 @@ -pub mod v3_2_0; +pub mod v4_0_0; diff --git a/contracts/transmuter/src/migrations/v3_2_0.rs b/contracts/transmuter/src/migrations/v3_2_0.rs deleted file mode 100644 index 3c2340f..0000000 --- a/contracts/transmuter/src/migrations/v3_2_0.rs +++ /dev/null @@ -1,106 +0,0 @@ -use cosmwasm_schema::cw_serde; - -use cosmwasm_std::{ensure_eq, DepsMut, Response, Storage}; -use cw2::{ContractVersion, VersionError, CONTRACT}; - -use crate::{ - contract::{CONTRACT_NAME, CONTRACT_VERSION}, - ContractError, -}; - -const FROM_VERSIONS: &[&str] = &["3.0.0", "3.1.0"]; -const TO_VERSION: &str = "3.2.0"; - -#[cw_serde] -pub struct MigrateMsg {} - -pub fn execute_migration(deps: DepsMut) -> Result { - // Assert that the stored contract version matches the expected version before migration - assert_contract_versions(deps.storage, CONTRACT_NAME, FROM_VERSIONS)?; - - // Ensure that the current contract version matches the target version to prevent migration to an incorrect version - ensure_eq!( - CONTRACT_VERSION, - TO_VERSION, - cw2::VersionError::WrongVersion { - expected: TO_VERSION.to_string(), - found: CONTRACT_VERSION.to_string() - } - ); - - // Set the contract version to the target version after successful migration - cw2::set_contract_version(deps.storage, CONTRACT_NAME, TO_VERSION)?; - - // Return a response with an attribute indicating the method that was executed - Ok(Response::new().add_attribute("method", "v3_2_0/execute_migraiton")) -} - -/// Assert that the stored contract version info matches the given value. -/// This is useful during migrations, for making sure that the correct contract -/// is being migrated, and it's being migrated from the correct version. -fn assert_contract_versions( - storage: &dyn Storage, - expected_contract: &str, - expected_versions: &[&str], -) -> Result<(), VersionError> { - let ContractVersion { contract, version } = match CONTRACT.may_load(storage)? { - Some(contract) => contract, - None => return Err(VersionError::NotFound), - }; - - if contract != expected_contract { - return Err(VersionError::WrongContract { - expected: expected_contract.into(), - found: contract, - }); - } - - if !expected_versions.contains(&version.as_str()) { - return Err(VersionError::WrongVersion { - expected: expected_versions.join(","), - found: version, - }); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::mock_dependencies; - - use super::*; - - #[test] - fn test_successful_migration() { - let mut deps = mock_dependencies(); - - for from_version in FROM_VERSIONS { - cw2::set_contract_version(&mut deps.storage, CONTRACT_NAME, from_version.to_string()) - .unwrap(); - - let res = execute_migration(deps.as_mut()).unwrap(); - - assert_eq!( - res, - Response::new().add_attribute("method", "v3_2_0/execute_migraiton") - ); - } - } - - #[test] - fn test_invalid_version() { - let mut deps = mock_dependencies(); - - cw2::set_contract_version(&mut deps.storage, CONTRACT_NAME, "2.0.0").unwrap(); - - let err = execute_migration(deps.as_mut()).unwrap_err(); - assert_eq!( - err, - ContractError::VersionError(cw2::VersionError::WrongVersion { - expected: FROM_VERSIONS.join(","), - found: "2.0.0".to_string() - }) - ); - } -} diff --git a/contracts/transmuter/src/migrations/v4_0_0.rs b/contracts/transmuter/src/migrations/v4_0_0.rs new file mode 100644 index 0000000..54bb0ce --- /dev/null +++ b/contracts/transmuter/src/migrations/v4_0_0.rs @@ -0,0 +1,160 @@ +use std::collections::BTreeMap; + +use cosmwasm_schema::cw_serde; + +use cosmwasm_std::{ensure_eq, DepsMut, Response, Storage}; +use cw2::{ContractVersion, VersionError, CONTRACT}; +use cw_storage_plus::Item; + +use crate::{ + asset::Asset, + contract::{key, CONTRACT_NAME, CONTRACT_VERSION}, + transmuter_pool::TransmuterPool, + ContractError, +}; + +const FROM_VERSION: &str = "3.2.0"; +const TO_VERSION: &str = "4.0.0"; + +#[cw_serde] +pub struct MigrateMsg {} + +#[cw_serde] +pub struct TransmuterPoolV3 { + pub pool_assets: Vec, + // [to-be-added] pub asset_groups: BTreeMap, +} + +pub fn execute_migration(deps: DepsMut) -> Result { + // Assert that the stored contract version matches the expected version before migration + assert_contract_versions(deps.storage, CONTRACT_NAME, FROM_VERSION)?; + + // Ensure that the current contract version matches the target version to prevent migration to an incorrect version + ensure_eq!( + CONTRACT_VERSION, + TO_VERSION, + cw2::VersionError::WrongVersion { + expected: TO_VERSION.to_string(), + found: CONTRACT_VERSION.to_string() + } + ); + + // add asset groups to the pool + let pool_v3: TransmuterPoolV3 = Item::::new(key::POOL).load(deps.storage)?; + + let pool_v4 = TransmuterPool { + pool_assets: pool_v3.pool_assets, + asset_groups: BTreeMap::new(), + }; + + Item::::new(key::POOL).save(deps.storage, &pool_v4)?; + + // Set the contract version to the target version after successful migration + cw2::set_contract_version(deps.storage, CONTRACT_NAME, TO_VERSION)?; + + // Return a response with an attribute indicating the method that was executed + Ok(Response::new().add_attribute("method", "v4_0_0/execute_migraiton")) +} + +/// Assert that the stored contract version info matches the given value. +/// This is useful during migrations, for making sure that the correct contract +/// is being migrated, and it's being migrated from the correct version. +fn assert_contract_versions( + storage: &dyn Storage, + expected_contract: &str, + expected_version: &str, +) -> Result<(), VersionError> { + let ContractVersion { contract, version } = match CONTRACT.may_load(storage)? { + Some(contract) => contract, + None => return Err(VersionError::NotFound), + }; + + if contract != expected_contract { + return Err(VersionError::WrongContract { + expected: expected_contract.into(), + found: contract, + }); + } + + if version.as_str() != expected_version { + return Err(VersionError::WrongVersion { + expected: expected_version.to_string(), + found: version, + }); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{testing::mock_dependencies, Uint128}; + + use super::*; + + #[test] + fn test_successful_migration() { + let mut deps = mock_dependencies(); + + cw2::set_contract_version(&mut deps.storage, CONTRACT_NAME, FROM_VERSION).unwrap(); + + let pool_assets = vec![ + Asset::new(Uint128::from(100u128), "uusdt", Uint128::from(1u128)).unwrap(), + Asset::new(Uint128::from(200u128), "uusdc", Uint128::from(1u128)).unwrap(), + ]; + let pool_v3 = TransmuterPoolV3 { + pool_assets: pool_assets.clone(), + }; + + Item::new(key::POOL) + .save(&mut deps.storage, &pool_v3) + .unwrap(); + + let res = execute_migration(deps.as_mut()).unwrap(); + + let pool = Item::::new(key::POOL) + .load(&deps.storage) + .unwrap(); + + assert_eq!( + pool, + TransmuterPool { + pool_assets, + asset_groups: BTreeMap::new() // migrgate with empty asset groups + } + ); + + assert_eq!( + res, + Response::new().add_attribute("method", "v4_0_0/execute_migraiton") + ); + } + + #[test] + fn test_invalid_version() { + let mut deps = mock_dependencies(); + + let pool_assets = vec![ + Asset::new(Uint128::from(100u128), "uusdt", Uint128::from(1u128)).unwrap(), + Asset::new(Uint128::from(200u128), "uusdc", Uint128::from(1u128)).unwrap(), + ]; + let pool_v3 = TransmuterPoolV3 { + pool_assets: pool_assets.clone(), + }; + + Item::new(key::POOL) + .save(&mut deps.storage, &pool_v3) + .unwrap(); + + cw2::set_contract_version(&mut deps.storage, CONTRACT_NAME, "3.0.0").unwrap(); + + let err = execute_migration(deps.as_mut()).unwrap_err(); + assert_eq!( + err, + ContractError::VersionError(cw2::VersionError::WrongVersion { + expected: FROM_VERSION.to_string(), + found: "3.0.0".to_string() + }) + ); + } +} diff --git a/contracts/transmuter/src/role/admin.rs b/contracts/transmuter/src/role/admin.rs index fcdedb1..7704a65 100644 --- a/contracts/transmuter/src/role/admin.rs +++ b/contracts/transmuter/src/role/admin.rs @@ -4,8 +4,8 @@ use cw_storage_plus::Item; use crate::ContractError; -pub struct Admin<'a> { - state: Item<'a, AdminState>, +pub struct Admin { + state: Item, } /// State of the admin to be stored in the contract storage @@ -15,8 +15,8 @@ pub enum AdminState { Transferring { current: Addr, candidate: Addr }, } -impl<'a> Admin<'a> { - pub const fn new(namespace: &'a str) -> Self { +impl Admin { + pub const fn new(namespace: &'static str) -> Self { Self { state: Item::new(namespace), } diff --git a/contracts/transmuter/src/role/mod.rs b/contracts/transmuter/src/role/mod.rs index 51569e7..8aa1777 100644 --- a/contracts/transmuter/src/role/mod.rs +++ b/contracts/transmuter/src/role/mod.rs @@ -5,13 +5,13 @@ use crate::{ensure_admin_authority, ContractError}; pub mod admin; pub mod moderator; -pub struct Role<'a> { - pub admin: admin::Admin<'a>, - pub moderator: moderator::Moderator<'a>, +pub struct Role { + pub admin: admin::Admin, + pub moderator: moderator::Moderator, } -impl<'a> Role<'a> { - pub const fn new(admin_namespace: &'a str, moderator_namespace: &'a str) -> Self { +impl Role { + pub const fn new(admin_namespace: &'static str, moderator_namespace: &'static str) -> Self { Role { admin: admin::Admin::new(admin_namespace), moderator: moderator::Moderator::new(moderator_namespace), diff --git a/contracts/transmuter/src/role/moderator.rs b/contracts/transmuter/src/role/moderator.rs index 72c066f..666033c 100644 --- a/contracts/transmuter/src/role/moderator.rs +++ b/contracts/transmuter/src/role/moderator.rs @@ -3,12 +3,12 @@ use cw_storage_plus::Item; use crate::ContractError; -pub struct Moderator<'a> { - moderator: Item<'a, Addr>, +pub struct Moderator { + moderator: Item, } -impl<'a> Moderator<'a> { - pub const fn new(namespace: &'a str) -> Self { +impl Moderator { + pub const fn new(namespace: &'static str) -> Self { Self { moderator: Item::new(namespace), } diff --git a/contracts/transmuter/src/scope.rs b/contracts/transmuter/src/scope.rs new file mode 100644 index 0000000..9ee3391 --- /dev/null +++ b/contracts/transmuter/src/scope.rs @@ -0,0 +1,58 @@ +use cosmwasm_schema::cw_serde; +use std::{fmt::Display, str::FromStr}; + +/// Scope for configuring limiters & rebalacing incentive for +#[cw_serde] +#[serde(tag = "type", content = "value")] +#[derive(Eq, Hash)] +pub enum Scope { + Denom(String), + AssetGroup(String), +} + +impl Display for Scope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.key()) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Invalid scope: {0}, must start with 'denom::' or 'asset_group::'")] +pub struct ParseScopeErr(String); + +impl FromStr for Scope { + type Err = ParseScopeErr; + + fn from_str(s: &str) -> Result { + if s.starts_with("denom::") { + s.strip_prefix("denom::") + .map(|s| Scope::Denom(s.to_string())) + .ok_or(ParseScopeErr(s.to_string())) + } else if s.starts_with("asset_group::") { + s.strip_prefix("asset_group::") + .map(|s| Scope::AssetGroup(s.to_string())) + .ok_or(ParseScopeErr(s.to_string())) + } else { + Err(ParseScopeErr(s.to_string())) + } + } +} + +impl Scope { + pub fn key(&self) -> String { + match self { + Scope::Denom(denom) => format!("denom::{}", denom), + Scope::AssetGroup(label) => format!("asset_group::{}", label), + } + } +} + +impl Scope { + pub fn denom(denom: &str) -> Self { + Scope::Denom(denom.to_string()) + } + + pub fn asset_group(label: &str) -> Self { + Scope::AssetGroup(label.to_string()) + } +} diff --git a/contracts/transmuter/src/sudo.rs b/contracts/transmuter/src/sudo.rs index 0b7116e..3609cd8 100644 --- a/contracts/transmuter/src/sudo.rs +++ b/contracts/transmuter/src/sudo.rs @@ -176,8 +176,9 @@ mod tests { swap::{SwapExactAmountInResponseData, SwapExactAmountOutResponseData}, }; use cosmwasm_std::{ - testing::{mock_dependencies, mock_env, mock_info, MOCK_CONTRACT_ADDR}, - to_json_binary, BankMsg, Reply, SubMsgResponse, SubMsgResult, + coin, + testing::{message_info, mock_dependencies, mock_env, MOCK_CONTRACT_ADDR}, + to_json_binary, BankMsg, Binary, MsgResponse, Reply, SubMsgResponse, SubMsgResult, }; use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ MsgBurn, MsgCreateDenomResponse, MsgMint, @@ -186,15 +187,16 @@ mod tests { #[test] fn test_swap_exact_amount_in() { let mut deps = mock_dependencies(); + let someone = deps.api.addr_make("someone"); + let admin = deps.api.addr_make("admin"); + let user = deps.api.addr_make("user"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply - deps.querier.update_balance( - "someone", - vec![Coin::new(1, "axlusdc"), Coin::new(1, "whusdc")], - ); + deps.querier + .bank + .update_balance(&someone, vec![coin(1, "axlusdc"), coin(1, "whusdc")]); - let admin = "admin"; - let user = "user"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("axlusdc"), @@ -203,10 +205,10 @@ mod tests { alloyed_asset_subdenom: "uusdc".to_string(), alloyed_asset_normalization_factor: Uint128::one(), admin: Some(admin.to_string()), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -217,18 +219,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -236,11 +227,11 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info( - user, + message_info( + &user, &[ - Coin::new(1_000_000_000_000, "axlusdc"), - Coin::new(1_000_000_000_000, "whusdc"), + coin(1_000_000_000_000, "axlusdc"), + coin(1_000_000_000_000, "whusdc"), ], ), join_pool_msg, @@ -250,7 +241,7 @@ mod tests { // Test swap exact amount in with 0 amount in should error with ZeroValueOperation let swap_msg = SudoMsg::SwapExactAmountIn { sender: user.to_string(), - token_in: Coin::new(0, "axlusdc".to_string()), + token_in: coin(0, "axlusdc".to_string()), token_out_denom: "whusdc".to_string(), token_out_min_amount: Uint128::from(0u128), swap_fee: Decimal::zero(), @@ -262,7 +253,7 @@ mod tests { // Test swap exact amount in with only pool assets let swap_msg = SudoMsg::SwapExactAmountIn { sender: user.to_string(), - token_in: Coin::new(500, "axlusdc".to_string()), + token_in: coin(500, "axlusdc".to_string()), token_out_denom: "whusdc".to_string(), token_out_min_amount: Uint128::from(500u128), swap_fee: Decimal::zero(), @@ -274,7 +265,7 @@ mod tests { .add_attribute("method", "swap_exact_amount_in") .add_message(BankMsg::Send { to_address: user.to_string(), - amount: vec![Coin::new(500, "whusdc".to_string())], + amount: vec![coin(500, "whusdc".to_string())], }) .set_data( to_json_binary(&SwapExactAmountInResponseData { @@ -287,10 +278,11 @@ mod tests { // Test swap with token in as alloyed asset deps.querier - .update_balance(MOCK_CONTRACT_ADDR, vec![Coin::new(500, alloyed_denom)]); + .bank + .update_balance(MOCK_CONTRACT_ADDR, vec![coin(500, alloyed_denom)]); let swap_msg = SudoMsg::SwapExactAmountIn { sender: user.to_string(), - token_in: Coin::new(500, alloyed_denom), + token_in: coin(500, alloyed_denom), token_out_denom: "whusdc".to_string(), token_out_min_amount: Uint128::from(500u128), swap_fee: Decimal::zero(), @@ -301,13 +293,13 @@ mod tests { let expected = Response::new() .add_attribute("method", "swap_exact_amount_in") .add_message(MsgBurn { - amount: Some(Coin::new(500, alloyed_denom).into()), + amount: Some(coin(500, alloyed_denom).into()), sender: env.contract.address.to_string(), burn_from_address: env.contract.address.to_string(), }) .add_message(BankMsg::Send { to_address: user.to_string(), - amount: vec![Coin::new(500, "whusdc".to_string())], + amount: vec![coin(500, "whusdc".to_string())], }) .set_data( to_json_binary(&SwapExactAmountInResponseData { @@ -321,7 +313,7 @@ mod tests { // Test swap with token out as alloyed asset let swap_msg = SudoMsg::SwapExactAmountIn { sender: user.to_string(), - token_in: Coin::new(500, "whusdc".to_string()), + token_in: coin(500, "whusdc".to_string()), token_out_denom: alloyed_denom.to_string(), token_out_min_amount: Uint128::from(500u128), swap_fee: Decimal::zero(), @@ -333,7 +325,7 @@ mod tests { .add_attribute("method", "swap_exact_amount_in") .add_message(MsgMint { sender: env.contract.address.to_string(), - amount: Some(Coin::new(500, alloyed_denom).into()), + amount: Some(coin(500, alloyed_denom).into()), mint_to_address: user.to_string(), }) .set_data( @@ -348,7 +340,7 @@ mod tests { // Test case for ensure token_out amount is greater than or equal to token_out_min_amount let swap_msg = SudoMsg::SwapExactAmountIn { sender: user.to_string(), - token_in: Coin::new(500, "whusdc".to_string()), + token_in: coin(500, "whusdc".to_string()), token_out_denom: "axlusdc".to_string(), token_out_min_amount: Uint128::from(1000u128), // set min amount greater than token_in swap_fee: Decimal::zero(), @@ -367,7 +359,7 @@ mod tests { // Test case for ensure token_out amount is greater than or equal to token_out_min_amount but token_in is alloyed asset let swap_msg = SudoMsg::SwapExactAmountIn { sender: user.to_string(), - token_in: Coin::new(500, alloyed_denom.to_string()), + token_in: coin(500, alloyed_denom.to_string()), token_out_denom: "axlusdc".to_string(), token_out_min_amount: Uint128::from(1000u128), // set min amount greater than token_in swap_fee: Decimal::zero(), @@ -386,7 +378,7 @@ mod tests { // Test case for ensure token_out amount is greater than or equal to token_out_min_amount but token_out is alloyed asset let swap_msg = SudoMsg::SwapExactAmountIn { sender: user.to_string(), - token_in: Coin::new(500, "whusdc".to_string()), + token_in: coin(500, "whusdc".to_string()), token_out_denom: alloyed_denom.to_string(), token_out_min_amount: Uint128::from(1000u128), // set min amount greater than token_in swap_fee: Decimal::zero(), @@ -406,15 +398,16 @@ mod tests { #[test] fn test_swap_exact_token_out() { let mut deps = mock_dependencies(); + let admin = deps.api.addr_make("admin"); + let user = deps.api.addr_make("user"); + let someone = deps.api.addr_make("someone"); + let moderator = deps.api.addr_make("moderator"); // make denom has non-zero total supply - deps.querier.update_balance( - "someone", - vec![Coin::new(1, "axlusdc"), Coin::new(1, "whusdc")], - ); + deps.querier + .bank + .update_balance(&someone, vec![coin(1, "axlusdc"), coin(1, "whusdc")]); - let admin = "admin"; - let user = "user"; let init_msg = InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("axlusdc"), @@ -423,10 +416,10 @@ mod tests { alloyed_asset_subdenom: "uusdc".to_string(), alloyed_asset_normalization_factor: Uint128::one(), admin: Some(admin.to_string()), - moderator: "moderator".to_string(), + moderator: moderator.to_string(), }; let env = mock_env(); - let info = mock_info(admin, &[]); + let info = message_info(&admin, &[]); // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info, init_msg).unwrap(); @@ -437,18 +430,7 @@ mod tests { reply( deps.as_mut(), env.clone(), - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - MsgCreateDenomResponse { - new_token_denom: alloyed_denom.to_string(), - } - .into(), - ), - }), - }, + reply_create_denom_response(alloyed_denom), ) .unwrap(); @@ -456,11 +438,11 @@ mod tests { execute( deps.as_mut(), env.clone(), - mock_info( - user, + message_info( + &user, &[ - Coin::new(1_000_000_000_000, "axlusdc"), - Coin::new(1_000_000_000_000, "whusdc"), + coin(1_000_000_000_000, "axlusdc"), + coin(1_000_000_000_000, "whusdc"), ], ), join_pool_msg, @@ -471,7 +453,7 @@ mod tests { let swap_msg = SudoMsg::SwapExactAmountOut { sender: user.to_string(), token_in_denom: "whusdc".to_string(), - token_out: Coin::new(0, "axlusdc".to_string()), + token_out: coin(0, "axlusdc".to_string()), token_in_max_amount: Uint128::from(0u128), swap_fee: Decimal::zero(), }; @@ -484,7 +466,7 @@ mod tests { sender: user.to_string(), token_in_denom: "axlusdc".to_string(), token_in_max_amount: Uint128::from(500u128), - token_out: Coin::new(500, "whusdc".to_string()), + token_out: coin(500, "whusdc".to_string()), swap_fee: Decimal::zero(), }; @@ -494,7 +476,7 @@ mod tests { .add_attribute("method", "swap_exact_amount_out") .add_message(BankMsg::Send { to_address: user.to_string(), - amount: vec![Coin::new(500, "whusdc".to_string())], + amount: vec![coin(500, "whusdc".to_string())], }) .set_data( to_json_binary(&SwapExactAmountOutResponseData { @@ -507,13 +489,14 @@ mod tests { // Test swap with token in as alloyed asset deps.querier - .update_balance(MOCK_CONTRACT_ADDR, vec![Coin::new(500, alloyed_denom)]); + .bank + .update_balance(MOCK_CONTRACT_ADDR, vec![coin(500, alloyed_denom)]); let swap_msg = SudoMsg::SwapExactAmountOut { sender: user.to_string(), token_in_denom: alloyed_denom.to_string(), token_in_max_amount: Uint128::from(500u128), - token_out: Coin::new(500, "whusdc".to_string()), + token_out: coin(500, "whusdc".to_string()), swap_fee: Decimal::zero(), }; @@ -522,13 +505,13 @@ mod tests { let expected = Response::new() .add_attribute("method", "swap_exact_amount_out") .add_message(MsgBurn { - amount: Some(Coin::new(500, alloyed_denom).into()), + amount: Some(coin(500, alloyed_denom).into()), sender: env.contract.address.to_string(), burn_from_address: env.contract.address.to_string(), }) .add_message(BankMsg::Send { to_address: user.to_string(), - amount: vec![Coin::new(500, "whusdc".to_string())], + amount: vec![coin(500, "whusdc".to_string())], }) .set_data( to_json_binary(&SwapExactAmountOutResponseData { @@ -544,7 +527,7 @@ mod tests { sender: user.to_string(), token_in_denom: "whusdc".to_string(), token_in_max_amount: Uint128::from(500u128), - token_out: Coin::new(500, alloyed_denom.to_string()), + token_out: coin(500, alloyed_denom.to_string()), swap_fee: Decimal::zero(), }; @@ -554,7 +537,7 @@ mod tests { .add_attribute("method", "swap_exact_amount_out") .add_message(MsgMint { sender: env.contract.address.to_string(), - amount: Some(Coin::new(500, alloyed_denom).into()), + amount: Some(coin(500, alloyed_denom).into()), mint_to_address: user.to_string(), }) .set_data( @@ -571,7 +554,7 @@ mod tests { sender: user.to_string(), token_in_denom: "whusdc".to_string(), token_in_max_amount: Uint128::from(500u128), // set max amount less than token_out - token_out: Coin::new(1000, "axlusdc".to_string()), + token_out: coin(1000, "axlusdc".to_string()), swap_fee: Decimal::zero(), }; @@ -590,7 +573,7 @@ mod tests { sender: user.to_string(), token_in_denom: alloyed_denom.to_string(), token_in_max_amount: Uint128::from(500u128), // set max amount less than token_out - token_out: Coin::new(1000, "axlusdc".to_string()), + token_out: coin(1000, "axlusdc".to_string()), swap_fee: Decimal::zero(), }; @@ -609,7 +592,7 @@ mod tests { sender: user.to_string(), token_in_denom: "whusdc".to_string(), token_in_max_amount: Uint128::from(500u128), // set max amount less than token_out - token_out: Coin::new(1000, alloyed_denom.to_string()), + token_out: coin(1000, alloyed_denom.to_string()), swap_fee: Decimal::zero(), }; @@ -623,4 +606,27 @@ mod tests { }) ); } + + fn reply_create_denom_response(alloyed_denom: &str) -> Reply { + let msg_create_denom_response = MsgCreateDenomResponse { + new_token_denom: alloyed_denom.to_string(), + }; + + Reply { + id: 1, + result: SubMsgResult::Ok( + #[allow(deprecated)] + SubMsgResponse { + events: vec![], + data: Some(msg_create_denom_response.clone().into()), // DEPRECATED + msg_responses: vec![MsgResponse { + type_url: MsgCreateDenomResponse::TYPE_URL.to_string(), + value: msg_create_denom_response.into(), + }], + }, + ), + payload: Binary::new(vec![]), + gas_used: 0, + } + } } diff --git a/contracts/transmuter/src/swap.rs b/contracts/transmuter/src/swap.rs index 88348a9..4928ae5 100644 --- a/contracts/transmuter/src/swap.rs +++ b/contracts/transmuter/src/swap.rs @@ -1,9 +1,9 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - ensure, ensure_eq, to_json_binary, Addr, BankMsg, Coin, Decimal, Deps, DepsMut, Env, Response, - StdError, Storage, Uint128, + coin, ensure, ensure_eq, to_json_binary, Addr, BankMsg, Coin, Decimal, Deps, DepsMut, Env, + Response, StdError, Storage, Uint128, }; use osmosis_std::types::osmosis::tokenfactory::v1beta1::{MsgBurn, MsgMint}; use serde::Serialize; @@ -11,14 +11,16 @@ use serde::Serialize; use crate::{ alloyed_asset::{swap_from_alloyed, swap_to_alloyed}, contract::Transmuter, - transmuter_pool::{AmountConstraint, TransmuterPool}, + corruptable::Corruptable, + scope::Scope, + transmuter_pool::{AmountConstraint, AssetGroup, TransmuterPool}, ContractError, }; /// Swap fee is hardcoded to zero intentionally. pub const SWAP_FEE: Decimal = Decimal::zero(); -impl Transmuter<'_> { +impl Transmuter { /// Getting the [SwapVariant] of the swap operation /// assuming the swap token is not pub fn swap_variant( @@ -98,7 +100,7 @@ impl Transmuter<'_> { token_out_amount, self.alloyed_asset.get_normalization_factor(deps.storage)?, )?; - let tokens_in = vec![Coin::new(in_amount.u128(), token_in_denom)]; + let tokens_in = vec![coin(in_amount.u128(), token_in_denom)]; let response = set_data_if_sudo( response, @@ -124,15 +126,21 @@ impl Transmuter<'_> { ContractError::ZeroValueOperation {} ); - let prev_weights = pool.weights_map()?; + let prev_weights = pool.asset_weights()?.unwrap_or_default(); pool.join_pool(&tokens_in)?; // check and update limiters only if pool assets are not zero - if let Some(updated_weights) = pool.weights()? { + if let Some(updated_weights) = pool.asset_weights()? { + let scope_value_pairs = construct_scope_value_pairs( + prev_weights, + updated_weights, + pool.asset_groups.clone(), + )?; + self.limiters.check_limits_and_update( deps.storage, - pair_weights_by_denom(prev_weights, updated_weights), + scope_value_pairs, env.block.time, )?; } @@ -143,7 +151,7 @@ impl Transmuter<'_> { self.pool.save(deps.storage, &pool)?; - let alloyed_asset_out = Coin::new( + let alloyed_asset_out = coin( out_amount.u128(), self.alloyed_asset.get_alloyed_denom(deps.storage)?, ); @@ -194,7 +202,7 @@ impl Transmuter<'_> { }, )?; - let tokens_out = vec![Coin::new(out_amount.u128(), token_out_denom)]; + let tokens_out = vec![coin(out_amount.u128(), token_out_denom)]; (token_in_amount, tokens_out, response) } @@ -272,6 +280,18 @@ impl Transmuter<'_> { }? .to_string(); + let denoms_in_corrupted_asset_group = pool + .asset_groups + .iter() + .flat_map(|(_, asset_group)| { + if asset_group.is_corrupted() { + asset_group.denoms().to_vec() + } else { + vec![] + } + }) + .collect::>(); + let is_force_exit_corrupted_assets = tokens_out.iter().all(|coin| { let total_liquidity = pool .get_pool_asset_by_denom(&coin.denom) @@ -279,8 +299,11 @@ impl Transmuter<'_> { .unwrap_or_default(); let is_redeeming_total_liquidity = coin.amount == total_liquidity; + let is_under_corrupted_asset_group = + denoms_in_corrupted_asset_group.contains(&coin.denom); - pool.is_corrupted_asset(&coin.denom) && is_redeeming_total_liquidity + is_redeeming_total_liquidity + && (is_under_corrupted_asset_group || pool.is_corrupted_asset(&coin.denom)) }); // If all tokens out are corrupted assets and exit with all remaining liquidity @@ -290,21 +313,38 @@ impl Transmuter<'_> { // change limiter needs reset if force redemption since it gets by passed // the current state will not be accurate + + let asset_weights_iter = pool + .asset_weights()? + .unwrap_or_default() + .into_iter() + .map(|(denom, weight)| (Scope::denom(&denom).key(), weight)); + let asset_group_weights_iter = pool + .asset_group_weights()? + .into_iter() + .map(|(label, weight)| (Scope::asset_group(&label).key(), weight)); + self.limiters.reset_change_limiter_states( deps.storage, env.block.time, - pool.weights()?.unwrap_or_default(), + asset_weights_iter.chain(asset_group_weights_iter), )?; } else { - let prev_weights = pool.weights_map()?; + let prev_weights = pool.asset_weights()?.unwrap_or_default(); pool.exit_pool(&tokens_out)?; // check and update limiters only if pool assets are not zero - if let Some(updated_weights) = pool.weights()? { + if let Some(updated_weights) = pool.asset_weights()? { + let scope_value_pairs = construct_scope_value_pairs( + prev_weights, + updated_weights, + pool.asset_groups.clone(), + )?; + self.limiters.check_limits_and_update( deps.storage, - pair_weights_by_denom(prev_weights, updated_weights), + scope_value_pairs, env.block.time, )?; } @@ -319,7 +359,7 @@ impl Transmuter<'_> { amount: tokens_out, }; - let alloyed_asset_to_burn = Coin::new( + let alloyed_asset_to_burn = coin( in_amount.u128(), self.alloyed_asset.get_alloyed_denom(deps.storage)?, ) @@ -345,7 +385,7 @@ impl Transmuter<'_> { env: Env, ) -> Result { let pool = self.pool.load(deps.storage)?; - let prev_weights = pool.weights_map()?; + let prev_weights = pool.asset_weights()?.unwrap_or_default(); let (mut pool, actual_token_out) = self.out_amt_given_in(deps.as_ref(), pool, token_in, token_out_denom)?; @@ -360,10 +400,16 @@ impl Transmuter<'_> { ); // check and update limiters only if pool assets are not zero - if let Some(updated_weights) = pool.weights()? { + if let Some(updated_weights) = pool.asset_weights()? { + let scope_value_pairs = construct_scope_value_pairs( + prev_weights, + updated_weights, + pool.asset_groups.clone(), + )?; + self.limiters.check_limits_and_update( deps.storage, - pair_weights_by_denom(prev_weights, updated_weights), + scope_value_pairs, env.block.time, )?; } @@ -397,7 +443,7 @@ impl Transmuter<'_> { env: Env, ) -> Result { let pool = self.pool.load(deps.storage)?; - let prev_weights = pool.weights_map()?; + let prev_weights = pool.asset_weights()?.unwrap_or_default(); let (mut pool, actual_token_in) = self.in_amt_given_out( deps.as_ref(), @@ -415,10 +461,15 @@ impl Transmuter<'_> { ); // check and update limiters only if pool assets are not zero - if let Some(updated_weights) = pool.weights()? { + if let Some(updated_weights) = pool.asset_weights()? { + let scope_value_pairs = construct_scope_value_pairs( + prev_weights, + updated_weights, + pool.asset_groups.clone(), + )?; self.limiters.check_limits_and_update( deps.storage, - pair_weights_by_denom(prev_weights, updated_weights), + scope_value_pairs, env.block.time, )?; } @@ -463,7 +514,7 @@ impl Transmuter<'_> { token_out.amount, self.alloyed_asset.get_normalization_factor(deps.storage)?, )?; - let token_in = Coin::new(token_in_amount.u128(), token_in_denom); + let token_in = coin(token_in_amount.u128(), token_in_denom); pool.join_pool(&[token_in.clone()])?; (pool, token_in) } @@ -477,7 +528,7 @@ impl Transmuter<'_> { self.alloyed_asset.get_normalization_factor(deps.storage)?, vec![(token_out.clone(), token_out_norm_factor)], )?; - let token_in = Coin::new(token_in_amount.u128(), token_in_denom); + let token_in = coin(token_in_amount.u128(), token_in_denom); pool.exit_pool(&[token_out])?; (pool, token_in) } @@ -523,7 +574,7 @@ impl Transmuter<'_> { Uint128::zero(), self.alloyed_asset.get_normalization_factor(deps.storage)?, )?; - let token_out = Coin::new(token_out_amount.u128(), token_out_denom); + let token_out = coin(token_out_amount.u128(), token_out_denom); pool.join_pool(&[token_in])?; (pool, token_out) } @@ -538,7 +589,7 @@ impl Transmuter<'_> { token_out_norm_factor, Uint128::zero(), )?; - let token_out = Coin::new(token_out_amount.u128(), token_out_denom); + let token_out = coin(token_out_amount.u128(), token_out_denom); pool.exit_pool(&[token_out.clone()])?; (pool, token_out) } @@ -585,11 +636,33 @@ impl Transmuter<'_> { storage: &mut dyn Storage, pool: &mut TransmuterPool, ) -> Result<(), ContractError> { + // remove corrupted assets for corrupted in pool.clone().corrupted_assets() { if corrupted.amount().is_zero() { - pool.remove_corrupted_asset(corrupted.denom())?; + pool.remove_asset(corrupted.denom())?; self.limiters - .uncheck_deregister_all_for_denom(storage, corrupted.denom())?; + .uncheck_deregister_all_for_scope(storage, Scope::denom(corrupted.denom()))?; + } + } + + // remove assets from asset groups + for (label, asset_group) in pool.clone().asset_groups { + // if asset group is corrupted + if asset_group.is_corrupted() { + // remove asset from pool if amount is zero. + // removing asset here will also remove it from the asset group + for denom in asset_group.denoms() { + if pool.get_pool_asset_by_denom(denom)?.amount().is_zero() { + pool.remove_asset(denom)?; + } + } + + // remove asset group is removed + // remove limiters for asset group as well + if pool.asset_groups.get(&label).is_none() { + self.limiters + .uncheck_deregister_all_for_scope(storage, Scope::asset_group(&label))?; + } } } @@ -597,18 +670,49 @@ impl Transmuter<'_> { } } -fn pair_weights_by_denom( +fn construct_scope_value_pairs( prev_weights: BTreeMap, - updated_weights: Vec<(String, Decimal)>, -) -> Vec<(String, (Decimal, Decimal))> { - let mut denom_weight_pairs = Vec::new(); + updated_weights: BTreeMap, + asset_group: BTreeMap, +) -> Result, StdError> { + let mut denom_weight_pairs: HashMap = HashMap::new(); + let mut asset_group_weight_pairs: HashMap = HashMap::new(); + + // Reverse index the asset groups + let mut asset_groups_of_denom = HashMap::new(); + for (group, asset_group) in asset_group { + for denom in asset_group.into_denoms() { + asset_groups_of_denom + .entry(denom) + .or_insert_with(Vec::new) + .push(group.clone()); + } + } + + for (denom, weight) in &updated_weights { + let prev_weight = prev_weights.get(denom.as_str()).unwrap_or(weight); + denom_weight_pairs.insert(Scope::denom(denom), (*prev_weight, *weight)); + + for group in asset_groups_of_denom.get(denom).unwrap_or(&vec![]) { + match asset_group_weight_pairs.get_mut(&Scope::asset_group(group)) { + Some((prev, curr)) => { + *prev = prev.checked_add(*prev_weight)?; + *curr = curr.checked_add(*weight)?; + } + None => { + asset_group_weight_pairs + .insert(Scope::asset_group(group), (*prev_weight, *weight)); + } + } - for (denom, weight) in updated_weights { - let prev_weight = prev_weights.get(denom.as_str()).unwrap_or(&weight); - denom_weight_pairs.push((denom, (*prev_weight, weight))); + // TODO: check for invalid cases like total weight is not 1, proptest it + } } - denom_weight_pairs + Ok(denom_weight_pairs + .into_iter() + .chain(asset_group_weight_pairs.into_iter()) + .collect()) } /// Possible variants of swap, depending on the input and output tokens @@ -695,7 +799,7 @@ pub enum BurnTarget { #[cfg(test)] mod tests { - use crate::{asset::Asset, limiter::LimiterParams}; + use crate::{asset::Asset, corruptable::Corruptable, limiter::LimiterParams}; use super::*; use cosmwasm_std::{ @@ -722,7 +826,7 @@ mod tests { #[case] res: Result, ) { let mut deps = cosmwasm_std::testing::mock_dependencies(); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -735,21 +839,21 @@ mod tests { #[case( Entrypoint::Exec, SwapToAlloyedConstraint::ExactIn { - tokens_in: &[Coin::new(100, "denom1")], + tokens_in: &[coin(100, "denom1")], token_out_min_amount: Uint128::one(), }, Addr::unchecked("addr1"), Ok(Response::new() .add_message(MsgMint { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(10000u128, "alloyed").into()), + amount: Some(coin(10000u128, "alloyed").into()), mint_to_address: "addr1".to_string() })), )] #[case( Entrypoint::Sudo, SwapToAlloyedConstraint::ExactIn { - tokens_in: &[Coin::new(100, "denom1")], + tokens_in: &[coin(100, "denom1")], token_out_min_amount: Uint128::one(), }, Addr::unchecked("addr1"), @@ -759,7 +863,7 @@ mod tests { }).unwrap()) .add_message(MsgMint { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(10000u128, "alloyed").into()), + amount: Some(coin(10000u128, "alloyed").into()), mint_to_address: "addr1".to_string() })), )] @@ -774,7 +878,7 @@ mod tests { Ok(Response::new() .add_message(MsgMint { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(10000u128, "alloyed").into()), + amount: Some(coin(10000u128, "alloyed").into()), mint_to_address: "addr1".to_string() })), )] @@ -792,7 +896,7 @@ mod tests { }).unwrap()) .add_message(MsgMint { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(10000u128, "alloyed").into()), + amount: Some(coin(10000u128, "alloyed").into()), mint_to_address: "addr1".to_string() })), )] @@ -803,7 +907,7 @@ mod tests { #[case] expected_res: Result, ) { let mut deps = mock_dependencies(); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -823,6 +927,7 @@ mod tests { Asset::new(Uint128::from(1000u128), "denom1", 1u128).unwrap(), Asset::new(Uint128::from(1000u128), "denom2", 10u128).unwrap(), ], + asset_groups: BTreeMap::new(), }, ) .unwrap(); @@ -851,12 +956,12 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100u128, "alloyed").into()), + amount: Some(coin(100u128, "alloyed").into()), burn_from_address: "addr1".to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1u128, "denom1")] + amount: vec![coin(1u128, "denom1")] })) )] #[case( @@ -871,12 +976,12 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100u128, "alloyed").into()), + amount: Some(coin(100u128, "alloyed").into()), burn_from_address: MOCK_CONTRACT_ADDR.to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1u128, "denom1")] + amount: vec![coin(1u128, "denom1")] }) .set_data(to_json_binary(&SwapExactAmountInResponseData { token_out_amount: Uint128::from(1u128) @@ -885,7 +990,7 @@ mod tests { #[case( Entrypoint::Exec, SwapFromAlloyedConstraint::ExactOut { - tokens_out: &[Coin::new(1u128, "denom1")], + tokens_out: &[coin(1u128, "denom1")], token_in_max_amount: Uint128::from(100u128), }, BurnTarget::SenderAccount, @@ -893,18 +998,18 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100u128, "alloyed").into()), + amount: Some(coin(100u128, "alloyed").into()), burn_from_address: "addr1".to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1u128, "denom1")] + amount: vec![coin(1u128, "denom1")] })) )] #[case( Entrypoint::Sudo, SwapFromAlloyedConstraint::ExactOut { - tokens_out: &[Coin::new(1u128, "denom1")], + tokens_out: &[coin(1u128, "denom1")], token_in_max_amount: Uint128::from(100u128), }, BurnTarget::SentFunds, @@ -912,12 +1017,12 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100u128, "alloyed").into()), + amount: Some(coin(100u128, "alloyed").into()), burn_from_address: MOCK_CONTRACT_ADDR.to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1u128, "denom1")] + amount: vec![coin(1u128, "denom1")] }) .set_data(to_json_binary(&SwapExactAmountOutResponseData { token_in_amount: Uint128::from(100u128) @@ -937,10 +1042,10 @@ mod tests { let mut deps = cosmwasm_std::testing::mock_dependencies_with_balances(&[( alloyed_holder.as_str(), - &[Coin::new(110000000000000u128, "alloyed")], + &[coin(110000000000000u128, "alloyed")], )]); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -960,6 +1065,7 @@ mod tests { Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), ], + asset_groups: BTreeMap::new(), }, ) .unwrap(); @@ -986,7 +1092,7 @@ mod tests { #[case( Entrypoint::Sudo, SwapFromAlloyedConstraint::ExactOut { - tokens_out: &[Coin::new(1000000000000u128, "denom1")], + tokens_out: &[coin(1000000000000u128, "denom1")], token_in_max_amount: Uint128::from(100000000000000u128), }, vec!["denom1"], @@ -996,12 +1102,12 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100000000000000u128, "alloyed").into()), + amount: Some(coin(100000000000000u128, "alloyed").into()), burn_from_address: MOCK_CONTRACT_ADDR.to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000000000000u128, "denom1")] + amount: vec![coin(1000000000000u128, "denom1")] }) .set_data(to_json_binary(&SwapExactAmountOutResponseData { token_in_amount: Uint128::from(100000000000000u128) @@ -1021,12 +1127,12 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100000000000000u128, "alloyed").into()), + amount: Some(coin(100000000000000u128, "alloyed").into()), burn_from_address: MOCK_CONTRACT_ADDR.to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000000000000u128, "denom1")] + amount: vec![coin(1000000000000u128, "denom1")] }) .set_data(to_json_binary(&SwapExactAmountInResponseData { token_out_amount: 1000000000000u128.into(), @@ -1046,18 +1152,18 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100000000000000u128, "alloyed").into()), + amount: Some(coin(100000000000000u128, "alloyed").into()), burn_from_address: "addr1".to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000000000000u128, "denom1")] + amount: vec![coin(1000000000000u128, "denom1")] })) )] #[case( Entrypoint::Exec, SwapFromAlloyedConstraint::ExactOut { - tokens_out: &[Coin::new(1000000000000u128, "denom1")], + tokens_out: &[coin(1000000000000u128, "denom1")], token_in_max_amount: Uint128::from(100000000000000u128), }, vec!["denom1"], @@ -1067,18 +1173,18 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(100000000000000u128, "alloyed").into()), + amount: Some(coin(100000000000000u128, "alloyed").into()), burn_from_address: "addr1".to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000000000000u128, "denom1")] + amount: vec![coin(1000000000000u128, "denom1")] })) )] #[case( Entrypoint::Sudo, SwapFromAlloyedConstraint::ExactOut { - tokens_out: &[Coin::new(1000000000000u128, "denom1"), Coin::new(1000000000000u128, "denom2")], + tokens_out: &[coin(1000000000000u128, "denom1"), coin(1000000000000u128, "denom2")], token_in_max_amount: Uint128::from(110000000000000u128), }, vec!["denom1", "denom2"], @@ -1088,12 +1194,12 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(110000000000000u128, "alloyed").into()), + amount: Some(coin(110000000000000u128, "alloyed").into()), burn_from_address: MOCK_CONTRACT_ADDR.to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000000000000u128, "denom1"), Coin::new(1000000000000u128, "denom2")] + amount: vec![coin(1000000000000u128, "denom1"), coin(1000000000000u128, "denom2")] }) .set_data(to_json_binary(&SwapExactAmountOutResponseData { token_in_amount: Uint128::from(110000000000000u128), @@ -1102,7 +1208,7 @@ mod tests { #[case( Entrypoint::Sudo, SwapFromAlloyedConstraint::ExactOut { - tokens_out: &[Coin::new(1000000000000u128, "denom1"), Coin::new(500000000000u128, "denom2")], + tokens_out: &[coin(1000000000000u128, "denom1"), coin(500000000000u128, "denom2")], token_in_max_amount: Uint128::from(105000000000000u128), }, vec!["denom1", "denom2"], @@ -1112,12 +1218,12 @@ mod tests { Ok(Response::new() .add_message(MsgBurn { sender: MOCK_CONTRACT_ADDR.to_string(), - amount: Some(Coin::new(105000000000000u128, "alloyed").into()), + amount: Some(coin(105000000000000u128, "alloyed").into()), burn_from_address: MOCK_CONTRACT_ADDR.to_string() }) .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000000000000u128, "denom1"), Coin::new(500000000000u128, "denom2")], + amount: vec![coin(1000000000000u128, "denom1"), coin(500000000000u128, "denom2")], }) .set_data(to_json_binary(&SwapExactAmountOutResponseData { token_in_amount: Uint128::from(105000000000000u128), @@ -1139,10 +1245,10 @@ mod tests { let mut deps = cosmwasm_std::testing::mock_dependencies_with_balances(&[( alloyed_holder.as_str(), - &[Coin::new(210000000000000u128, "alloyed")], + &[coin(210000000000000u128, "alloyed")], )]); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -1159,6 +1265,7 @@ mod tests { Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), // 1000000000000 * 10 Asset::new(Uint128::from(1000000000000u128), "denom3", 1u128).unwrap(), // 1000000000000 * 100 ], + asset_groups: BTreeMap::new(), }; let all_denoms = pool @@ -1166,16 +1273,11 @@ mod tests { .pool_assets .into_iter() .map(|asset| asset.denom().to_string()) - .collect::>(); + .collect::>(); - pool.mark_corrupted_assets( - corrupted_denoms - .iter() - .map(|denom| denom.to_string()) - .collect::>() - .as_slice(), - ) - .unwrap(); + for denom in corrupted_denoms { + pool.mark_corrupted_asset(denom).unwrap(); + } transmuter.pool.save(&mut deps.storage, &pool).unwrap(); @@ -1184,7 +1286,7 @@ mod tests { .limiters .register( &mut deps.storage, - denom.as_str(), + Scope::denom(denom.as_str()), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(100), @@ -1219,7 +1321,7 @@ mod tests { assert!( transmuter .limiters - .list_limiters_by_denom(&deps.storage, denom.as_str()) + .list_limiters_by_scope(&deps.storage, &Scope::denom(denom.as_str())) .unwrap() .is_empty(), "must not contain limiter for {} since it's corrupted and drained", @@ -1236,7 +1338,7 @@ mod tests { assert!( !transmuter .limiters - .list_limiters_by_denom(&deps.storage, denom.as_str()) + .list_limiters_by_scope(&deps.storage, &Scope::denom(denom.as_str())) .unwrap() .is_empty(), "must contain limiter for {} since it's not corrupted or not drained", @@ -1249,7 +1351,7 @@ mod tests { #[test] fn test_swap_non_alloyed_exact_amount_in_with_corrupted_assets() { let mut deps = mock_dependencies(); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -1266,6 +1368,7 @@ mod tests { Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), // 1000000000000 * 10 Asset::new(Uint128::from(1000000000000u128), "denom3", 1u128).unwrap(), // 1000000000000 * 100 ], + asset_groups: BTreeMap::new(), }; let all_denoms = pool @@ -1275,7 +1378,7 @@ mod tests { .map(|asset| asset.denom().to_string()) .collect::>(); - pool.mark_corrupted_assets(&["denom1".to_owned()]).unwrap(); + pool.mark_corrupted_asset("denom1").unwrap(); transmuter.pool.save(&mut deps.storage, &pool).unwrap(); @@ -1284,7 +1387,7 @@ mod tests { .limiters .register( &mut deps.storage, - denom.as_str(), + Scope::denom(denom.as_str()), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(100), @@ -1324,13 +1427,16 @@ mod tests { .unique() .collect_vec(); - assert_eq!(limiter_denoms, vec!["denom2", "denom3"]); + assert_eq!( + limiter_denoms, + vec![Scope::denom("denom2").key(), Scope::denom("denom3").key()] + ); } #[test] fn test_swap_non_alloyed_exact_amount_out_with_corrupted_assets() { let mut deps = mock_dependencies(); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -1347,6 +1453,7 @@ mod tests { Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), // 1000000000000 * 10 Asset::new(Uint128::from(1000000000000u128), "denom3", 1u128).unwrap(), // 1000000000000 * 100 ], + asset_groups: BTreeMap::new(), }; let all_denoms = pool @@ -1356,7 +1463,7 @@ mod tests { .map(|asset| asset.denom().to_string()) .collect::>(); - pool.mark_corrupted_assets(&["denom1".to_owned()]).unwrap(); + pool.mark_corrupted_asset("denom1").unwrap(); transmuter.pool.save(&mut deps.storage, &pool).unwrap(); @@ -1365,7 +1472,7 @@ mod tests { .limiters .register( &mut deps.storage, - denom.as_str(), + Scope::denom(denom.as_str()), "static", LimiterParams::StaticLimiter { upper_limit: Decimal::percent(100), @@ -1405,40 +1512,43 @@ mod tests { .unique() .collect_vec(); - assert_eq!(limiter_denoms, vec!["denom2", "denom3"]); + assert_eq!( + limiter_denoms, + vec![Scope::denom("denom2").key(), Scope::denom("denom3").key()] + ); } #[rstest] #[case( - Coin::new(100u128, "denom1"), + coin(100u128, "denom1"), "denom2", 1000u128, Addr::unchecked("addr1"), Ok(Response::new() .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000u128, "denom2")] + amount: vec![coin(1000u128, "denom2")] }) .set_data(to_json_binary(&SwapExactAmountInResponseData { token_out_amount: Uint128::from(1000u128) }).unwrap())) )] #[case( - Coin::new(100u128, "denom2"), + coin(100u128, "denom2"), "denom1", 10u128, Addr::unchecked("addr1"), Ok(Response::new() .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(10u128, "denom1")] + amount: vec![coin(10u128, "denom1")] }) .set_data(to_json_binary(&SwapExactAmountInResponseData { token_out_amount: Uint128::from(10u128) }).unwrap())) )] #[case( - Coin::new(100u128, "denom2"), + coin(100u128, "denom2"), "denom1", 100u128, Addr::unchecked("addr1"), @@ -1448,13 +1558,13 @@ mod tests { }) )] #[case( - Coin::new(100000000001u128, "denom1"), + coin(100000000001u128, "denom1"), "denom2", 1000000000010u128, Addr::unchecked("addr1"), Err(ContractError::InsufficientPoolAsset { - required: Coin::new(1000000000010u128, "denom2"), - available: Coin::new(1000000000000u128, "denom2"), + required: coin(1000000000010u128, "denom2"), + available: coin(1000000000000u128, "denom2"), }) )] fn test_swap_non_alloyed_exact_amount_in( @@ -1466,10 +1576,10 @@ mod tests { ) { let mut deps = cosmwasm_std::testing::mock_dependencies_with_balances(&[( sender.to_string().as_str(), - &[Coin::new(2000000000000u128, "alloyed")], + &[coin(2000000000000u128, "alloyed")], )]); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -1489,6 +1599,7 @@ mod tests { Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), ], + asset_groups: BTreeMap::new(), }, ) .unwrap(); @@ -1509,12 +1620,12 @@ mod tests { #[case( "denom1", 100u128, - Coin::new(1000u128, "denom2"), + coin(1000u128, "denom2"), Addr::unchecked("addr1"), Ok(Response::new() .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(1000u128, "denom2")] + amount: vec![coin(1000u128, "denom2")] }) .set_data(to_json_binary(&SwapExactAmountOutResponseData { token_in_amount: 100u128.into() @@ -1523,12 +1634,12 @@ mod tests { #[case( "denom2", 100u128, - Coin::new(10u128, "denom1"), + coin(10u128, "denom1"), Addr::unchecked("addr1"), Ok(Response::new() .add_message(BankMsg::Send { to_address: "addr1".to_string(), - amount: vec![Coin::new(10u128, "denom1")] + amount: vec![coin(10u128, "denom1")] }) .set_data(to_json_binary(&SwapExactAmountOutResponseData { token_in_amount: 100u128.into() @@ -1537,7 +1648,7 @@ mod tests { #[case( "denom2", 100u128, - Coin::new(100u128, "denom1"), + coin(100u128, "denom1"), Addr::unchecked("addr1"), Err(ContractError::ExcessiveRequiredTokenIn { limit: 100u128.into(), @@ -1547,11 +1658,11 @@ mod tests { #[case( "denom1", 100000000001u128, - Coin::new(1000000000010u128, "denom2"), + coin(1000000000010u128, "denom2"), Addr::unchecked("addr1"), Err(ContractError::InsufficientPoolAsset { - required: Coin::new(1000000000010u128, "denom2"), - available: Coin::new(1000000000000u128, "denom2"), + required: coin(1000000000010u128, "denom2"), + available: coin(1000000000000u128, "denom2"), }) )] fn test_swap_non_alloyed_exact_amount_out( @@ -1563,10 +1674,10 @@ mod tests { ) { let mut deps = cosmwasm_std::testing::mock_dependencies_with_balances(&[( sender.to_string().as_str(), - &[Coin::new(2000000000000u128, "alloyed")], + &[coin(2000000000000u128, "alloyed")], )]); - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); transmuter .alloyed_asset .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) @@ -1586,6 +1697,7 @@ mod tests { Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), ], + asset_groups: BTreeMap::new(), }, ) .unwrap(); @@ -1601,4 +1713,276 @@ mod tests { assert_eq!(res, expected_res); } + + #[rstest] + #[case::empty( + HashMap::from([]), + vec![], + vec![], + )] + #[case::no_asset_group( + HashMap::from([]), + vec![ + ("eth.axl", (Decimal::percent(20), Decimal::percent(40))), + ("eth.wh", (Decimal::percent(60), Decimal::percent(40))), + ("wsteth.axl", (Decimal::percent(20), Decimal::percent(20))), + ], + vec![], + )] + #[case( + HashMap::from([ + ("axelar", vec!["eth.axl", "wsteth.axl"]), + ("wormhole", vec!["eth.wh"]), + ]), + vec![ + ("eth.axl", (Decimal::percent(20), Decimal::percent(40))), + ("wsteth.axl", (Decimal::percent(20), Decimal::percent(20))), + ("eth.wh", (Decimal::percent(60), Decimal::percent(40))), + ], + vec![ + (Scope::asset_group("axelar"), (Decimal::percent(40), Decimal::percent(60))), + (Scope::asset_group("wormhole"), (Decimal::percent(60), Decimal::percent(40))), + ], + )] + fn test_construct_scope_value_pairs( + #[case] asset_groups: HashMap<&str, Vec<&str>>, + #[case] denom_weights: Vec<(&str, (Decimal, Decimal))>, + #[case] expected_asset_group_scopes: Vec<(Scope, (Decimal, Decimal))>, + ) { + let asset_groups = asset_groups + .into_iter() + .map(|(label, asset_group)| { + ( + label.to_string(), + AssetGroup::new( + asset_group + .into_iter() + .map(|asset| asset.to_string()) + .collect_vec(), + ), + ) + }) + .collect::>(); + + let prev_weights = denom_weights + .clone() + .into_iter() + .map(|(denom, (prev_weight, _))| (denom.to_string(), prev_weight)) + .collect(); + + let updated_weights = denom_weights + .clone() + .into_iter() + .map(|(denom, (_, updated_weight))| (denom.to_string(), updated_weight)) + .collect(); + + let mut scope_value_pairs = + construct_scope_value_pairs(prev_weights, updated_weights, asset_groups).unwrap(); + + let scope_denom_value_pairs = denom_weights + .into_iter() + .map(|(denom, weight_transition)| (Scope::denom(denom), weight_transition)) + .collect_vec(); + + let mut expected_scope_value_pairs = + vec![scope_denom_value_pairs, expected_asset_group_scopes].concat(); + + // assert by disregrard order + scope_value_pairs.sort_by_key(|(scope, _)| scope.key()); + expected_scope_value_pairs.sort_by_key(|(scope, _)| scope.key()); + + assert_eq!(scope_value_pairs, expected_scope_value_pairs); + } + + #[test] + fn test_clean_up_drained_corrupted_assets_group() { + let sender = Addr::unchecked("addr1"); + let mut deps = cosmwasm_std::testing::mock_dependencies_with_balances(&[( + sender.to_string().as_str(), + &[coin(2000000000000u128, "alloyed")], + )]); + + let transmuter = Transmuter::new(); + transmuter + .alloyed_asset + .set_alloyed_denom(&mut deps.storage, &"alloyed".to_string()) + .unwrap(); + + transmuter + .alloyed_asset + .set_normalization_factor(&mut deps.storage, 100u128.into()) + .unwrap(); + + let init_pool = TransmuterPool { + pool_assets: vec![ + Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), + Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), + Asset::new(Uint128::from(1000000000000u128), "denom3", 100u128).unwrap(), + ], + asset_groups: BTreeMap::from([( + "group1".to_string(), + AssetGroup::new(vec!["denom2".to_string(), "denom3".to_string()]) + .mark_as_corrupted() + .clone(), + )]), + }; + transmuter.pool.save(&mut deps.storage, &init_pool).unwrap(); + + // Register limiters for group1 + transmuter + .limiters + .register( + &mut deps.storage, + Scope::asset_group("group1"), + "1w", + LimiterParams::StaticLimiter { + upper_limit: Decimal::percent(60), + }, + ) + .unwrap(); + + let mut pool = transmuter.pool.load(&deps.storage).unwrap(); + let res = transmuter.clean_up_drained_corrupted_assets(&mut deps.storage, &mut pool); + assert_eq!(res, Ok(())); + + pool = transmuter.pool.load(&deps.storage).unwrap(); + assert_eq!(pool, init_pool); + + pool.exit_pool(&[coin(1000000000000u128, "denom2")]) + .unwrap(); + transmuter.pool.save(&mut deps.storage, &pool).unwrap(); + + let res = transmuter.clean_up_drained_corrupted_assets(&mut deps.storage, &mut pool); + assert_eq!(res, Ok(())); + + let expected_pool = TransmuterPool { + pool_assets: vec![ + Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), + Asset::new(Uint128::from(1000000000000u128), "denom3", 100u128).unwrap(), + ], + asset_groups: BTreeMap::from([( + "group1".to_string(), + AssetGroup::new(vec!["denom3".to_string()]) + .mark_as_corrupted() + .clone(), + )]), + }; + assert_eq!(pool, expected_pool); + + // Check that the limiter for group1 is still registered + let limiters = transmuter.limiters.list_limiters(&deps.storage).unwrap(); + assert_eq!(limiters.len(), 1); + + // Save the updated pool + transmuter.pool.save(&mut deps.storage, &pool).unwrap(); + + pool.exit_pool(&[coin(1000000000000u128, "denom3")]) + .unwrap(); + + let res = transmuter.clean_up_drained_corrupted_assets(&mut deps.storage, &mut pool); + assert_eq!(res, Ok(())); + + let expected_pool = TransmuterPool { + pool_assets: vec![ + Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), + ], + asset_groups: BTreeMap::new(), + }; + assert_eq!(pool, expected_pool); + + // Check that the limiter for group1 is removed + let limiters = transmuter.limiters.list_limiters(&deps.storage).unwrap(); + assert_eq!(limiters.len(), 0); + } + + #[test] + fn test_clean_up_drained_corrupted_assets_group_not_corrupted() { + let mut deps = mock_dependencies(); + let transmuter = Transmuter::new(); + + // Initialize the pool with non-corrupted assets and groups + let init_pool = TransmuterPool { + pool_assets: vec![ + Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), + Asset::new(Uint128::from(1000000000000u128), "denom2", 10u128).unwrap(), + Asset::new(Uint128::from(1000000000000u128), "denom3", 100u128).unwrap(), + ], + asset_groups: BTreeMap::from([( + "group1".to_string(), + AssetGroup::new(vec!["denom2".to_string(), "denom3".to_string()]), + )]), + }; + + transmuter.pool.save(&mut deps.storage, &init_pool).unwrap(); + + // Register a limiter for the group + transmuter + .limiters + .register( + &mut deps.storage, + Scope::asset_group("group1"), + "limiter1", + LimiterParams::StaticLimiter { + upper_limit: Decimal::one(), + }, + ) + .unwrap(); + + let mut pool = transmuter.pool.load(&deps.storage).unwrap(); + assert_eq!(pool, init_pool); + + // Drain denom2 from the pool + pool.exit_pool(&[coin(1000000000000u128, "denom2")]) + .unwrap(); + transmuter.pool.save(&mut deps.storage, &pool).unwrap(); + + let res = transmuter.clean_up_drained_corrupted_assets(&mut deps.storage, &mut pool); + assert_eq!(res, Ok(())); + + // Check that the pool remains unchanged + let expected_pool = TransmuterPool { + pool_assets: vec![ + Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), + Asset::new(Uint128::zero(), "denom2", 10u128).unwrap(), + Asset::new(Uint128::from(1000000000000u128), "denom3", 100u128).unwrap(), + ], + asset_groups: BTreeMap::from([( + "group1".to_string(), + AssetGroup::new(vec!["denom2".to_string(), "denom3".to_string()]), + )]), + }; + assert_eq!(pool, expected_pool); + + // Check that the limiter for group1 is still registered + let limiters = transmuter.limiters.list_limiters(&deps.storage).unwrap(); + assert_eq!(limiters.len(), 1); + + // Save the updated pool + transmuter.pool.save(&mut deps.storage, &pool).unwrap(); + + // Drain denom3 from the pool + pool.exit_pool(&[coin(1000000000000u128, "denom3")]) + .unwrap(); + + let res = transmuter.clean_up_drained_corrupted_assets(&mut deps.storage, &mut pool); + assert_eq!(res, Ok(())); + + // Check that the pool remains unchanged except for the drained assets + let expected_pool = TransmuterPool { + pool_assets: vec![ + Asset::new(Uint128::from(1000000000000u128), "denom1", 1u128).unwrap(), + Asset::new(Uint128::zero(), "denom2", 10u128).unwrap(), + Asset::new(Uint128::zero(), "denom3", 100u128).unwrap(), + ], + asset_groups: BTreeMap::from([( + "group1".to_string(), + AssetGroup::new(vec!["denom2".to_string(), "denom3".to_string()]), + )]), + }; + assert_eq!(pool, expected_pool); + + // Check that the limiter for group1 is still registered + let limiters = transmuter.limiters.list_limiters(&deps.storage).unwrap(); + assert_eq!(limiters.len(), 1); + } } diff --git a/contracts/transmuter/src/test/cases/scenarios.rs b/contracts/transmuter/src/test/cases/scenarios.rs index e487955..01593ca 100644 --- a/contracts/transmuter/src/test/cases/scenarios.rs +++ b/contracts/transmuter/src/test/cases/scenarios.rs @@ -2,20 +2,20 @@ use std::{str::FromStr, vec}; use crate::{ asset::AssetConfig, - contract::sv::QueryMsg, - contract::sv::{ExecMsg, InstantiateMsg}, contract::{ + sv::{ExecMsg, InstantiateMsg, QueryMsg}, GetShareDenomResponse, GetSharesResponse, GetTotalPoolLiquidityResponse, GetTotalSharesResponse, ListLimitersResponse, }, limiter::{ChangeLimiter, Limiter, LimiterParams, StaticLimiter, WindowConfig}, + scope::Scope, test::{ modules::cosmwasm_pool::CosmwasmPool, test_env::{assert_contract_err, TestEnvBuilder}, }, ContractError, }; -use cosmwasm_std::{Coin, Decimal, Uint128, Uint64}; +use cosmwasm_std::{coin, Decimal, Uint128, Uint64}; use osmosis_std::types::{ cosmos::bank::v1beta1::MsgSend, @@ -40,14 +40,14 @@ fn test_join_pool() { .with_account( "provider_1", vec![ - Coin::new(2_000, COSMOS_USDC), - Coin::new(2_000, AXL_USDC), - Coin::new(2_000, "urandom"), + coin(2_000, COSMOS_USDC), + coin(2_000, AXL_USDC), + coin(2_000, "urandom"), ], ) .with_account( "provider_2", - vec![Coin::new(2_000, COSMOS_USDC), Coin::new(2_000, AXL_USDC)], + vec![coin(2_000, COSMOS_USDC), coin(2_000, AXL_USDC)], ) .with_account("moderator", vec![]) .with_instantiate_msg(InstantiateMsg { @@ -71,7 +71,7 @@ fn test_join_pool() { assert_contract_err(ContractError::AtLeastSingleTokenExpected {}, err); // fail to join pool with denom that is not in the pool - let tokens_in = vec![Coin::new(1_000, "urandom")]; + let tokens_in = vec![coin(1_000, "urandom")]; let err = t .contract .execute(&ExecMsg::JoinPool {}, &tokens_in, &t.accounts["provider_1"]) @@ -86,7 +86,7 @@ fn test_join_pool() { ); // join pool with correct pool's denom should added to the contract's balance and update state - let tokens_in = vec![Coin::new(1_000, COSMOS_USDC)]; + let tokens_in = vec![coin(1_000, COSMOS_USDC)]; t.contract .execute(&ExecMsg::JoinPool {}, &tokens_in, &t.accounts["provider_1"]) @@ -105,7 +105,7 @@ fn test_join_pool() { assert_eq!( total_pool_liquidity, - vec![Coin::new(0, AXL_USDC), tokens_in[0].clone()] + vec![coin(0, AXL_USDC), tokens_in[0].clone()] ); // check shares @@ -125,13 +125,13 @@ fn test_join_pool() { assert_eq!(total_shares, tokens_in[0].amount); // join pool with multiple correct pool's denom should added to the contract's balance and update state - let tokens_in = vec![Coin::new(1_000, AXL_USDC), Coin::new(1_000, COSMOS_USDC)]; + let tokens_in = vec![coin(1_000, AXL_USDC), coin(1_000, COSMOS_USDC)]; t.contract .execute(&ExecMsg::JoinPool {}, &tokens_in, &t.accounts["provider_1"]) .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(1_000, AXL_USDC), Coin::new(2_000, COSMOS_USDC)]); + t.assert_contract_balances(&[coin(1_000, AXL_USDC), coin(2_000, COSMOS_USDC)]); // check pool balance let GetTotalPoolLiquidityResponse { @@ -143,7 +143,7 @@ fn test_join_pool() { assert_eq!( total_pool_liquidity, - vec![Coin::new(1_000, AXL_USDC), Coin::new(2_000, COSMOS_USDC)] + vec![coin(1_000, AXL_USDC), coin(2_000, COSMOS_USDC)] ); // check shares @@ -163,13 +163,13 @@ fn test_join_pool() { assert_eq!(total_shares, Uint128::new(3000)); // join pool with another provider with multiple correct pool's denom should added to the contract's balance and update state - let tokens_in = vec![Coin::new(2_000, AXL_USDC), Coin::new(2_000, COSMOS_USDC)]; + let tokens_in = vec![coin(2_000, AXL_USDC), coin(2_000, COSMOS_USDC)]; t.contract .execute(&ExecMsg::JoinPool {}, &tokens_in, &t.accounts["provider_2"]) .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(3_000, AXL_USDC), Coin::new(4_000, COSMOS_USDC)]); + t.assert_contract_balances(&[coin(3_000, AXL_USDC), coin(4_000, COSMOS_USDC)]); // check pool balance let GetTotalPoolLiquidityResponse { @@ -181,7 +181,7 @@ fn test_join_pool() { assert_eq!( total_pool_liquidity, - vec![Coin::new(3_000, AXL_USDC), Coin::new(4_000, COSMOS_USDC)] + vec![coin(3_000, AXL_USDC), coin(4_000, COSMOS_USDC)] ); // check shares @@ -211,13 +211,13 @@ fn test_swap() { .with_account( "alice", vec![ - Coin::new(1_500, AXL_USDC), - Coin::new(1_000, COSMOS_USDC), - Coin::new(1_000, "urandom2"), + coin(1_500, AXL_USDC), + coin(1_000, COSMOS_USDC), + coin(1_000, "urandom2"), ], ) - .with_account("bob", vec![Coin::new(29_902, AXL_USDC)]) - .with_account("provider", vec![Coin::new(200_000, COSMOS_USDC)]) + .with_account("bob", vec![coin(29_902, AXL_USDC)]) + .with_account("provider", vec![coin(200_000, COSMOS_USDC)]) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str(AXL_USDC), @@ -231,7 +231,7 @@ fn test_swap() { .build(&app); // join pool - let tokens_in = vec![Coin::new(100_000, COSMOS_USDC)]; + let tokens_in = vec![coin(100_000, COSMOS_USDC)]; // join pool with send tokens should not update pool balance bank.send( @@ -248,7 +248,7 @@ fn test_swap() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_500, AXL_USDC).into()), + token_in: Some(coin(1_500, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), @@ -261,8 +261,8 @@ fn test_swap() { assert_contract_err( ContractError::InsufficientPoolAsset { - required: Coin::new(1_500, COSMOS_USDC), - available: Coin::new(0, COSMOS_USDC), + required: coin(1_500, COSMOS_USDC), + available: coin(0, COSMOS_USDC), }, err, ); @@ -277,7 +277,7 @@ fn test_swap() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_000, COSMOS_USDC).into()), + token_in: Some(coin(1_000, COSMOS_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: "urandom".to_string(), @@ -300,7 +300,7 @@ fn test_swap() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_000, COSMOS_USDC).into()), + token_in: Some(coin(1_000, COSMOS_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: "urandom2".to_string(), @@ -333,6 +333,7 @@ fn test_swap() { pool_id: t.contract.pool_id, token_in: format!("1500{AXL_USDC}"), routes: routes.clone(), + sender: t.accounts["alice"].address(), }, ) .unwrap(); @@ -340,7 +341,7 @@ fn test_swap() { assert_eq!(token_out_amount, "1500"); // swap with correct token_in should succeed this time - let token_in = Coin::new(1_500, AXL_USDC); + let token_in = coin(1_500, AXL_USDC); cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), @@ -354,8 +355,8 @@ fn test_swap() { // check balances t.assert_contract_balances(&[ - Coin::new(1_500, AXL_USDC), - Coin::new(100_000 + 100_000 - 1_500, COSMOS_USDC), // +100_000 due to bank send + coin(1_500, AXL_USDC), + coin(100_000 + 100_000 - 1_500, COSMOS_USDC), // +100_000 due to bank send ]); let GetTotalPoolLiquidityResponse { @@ -367,25 +368,19 @@ fn test_swap() { assert_eq!( total_pool_liquidity, - vec![ - Coin::new(1_500, AXL_USDC), - Coin::new(100_000 - 1_500, COSMOS_USDC) - ] + vec![coin(1_500, AXL_USDC), coin(100_000 - 1_500, COSMOS_USDC)] ); // +1_000 due to existing alice balance t.assert_account_balances( "alice", - vec![ - Coin::new(1_500 + 1_000, COSMOS_USDC), - Coin::new(1_000, "urandom2"), - ], + vec![coin(1_500 + 1_000, COSMOS_USDC), coin(1_000, "urandom2")], vec!["uosmo"], ); // swap again with another user // swap with correct token_in should succeed this time - let token_in = Coin::new(29_902, AXL_USDC); + let token_in = coin(29_902, AXL_USDC); cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["bob"].address(), @@ -402,8 +397,8 @@ fn test_swap() { // check balances t.assert_contract_balances(&[ - Coin::new(1_500 + 29_902, AXL_USDC), - Coin::new(100_000 + 100_000 - 1_500 - 29_902, COSMOS_USDC), // +100_000 due to bank send + coin(1_500 + 29_902, AXL_USDC), + coin(100_000 + 100_000 - 1_500 - 29_902, COSMOS_USDC), // +100_000 due to bank send ]); let GetTotalPoolLiquidityResponse { @@ -416,15 +411,15 @@ fn test_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1_500 + 29_902, AXL_USDC), - Coin::new(100_000 - 1_500 - 29_902, COSMOS_USDC) + coin(1_500 + 29_902, AXL_USDC), + coin(100_000 - 1_500 - 29_902, COSMOS_USDC) ] ); - t.assert_account_balances("bob", vec![Coin::new(29_902, COSMOS_USDC)], vec!["uosmo"]); + t.assert_account_balances("bob", vec![coin(29_902, COSMOS_USDC)], vec!["uosmo"]); // swap back with `SwapExactAmountOut` - let token_out = Coin::new(1_500, AXL_USDC); + let token_out = coin(1_500, AXL_USDC); cp.swap_exact_amount_out( MsgSwapExactAmountOut { @@ -449,23 +444,20 @@ fn test_swap() { assert_eq!( total_pool_liquidity, - vec![ - Coin::new(29_902, AXL_USDC), - Coin::new(100_000 - 29_902, COSMOS_USDC), - ] + vec![coin(29_902, AXL_USDC), coin(100_000 - 29_902, COSMOS_USDC),] ); // check balances t.assert_contract_balances(&[ - Coin::new(29_902, AXL_USDC), - Coin::new(100_000 + 100_000 - 29_902, COSMOS_USDC), + coin(29_902, AXL_USDC), + coin(100_000 + 100_000 - 29_902, COSMOS_USDC), ]); t.assert_account_balances( "bob", vec![ - Coin::new(1_500, AXL_USDC), - Coin::new(29_902 - 1_500, COSMOS_USDC), // +100_000 due to bank send + coin(1_500, AXL_USDC), + coin(29_902 - 1_500, COSMOS_USDC), // +100_000 due to bank send ], vec!["uosmo"], ); @@ -477,9 +469,9 @@ fn test_exit_pool() { let cp = CosmwasmPool::new(&app); let t = TestEnvBuilder::new() - .with_account("user", vec![Coin::new(1_500, AXL_USDC)]) - .with_account("provider_1", vec![Coin::new(100_000, COSMOS_USDC)]) - .with_account("provider_2", vec![Coin::new(100_000, COSMOS_USDC)]) + .with_account("user", vec![coin(1_500, AXL_USDC)]) + .with_account("provider_1", vec![coin(100_000, COSMOS_USDC)]) + .with_account("provider_2", vec![coin(100_000, COSMOS_USDC)]) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str(AXL_USDC), @@ -496,7 +488,7 @@ fn test_exit_pool() { t.contract .execute( &ExecMsg::JoinPool {}, - &[Coin::new(100_000, COSMOS_USDC)], + &[coin(100_000, COSMOS_USDC)], &t.accounts["provider_1"], ) .unwrap(); @@ -504,13 +496,13 @@ fn test_exit_pool() { t.contract .execute( &ExecMsg::JoinPool {}, - &[Coin::new(100_000, COSMOS_USDC)], + &[coin(100_000, COSMOS_USDC)], &t.accounts["provider_2"], ) .unwrap(); // swap to build up token_in - let token_in = Coin::new(1_500, AXL_USDC); + let token_in = coin(1_500, AXL_USDC); cp.swap_exact_amount_in( MsgSwapExactAmountIn { @@ -531,7 +523,7 @@ fn test_exit_pool() { .contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_500, AXL_USDC)], + tokens_out: vec![coin(1_500, AXL_USDC)], }, &[], &t.accounts["user"], @@ -550,7 +542,7 @@ fn test_exit_pool() { t.contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(500, AXL_USDC)], + tokens_out: vec![coin(500, AXL_USDC)], }, &[], &t.accounts["provider_1"], @@ -575,8 +567,8 @@ fn test_exit_pool() { // check balances t.assert_contract_balances(&[ - Coin::new(1500 - 500, AXL_USDC), - Coin::new(200_000 - 1500, COSMOS_USDC), + coin(1500 - 500, AXL_USDC), + coin(200_000 - 1500, COSMOS_USDC), ]); let GetTotalPoolLiquidityResponse { @@ -589,8 +581,8 @@ fn test_exit_pool() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1500 - 500, AXL_USDC), - Coin::new(200_000 - 1500, COSMOS_USDC) + coin(1500 - 500, AXL_USDC), + coin(200_000 - 1500, COSMOS_USDC) ] ); @@ -598,7 +590,7 @@ fn test_exit_pool() { t.contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_000, AXL_USDC), Coin::new(99_000, COSMOS_USDC)], + tokens_out: vec![coin(1_000, AXL_USDC), coin(99_000, COSMOS_USDC)], }, &[], &t.accounts["provider_2"], @@ -622,7 +614,7 @@ fn test_exit_pool() { assert_eq!(total_shares, Uint128::new(200_000 - 500 - 1000 - 99_000)); // check balances - t.assert_contract_balances(&[Coin::new(200_000 - 1500 - 99_000, COSMOS_USDC)]); + t.assert_contract_balances(&[coin(200_000 - 1500 - 99_000, COSMOS_USDC)]); let GetTotalPoolLiquidityResponse { total_pool_liquidity, @@ -634,8 +626,8 @@ fn test_exit_pool() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, AXL_USDC), - Coin::new(200_000 - 1500 - 99_000, COSMOS_USDC) + coin(0, AXL_USDC), + coin(200_000 - 1500 - 99_000, COSMOS_USDC) ] ); @@ -644,7 +636,7 @@ fn test_exit_pool() { .contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1, AXL_USDC)], + tokens_out: vec![coin(1, AXL_USDC)], }, &[], &t.accounts["provider_2"], @@ -664,7 +656,7 @@ fn test_exit_pool() { .contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1, AXL_USDC)], + tokens_out: vec![coin(1, AXL_USDC)], }, &[], &t.accounts["provider_1"], @@ -673,8 +665,8 @@ fn test_exit_pool() { assert_contract_err( ContractError::InsufficientPoolAsset { - required: Coin::new(1, AXL_USDC), - available: Coin::new(0, AXL_USDC), + required: coin(1, AXL_USDC), + available: coin(0, AXL_USDC), }, err, ); @@ -686,9 +678,9 @@ fn test_3_pool_swap() { let cp = CosmwasmPool::new(&app); let t = TestEnvBuilder::new() - .with_account("alice", vec![Coin::new(1_500, AXL_USDC)]) - .with_account("bob", vec![Coin::new(1_500, AXL_DAI)]) - .with_account("provider", vec![Coin::new(100_000, COSMOS_USDC)]) + .with_account("alice", vec![coin(1_500, AXL_USDC)]) + .with_account("bob", vec![coin(1_500, AXL_DAI)]) + .with_account("provider", vec![coin(100_000, COSMOS_USDC)]) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str(AXL_USDC), @@ -710,7 +702,7 @@ fn test_3_pool_swap() { t.contract .execute( &ExecMsg::JoinPool {}, - &[Coin::new(100_000, COSMOS_USDC)], + &[coin(100_000, COSMOS_USDC)], &t.accounts["provider"], ) .unwrap(); @@ -726,7 +718,7 @@ fn test_3_pool_swap() { assert_eq!(shares, Uint128::new(100_000)); // check contract balances - t.assert_contract_balances(&[Coin::new(100_000, COSMOS_USDC)]); + t.assert_contract_balances(&[coin(100_000, COSMOS_USDC)]); // check provider balances t.assert_account_balances("provider", vec![], vec!["uosmo", &share_denom]); @@ -742,9 +734,9 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, AXL_USDC), - Coin::new(0, AXL_DAI), - Coin::new(100_000, COSMOS_USDC) + coin(0, AXL_USDC), + coin(0, AXL_DAI), + coin(100_000, COSMOS_USDC) ] ); @@ -754,7 +746,7 @@ fn test_3_pool_swap() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_000, AXL_USDC).into()), + token_in: Some(coin(1_000, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: AXL_DAI.to_string(), @@ -767,8 +759,8 @@ fn test_3_pool_swap() { assert_contract_err( ContractError::InsufficientPoolAsset { - required: Coin::new(1_000, AXL_DAI), - available: Coin::new(0, AXL_DAI), + required: coin(1_000, AXL_DAI), + available: coin(0, AXL_DAI), }, err, ); @@ -777,7 +769,7 @@ fn test_3_pool_swap() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_000, AXL_USDC).into()), + token_in: Some(coin(1_000, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), @@ -789,12 +781,12 @@ fn test_3_pool_swap() { .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(1_000, AXL_USDC), Coin::new(99_000, COSMOS_USDC)]); + t.assert_contract_balances(&[coin(1_000, AXL_USDC), coin(99_000, COSMOS_USDC)]); // check alice balance t.assert_account_balances( "alice", - vec![Coin::new(500, AXL_USDC), Coin::new(1_000, COSMOS_USDC)], + vec![coin(500, AXL_USDC), coin(1_000, COSMOS_USDC)], vec!["uosmo", "ucosm"], ); @@ -809,9 +801,9 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1_000, AXL_USDC), - Coin::new(0, AXL_DAI), - Coin::new(99_000, COSMOS_USDC) + coin(1_000, AXL_USDC), + coin(0, AXL_DAI), + coin(99_000, COSMOS_USDC) ] ); @@ -820,7 +812,7 @@ fn test_3_pool_swap() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["bob"].address(), - token_in: Some(Coin::new(1_000, AXL_DAI).into()), + token_in: Some(coin(1_000, AXL_DAI).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: AXL_USDC.to_string(), @@ -832,12 +824,12 @@ fn test_3_pool_swap() { .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(1_000, AXL_DAI), Coin::new(99_000, COSMOS_USDC)]); + t.assert_contract_balances(&[coin(1_000, AXL_DAI), coin(99_000, COSMOS_USDC)]); // check bob balances t.assert_account_balances( "bob", - vec![Coin::new(500, AXL_DAI), Coin::new(1_000, AXL_USDC)], + vec![coin(500, AXL_DAI), coin(1_000, AXL_USDC)], vec!["uosmo"], ); @@ -852,9 +844,9 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, AXL_USDC), - Coin::new(1_000, AXL_DAI), - Coin::new(99_000, COSMOS_USDC) + coin(0, AXL_USDC), + coin(1_000, AXL_DAI), + coin(99_000, COSMOS_USDC) ] ); @@ -862,7 +854,7 @@ fn test_3_pool_swap() { t.contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_000, AXL_DAI), Coin::new(99_000, COSMOS_USDC)], + tokens_out: vec![coin(1_000, AXL_DAI), coin(99_000, COSMOS_USDC)], }, &[], &t.accounts["provider"], @@ -882,17 +874,13 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, - vec![ - Coin::new(0, AXL_USDC), - Coin::new(0, AXL_DAI), - Coin::new(0, COSMOS_USDC) - ] + vec![coin(0, AXL_USDC), coin(0, AXL_DAI), coin(0, COSMOS_USDC)] ); t.assert_contract_balances(&[]); t.assert_account_balances( "provider", - vec![Coin::new(1_000, AXL_DAI), Coin::new(99_000, COSMOS_USDC)], + vec![coin(1_000, AXL_DAI), coin(99_000, COSMOS_USDC)], vec!["uosmo"], ); } @@ -903,11 +891,11 @@ fn test_swap_alloyed_asset() { let alloyed_asset_subdenom = "eth"; let t = TestEnvBuilder::new() - .with_account("alice", vec![Coin::new(1_500, AXL_ETH)]) - .with_account("bob", vec![Coin::new(1_500, WH_ETH)]) + .with_account("alice", vec![coin(1_500, AXL_ETH)]) + .with_account("bob", vec![coin(1_500, WH_ETH)]) .with_account( "provider", - vec![Coin::new(100_000, AXL_ETH), Coin::new(100_000, WH_ETH)], + vec![coin(100_000, AXL_ETH), coin(100_000, WH_ETH)], ) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ @@ -939,32 +927,21 @@ fn test_limiters() { let app = OsmosisTestApp::new(); let cp = CosmwasmPool::new(&app); - let admin = app - .init_account(&[Coin::new(100_000u128, "uosmo")]) - .unwrap(); + let admin = app.init_account(&[coin(100_000u128, "uosmo")]).unwrap(); let t = TestEnvBuilder::new() .with_account( "alice", - vec![ - Coin::new(1_000_000, AXL_USDC), - Coin::new(1_000_000, COSMOS_USDC), - ], + vec![coin(1_000_000, AXL_USDC), coin(1_000_000, COSMOS_USDC)], ) .with_account( "bob", - vec![ - Coin::new(1_000_000, AXL_USDC), - Coin::new(1_000_000, COSMOS_USDC), - ], + vec![coin(1_000_000, AXL_USDC), coin(1_000_000, COSMOS_USDC)], ) .with_account("admin", vec![]) .with_account( "provider", - vec![ - Coin::new(1_000_000, AXL_USDC), - Coin::new(1_000_000, COSMOS_USDC), - ], + vec![coin(1_000_000, AXL_USDC), coin(1_000_000, COSMOS_USDC)], ) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ @@ -992,7 +969,7 @@ fn test_limiters() { t.contract .execute( &ExecMsg::RegisterLimiter { - denom: AXL_USDC.to_string(), + scope: Scope::Denom(AXL_USDC.to_string()), label: "1h".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: config_1h.clone(), @@ -1007,7 +984,7 @@ fn test_limiters() { t.contract .execute( &ExecMsg::RegisterLimiter { - denom: AXL_USDC.to_string(), + scope: Scope::Denom(AXL_USDC.to_string()), label: "1w".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: config_1w.clone(), @@ -1022,7 +999,7 @@ fn test_limiters() { t.contract .execute( &ExecMsg::RegisterLimiter { - denom: COSMOS_USDC.to_string(), + scope: Scope::Denom(COSMOS_USDC.to_string()), label: "1h".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: config_1h.clone(), @@ -1037,7 +1014,7 @@ fn test_limiters() { t.contract .execute( &ExecMsg::RegisterLimiter { - denom: COSMOS_USDC.to_string(), + scope: Scope::Denom(COSMOS_USDC.to_string()), label: "1w".to_string(), limiter_params: LimiterParams::ChangeLimiter { window_config: config_1w.clone(), @@ -1052,7 +1029,7 @@ fn test_limiters() { t.contract .execute( &ExecMsg::RegisterLimiter { - denom: COSMOS_USDC.to_string(), + scope: Scope::Denom(COSMOS_USDC.to_string()), label: "static".to_string(), limiter_params: LimiterParams::StaticLimiter { upper_limit: Decimal::percent(55), @@ -1070,29 +1047,29 @@ fn test_limiters() { limiters, vec![ ( - (AXL_USDC.to_string(), "1h".to_string()), + (Scope::denom(AXL_USDC).key(), "1h".to_string()), Limiter::ChangeLimiter( ChangeLimiter::new(config_1h.clone(), Decimal::percent(10)).unwrap() ) ), ( - (AXL_USDC.to_string(), "1w".to_string()), + (Scope::denom(AXL_USDC).key(), "1w".to_string()), Limiter::ChangeLimiter( ChangeLimiter::new(config_1w.clone(), Decimal::percent(5)).unwrap() ) ), ( - (COSMOS_USDC.to_string(), "1h".to_string()), + (Scope::denom(COSMOS_USDC).key(), "1h".to_string()), Limiter::ChangeLimiter( ChangeLimiter::new(config_1h, Decimal::percent(10)).unwrap() ) ), ( - (COSMOS_USDC.to_string(), "1w".to_string()), + (Scope::denom(COSMOS_USDC).key(), "1w".to_string()), Limiter::ChangeLimiter(ChangeLimiter::new(config_1w, Decimal::percent(5)).unwrap()) ), ( - (COSMOS_USDC.to_string(), "static".to_string()), + (Scope::denom(COSMOS_USDC).key(), "static".to_string()), Limiter::StaticLimiter(StaticLimiter::new(Decimal::percent(55)).unwrap()) ), ] @@ -1102,10 +1079,7 @@ fn test_limiters() { t.contract .execute( &ExecMsg::JoinPool {}, - &[ - Coin::new(500_000, AXL_USDC), - Coin::new(500_000, COSMOS_USDC), - ], + &[coin(500_000, AXL_USDC), coin(500_000, COSMOS_USDC)], &t.accounts["provider"], ) .unwrap(); @@ -1117,7 +1091,7 @@ fn test_limiters() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(100_001, AXL_USDC).into()), + token_in: Some(coin(100_001, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), @@ -1130,7 +1104,7 @@ fn test_limiters() { assert_contract_err( ContractError::UpperLimitExceeded { - denom: AXL_USDC.to_string(), + scope: Scope::denom(AXL_USDC), upper_limit: Decimal::from_str("0.6").unwrap(), value: Decimal::from_str("0.600001").unwrap(), }, @@ -1142,7 +1116,7 @@ fn test_limiters() { .swap_exact_amount_out( MsgSwapExactAmountOut { sender: t.accounts["alice"].address(), - token_out: Some(Coin::new(50_001, AXL_USDC).into()), + token_out: Some(coin(50_001, AXL_USDC).into()), routes: vec![SwapAmountOutRoute { pool_id: t.contract.pool_id, token_in_denom: COSMOS_USDC.to_string(), @@ -1155,7 +1129,7 @@ fn test_limiters() { assert_contract_err( ContractError::UpperLimitExceeded { - denom: COSMOS_USDC.to_string(), + scope: Scope::denom(COSMOS_USDC), upper_limit: Decimal::from_str("0.55").unwrap(), value: Decimal::from_str("0.550001").unwrap(), }, @@ -1166,7 +1140,7 @@ fn test_limiters() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(50_000, COSMOS_USDC).into()), + token_in: Some(coin(50_000, COSMOS_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: AXL_USDC.to_string(), @@ -1184,7 +1158,7 @@ fn test_limiters() { .contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(200_000, COSMOS_USDC)], + tokens_out: vec![coin(200_000, COSMOS_USDC)], }, &[], &t.accounts["provider"], @@ -1193,7 +1167,7 @@ fn test_limiters() { assert_contract_err( ContractError::UpperLimitExceeded { - denom: AXL_USDC.to_string(), + scope: Scope::denom(AXL_USDC), upper_limit: Decimal::from_str("0.55").unwrap(), value: Decimal::from_str("0.5625").unwrap(), }, @@ -1205,15 +1179,15 @@ fn test_limiters() { .contract .execute( &ExecMsg::JoinPool {}, - &[Coin::new(200_000, AXL_USDC)], + &[coin(200_000, AXL_USDC)], &t.accounts["provider"], ) .unwrap_err(); assert_contract_err( ContractError::UpperLimitExceeded { - denom: AXL_USDC.to_string(), - upper_limit: Decimal::from_str("0.525034626038781163").unwrap(), + scope: Scope::denom(AXL_USDC), + upper_limit: Decimal::from_str("0.525017349063150589").unwrap(), value: Decimal::from_str("0.5416666666666666").unwrap(), }, err, @@ -1230,7 +1204,7 @@ fn test_limiters() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(200_000, alloyed_denom.clone()).into()), + token_in: Some(coin(200_000, alloyed_denom.clone()).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: AXL_USDC.to_string(), @@ -1243,7 +1217,7 @@ fn test_limiters() { assert_contract_err( ContractError::UpperLimitExceeded { - denom: COSMOS_USDC.to_string(), + scope: Scope::denom(COSMOS_USDC), upper_limit: Decimal::from_str("0.65").unwrap(), value: Decimal::from_str("0.6875").unwrap(), }, @@ -1255,7 +1229,7 @@ fn test_limiters() { .swap_exact_amount_out( MsgSwapExactAmountOut { sender: t.accounts["provider"].address(), - token_out: Some(Coin::new(200_000, alloyed_denom).into()), + token_out: Some(coin(200_000, alloyed_denom).into()), routes: vec![SwapAmountOutRoute { pool_id: t.contract.pool_id, token_in_denom: COSMOS_USDC.to_string(), @@ -1268,8 +1242,8 @@ fn test_limiters() { assert_contract_err( ContractError::UpperLimitExceeded { - denom: COSMOS_USDC.to_string(), - upper_limit: Decimal::from_str("0.575").unwrap(), + scope: Scope::denom(COSMOS_USDC), + upper_limit: Decimal::from_str("0.57498265093684941").unwrap(), value: Decimal::from_str("0.625").unwrap(), }, err, @@ -1281,32 +1255,21 @@ fn test_register_limiter_after_having_liquidity() { let app = OsmosisTestApp::new(); let cp = CosmwasmPool::new(&app); - let admin = app - .init_account(&[Coin::new(100_000u128, "uosmo")]) - .unwrap(); + let admin = app.init_account(&[coin(100_000u128, "uosmo")]).unwrap(); let t = TestEnvBuilder::new() .with_account( "alice", - vec![ - Coin::new(1_000_000, AXL_USDC), - Coin::new(1_000_000, COSMOS_USDC), - ], + vec![coin(1_000_000, AXL_USDC), coin(1_000_000, COSMOS_USDC)], ) .with_account( "bob", - vec![ - Coin::new(1_000_000, AXL_USDC), - Coin::new(1_000_000, COSMOS_USDC), - ], + vec![coin(1_000_000, AXL_USDC), coin(1_000_000, COSMOS_USDC)], ) .with_account("admin", vec![]) .with_account( "provider", - vec![ - Coin::new(1_000_000, AXL_USDC), - Coin::new(1_000_000, COSMOS_USDC), - ], + vec![coin(1_000_000, AXL_USDC), coin(1_000_000, COSMOS_USDC)], ) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ @@ -1329,7 +1292,7 @@ fn test_register_limiter_after_having_liquidity() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(200_000, COSMOS_USDC).into()), + token_in: Some(coin(200_000, COSMOS_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: alloyed_denom.clone(), @@ -1343,7 +1306,7 @@ fn test_register_limiter_after_having_liquidity() { t.contract .execute( &ExecMsg::RegisterLimiter { - denom: COSMOS_USDC.to_string(), + scope: Scope::Denom(COSMOS_USDC.to_string()), label: "static".to_string(), limiter_params: LimiterParams::StaticLimiter { upper_limit: Decimal::percent(60), @@ -1358,7 +1321,7 @@ fn test_register_limiter_after_having_liquidity() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(1, COSMOS_USDC).into()), + token_in: Some(coin(1, COSMOS_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: alloyed_denom.clone(), @@ -1371,7 +1334,7 @@ fn test_register_limiter_after_having_liquidity() { assert_contract_err( ContractError::UpperLimitExceeded { - denom: COSMOS_USDC.to_string(), + scope: Scope::denom(COSMOS_USDC), upper_limit: Decimal::from_str("0.6").unwrap(), value: Decimal::from_str("1").unwrap(), }, @@ -1382,7 +1345,7 @@ fn test_register_limiter_after_having_liquidity() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(1, AXL_USDC).into()), + token_in: Some(coin(1, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: alloyed_denom.clone(), @@ -1397,7 +1360,7 @@ fn test_register_limiter_after_having_liquidity() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(1, &alloyed_denom).into()), + token_in: Some(coin(1, &alloyed_denom).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), @@ -1412,7 +1375,7 @@ fn test_register_limiter_after_having_liquidity() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(1, AXL_USDC).into()), + token_in: Some(coin(1, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), @@ -1428,7 +1391,7 @@ fn test_register_limiter_after_having_liquidity() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(1, COSMOS_USDC).into()), + token_in: Some(coin(1, COSMOS_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: AXL_USDC.to_string(), @@ -1441,7 +1404,7 @@ fn test_register_limiter_after_having_liquidity() { assert_contract_err( ContractError::UpperLimitExceeded { - denom: COSMOS_USDC.to_string(), + scope: Scope::denom(COSMOS_USDC), upper_limit: Decimal::from_str("0.6").unwrap(), value: Decimal::from_str("0.999995").unwrap(), }, @@ -1452,7 +1415,7 @@ fn test_register_limiter_after_having_liquidity() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["provider"].address(), - token_in: Some(Coin::new(1, alloyed_denom).into()), + token_in: Some(coin(1, alloyed_denom).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), diff --git a/contracts/transmuter/src/test/cases/units/add_new_assets.rs b/contracts/transmuter/src/test/cases/units/add_new_assets.rs index 4d46f4e..53bef42 100644 --- a/contracts/transmuter/src/test/cases/units/add_new_assets.rs +++ b/contracts/transmuter/src/test/cases/units/add_new_assets.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{coin, Uint128}; use osmosis_test_tube::OsmosisTestApp; use crate::{ @@ -15,10 +15,10 @@ fn test_add_new_assets() { // create denom app.init_account(&[ - Coin::new(1, "denom1"), - Coin::new(1, "denom2"), - Coin::new(1, "denom3"), - Coin::new(1, "denom4"), + coin(1, "denom1"), + coin(1, "denom2"), + coin(1, "denom3"), + coin(1, "denom4"), ]) .unwrap(); @@ -81,10 +81,10 @@ fn test_add_new_assets() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, "denom1"), - Coin::new(0, "denom2"), - Coin::new(0, "denom3"), - Coin::new(0, "denom4"), + coin(0, "denom1"), + coin(0, "denom2"), + coin(0, "denom3"), + coin(0, "denom4"), ] ); diff --git a/contracts/transmuter/src/test/cases/units/admin.rs b/contracts/transmuter/src/test/cases/units/admin.rs index 0cf66c4..26bd20c 100644 --- a/contracts/transmuter/src/test/cases/units/admin.rs +++ b/contracts/transmuter/src/test/cases/units/admin.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{coin, Uint128}; use osmosis_std::types::cosmos::bank::v1beta1::{ DenomUnit, Metadata, QueryDenomMetadataRequest, QueryDenomMetadataResponse, @@ -21,12 +21,9 @@ fn test_admin_set_denom_metadata() { let alloyed_asset_subdenom = "eth"; let t = TestEnvBuilder::new() - .with_account("alice", vec![Coin::new(1_500, AXL_ETH)]) - .with_account("bob", vec![Coin::new(1_500, WH_ETH)]) - .with_account( - "admin", - vec![Coin::new(100_000, AXL_ETH), Coin::new(100_000, WH_ETH)], - ) + .with_account("alice", vec![coin(1_500, AXL_ETH)]) + .with_account("bob", vec![coin(1_500, WH_ETH)]) + .with_account("admin", vec![coin(100_000, AXL_ETH), coin(100_000, WH_ETH)]) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str(AXL_ETH), diff --git a/contracts/transmuter/src/test/cases/units/create_pool.rs b/contracts/transmuter/src/test/cases/units/create_pool.rs index 49786a8..3dfe82e 100644 --- a/contracts/transmuter/src/test/cases/units/create_pool.rs +++ b/contracts/transmuter/src/test/cases/units/create_pool.rs @@ -7,7 +7,7 @@ use crate::{ IsActiveResponse, }, }; -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{coin, Uint128}; use osmosis_test_tube::OsmosisTestApp; use crate::test::test_env::TestEnvBuilder; @@ -17,7 +17,7 @@ fn test_create_pool() { let app = OsmosisTestApp::new(); // create denom - app.init_account(&[Coin::new(1, "denom1"), Coin::new(1, "denom2")]) + app.init_account(&[coin(1, "denom1"), coin(1, "denom2")]) .unwrap(); let t = TestEnvBuilder::new() @@ -52,10 +52,7 @@ fn test_create_pool() { assert_eq!( total_pool_liquidity, - vec![ - Coin::new(0, "denom1".to_string()), - Coin::new(0, "denom2".to_string()) - ] + vec![coin(0, "denom1".to_string()), coin(0, "denom2".to_string())] ); // get total shares diff --git a/contracts/transmuter/src/test/cases/units/join_and_exit.rs b/contracts/transmuter/src/test/cases/units/join_and_exit.rs index 214163a..ba5224a 100644 --- a/contracts/transmuter/src/test/cases/units/join_and_exit.rs +++ b/contracts/transmuter/src/test/cases/units/join_and_exit.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use cosmwasm_std::{attr, Coin, Uint128}; +use cosmwasm_std::{attr, coin, Coin, Uint128}; use itertools::Itertools; use osmosis_test_tube::{Account, OsmosisTestApp}; @@ -24,40 +24,34 @@ fn test_join_pool_with_single_lp_should_update_shares_and_liquidity_properly() { let cases = vec![ Case { - funds: vec![Coin::new(1, "denoma")], + funds: vec![coin(1, "denoma")], }, Case { - funds: vec![Coin::new(1, "denomb")], + funds: vec![coin(1, "denomb")], }, Case { - funds: vec![Coin::new(100, "denoma")], + funds: vec![coin(100, "denoma")], }, Case { - funds: vec![Coin::new(100, "denomb")], + funds: vec![coin(100, "denomb")], }, Case { - funds: vec![Coin::new(100_000_000_000_000, "denoma")], + funds: vec![coin(100_000_000_000_000, "denoma")], }, Case { - funds: vec![Coin::new(100_000_000_000_000, "denomb")], + funds: vec![coin(100_000_000_000_000, "denomb")], }, Case { - funds: vec![Coin::new(u128::MAX, "denoma")], + funds: vec![coin(u128::MAX, "denoma")], }, Case { - funds: vec![Coin::new(u128::MAX, "denomb")], + funds: vec![coin(u128::MAX, "denomb")], }, Case { - funds: vec![ - Coin::new(999_999_999, "denoma"), - Coin::new(999_999_999, "denomb"), - ], + funds: vec![coin(999_999_999, "denoma"), coin(999_999_999, "denomb")], }, Case { - funds: vec![ - Coin::new(12_000_000_000, "denoma"), - Coin::new(999_999_999, "denomb"), - ], + funds: vec![coin(12_000_000_000, "denoma"), coin(999_999_999, "denomb")], }, ]; @@ -84,7 +78,7 @@ fn test_join_pool_with_single_lp_should_update_shares_and_liquidity_properly() { // make supply non-zero for denom in missing_denoms { - app.init_account(&[Coin::new(1, denom)]).unwrap(); + app.init_account(&[coin(1, denom)]).unwrap(); } let t = TestEnvBuilder::new() @@ -161,41 +155,32 @@ fn test_join_pool_should_update_shares_and_liquidity_properly() { let cases = vec![ Case { joins: vec![ - ("addr1", vec![Coin::new(1, "denoma")]), - ("addr2", vec![Coin::new(1, "denomb")]), + ("addr1", vec![coin(1, "denoma")]), + ("addr2", vec![coin(1, "denomb")]), ], }, Case { - joins: vec![("addr1", vec![Coin::new(u128::MAX, "denoma")])], + joins: vec![("addr1", vec![coin(u128::MAX, "denoma")])], }, Case { - joins: vec![("addr1", vec![Coin::new(u128::MAX, "denomb")])], + joins: vec![("addr1", vec![coin(u128::MAX, "denomb")])], }, Case { joins: vec![ - ("addr1", vec![Coin::new(100_000, "denoma")]), - ("addr2", vec![Coin::new(999_999_999, "denomb")]), - ("addr3", vec![Coin::new(1, "denoma")]), - ("addr4", vec![Coin::new(2, "denomb")]), + ("addr1", vec![coin(100_000, "denoma")]), + ("addr2", vec![coin(999_999_999, "denomb")]), + ("addr3", vec![coin(1, "denoma")]), + ("addr4", vec![coin(2, "denomb")]), ], }, Case { joins: vec![ - ( - "addr1", - vec![Coin::new(100_000, "denoma"), Coin::new(999, "denomb")], - ), + ("addr1", vec![coin(100_000, "denoma"), coin(999, "denomb")]), ( "addr2", - vec![ - Coin::new(999_999_999, "denoma"), - Coin::new(999_999_999, "denomb"), - ], - ), - ( - "addr3", - vec![Coin::new(1, "denoma"), Coin::new(1, "denomb")], + vec![coin(999_999_999, "denoma"), coin(999_999_999, "denomb")], ), + ("addr3", vec![coin(1, "denoma"), coin(1, "denomb")]), ], }, ]; @@ -224,7 +209,7 @@ fn test_join_pool_should_update_shares_and_liquidity_properly() { // make supply non-zero for denom in missing_denoms { - app.init_account(&[Coin::new(1, denom)]).unwrap(); + app.init_account(&[coin(1, denom)]).unwrap(); } for (acc, funds) in case.joins.clone() { @@ -318,28 +303,28 @@ fn test_exit_pool_less_than_their_shares_should_update_shares_and_liquidity_prop let cases = vec![ Case { - join: vec![Coin::new(1, "denoma")], - exit: vec![Coin::new(1, "denoma")], + join: vec![coin(1, "denoma")], + exit: vec![coin(1, "denoma")], }, Case { - join: vec![Coin::new(100_000, "denoma"), Coin::new(1, "denomb")], - exit: vec![Coin::new(100_000, "denoma")], + join: vec![coin(100_000, "denoma"), coin(1, "denomb")], + exit: vec![coin(100_000, "denoma")], }, Case { - join: vec![Coin::new(1, "denoma"), Coin::new(100_000, "denomb")], - exit: vec![Coin::new(100_000, "denomb")], + join: vec![coin(1, "denoma"), coin(100_000, "denomb")], + exit: vec![coin(100_000, "denomb")], }, Case { - join: vec![Coin::new(u128::MAX, "denoma")], - exit: vec![Coin::new(u128::MAX, "denoma")], + join: vec![coin(u128::MAX, "denoma")], + exit: vec![coin(u128::MAX, "denoma")], }, Case { - join: vec![Coin::new(u128::MAX, "denoma")], - exit: vec![Coin::new(u128::MAX - 1, "denoma")], + join: vec![coin(u128::MAX, "denoma")], + exit: vec![coin(u128::MAX - 1, "denoma")], }, Case { - join: vec![Coin::new(u128::MAX, "denoma")], - exit: vec![Coin::new(u128::MAX - 100_000_000, "denoma")], + join: vec![coin(u128::MAX, "denoma")], + exit: vec![coin(u128::MAX - 100_000_000, "denoma")], }, ]; @@ -350,10 +335,7 @@ fn test_exit_pool_less_than_their_shares_should_update_shares_and_liquidity_prop .with_account("instantiator", vec![]) .with_account( "addr1", - vec![ - Coin::new(u128::MAX, "denoma"), - Coin::new(u128::MAX, "denomb"), - ], + vec![coin(u128::MAX, "denoma"), coin(u128::MAX, "denomb")], ) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ @@ -455,6 +437,7 @@ fn test_exit_pool_less_than_their_shares_should_update_shares_and_liquidity_prop vec![ attr("burn_from_address", t.accounts["addr1"].address()), attr("amount", format!("{}{}", exit_amount, share_denom)), + attr("msg_index", "0"), ] ); @@ -509,7 +492,7 @@ fn test_exit_pool_less_than_their_shares_should_update_shares_and_liquidity_prop .exit .iter() .find(|coin| coin.denom == curr.denom) - .unwrap_or(&Coin::new(0, curr.denom)) + .unwrap_or(&coin(0, curr.denom)) .amount; assert_eq!(curr.amount, prev.amount - exit_amount); }); @@ -526,23 +509,23 @@ fn test_exit_pool_greater_than_their_shares_should_fail() { let cases = vec![ Case { - join: vec![Coin::new(1, "denoma")], - exit: vec![Coin::new(2, "denoma")], + join: vec![coin(1, "denoma")], + exit: vec![coin(2, "denoma")], }, Case { - join: vec![Coin::new(100_000_000, "denoma")], - exit: vec![Coin::new(100_000_001, "denoma")], + join: vec![coin(100_000_000, "denoma")], + exit: vec![coin(100_000_001, "denoma")], }, Case { - join: vec![Coin::new(u128::MAX - 1, "denoma")], - exit: vec![Coin::new(u128::MAX, "denoma")], + join: vec![coin(u128::MAX - 1, "denoma")], + exit: vec![coin(u128::MAX, "denoma")], }, Case { join: vec![ - Coin::new(u128::MAX - 100_000_000, "denoma"), - Coin::new(99_999_999, "denomb"), + coin(u128::MAX - 100_000_000, "denoma"), + coin(99_999_999, "denomb"), ], - exit: vec![Coin::new(u128::MAX, "denoma")], + exit: vec![coin(u128::MAX, "denoma")], }, ]; @@ -550,7 +533,7 @@ fn test_exit_pool_greater_than_their_shares_should_fail() { let app = OsmosisTestApp::new(); // create denom - app.init_account(&[Coin::new(1, "denoma"), Coin::new(1, "denomb")]) + app.init_account(&[coin(1, "denoma"), coin(1, "denomb")]) .unwrap(); let t = TestEnvBuilder::new() @@ -605,8 +588,8 @@ fn test_exit_pool_greater_than_their_shares_should_fail() { fn test_exit_pool_within_shares_but_over_joined_denom_amount() { let app = OsmosisTestApp::new(); let t = TestEnvBuilder::new() - .with_account("instantiator", vec![Coin::new(100_000_000, "denoma")]) - .with_account("addr1", vec![Coin::new(200_000_000, "denomb")]) + .with_account("instantiator", vec![coin(100_000_000, "denoma")]) + .with_account("addr1", vec![coin(200_000_000, "denomb")]) .with_instantiate_msg(InstantiateMsg { pool_asset_configs: vec![ AssetConfig::from_denom_str("denoma"), @@ -622,7 +605,7 @@ fn test_exit_pool_within_shares_but_over_joined_denom_amount() { t.contract .execute( &ExecMsg::JoinPool {}, - &[Coin::new(100_000_000, "denoma")], + &[coin(100_000_000, "denoma")], &t.accounts["instantiator"], ) .unwrap(); @@ -630,7 +613,7 @@ fn test_exit_pool_within_shares_but_over_joined_denom_amount() { t.contract .execute( &ExecMsg::JoinPool {}, - &[Coin::new(200_000_000, "denomb")], + &[coin(200_000_000, "denomb")], &t.accounts["addr1"], ) .unwrap(); @@ -638,10 +621,7 @@ fn test_exit_pool_within_shares_but_over_joined_denom_amount() { t.contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![ - Coin::new(100_000_000, "denoma"), - Coin::new(100_000_000, "denomb"), - ], + tokens_out: vec![coin(100_000_000, "denoma"), coin(100_000_000, "denomb")], }, &[], &t.accounts["addr1"], diff --git a/contracts/transmuter/src/test/cases/units/migrate.rs b/contracts/transmuter/src/test/cases/units/migrate.rs index ae35b4f..0c06d9d 100644 --- a/contracts/transmuter/src/test/cases/units/migrate.rs +++ b/contracts/transmuter/src/test/cases/units/migrate.rs @@ -1,16 +1,16 @@ -use std::{iter, path::PathBuf}; +use std::{collections::BTreeMap, iter, path::PathBuf}; use crate::{ asset::AssetConfig, contract::{ sv::{InstantiateMsg, QueryMsg}, - GetModeratorResponse, ListAssetConfigsResponse, + GetModeratorResponse, ListAssetConfigsResponse, ListAssetGroupsResponse, }, - migrations::v3_2_0::MigrateMsg, + migrations::v4_0_0::MigrateMsg, test::{modules::cosmwasm_pool::CosmwasmPool, test_env::TransmuterContract}, }; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_json, to_json_binary, Coin, Uint128}; +use cosmwasm_std::{coin, from_json, to_json_binary, Uint128}; use osmosis_std::types::{ cosmwasm::wasm::v1::{QueryRawContractStateRequest, QueryRawContractStateResponse}, osmosis::cosmwasmpool::v1beta1::{ @@ -42,9 +42,9 @@ fn test_migrate_v2_to_v3() { let app = OsmosisTestApp::new(); let signer = app .init_account(&[ - Coin::new(100000, "denom1"), - Coin::new(100000, "denom2"), - Coin::new(10000000000000, "uosmo"), + coin(100000, "denom1"), + coin(100000, "denom2"), + coin(10000000000000, "uosmo"), ]) .unwrap(); @@ -143,20 +143,24 @@ fn test_migrate_v2_to_v3() { assert_eq!(asset_configs, expected_asset_configs); let GetModeratorResponse { moderator } = t.query(&QueryMsg::GetModerator {}).unwrap(); - assert_eq!(moderator, migrate_msg.moderator.unwrap()); + + assert_eq!(moderator.into_string(), migrate_msg.moderator.unwrap()); } +#[cw_serde] +struct MigrateMsgV3_2 {} + #[rstest] #[case("v3")] #[case("v3_1")] -fn test_migrate_v3(#[case] from_version: &str) { +fn test_migrate_v3_2(#[case] from_version: &str) { // --- setup account --- let app = OsmosisTestApp::new(); let signer = app .init_account(&[ - Coin::new(100000, "denom1"), - Coin::new(100000, "denom2"), - Coin::new(10000000000000, "uosmo"), + coin(100000, "denom1"), + coin(100000, "denom2"), + coin(10000000000000, "uosmo"), ]) .unwrap(); @@ -217,7 +221,7 @@ fn test_migrate_v3(#[case] from_version: &str) { let t = TransmuterContract::new(&app, code_id, pool_id, contract_address.clone()); // --- migrate pool --- - let migrate_msg = MigrateMsg {}; + let migrate_msg = MigrateMsgV3_2 {}; gov.propose_and_execute( MigratePoolContractsProposal::TYPE_URL.to_string(), @@ -226,7 +230,7 @@ fn test_migrate_v3(#[case] from_version: &str) { description: "test migration".to_string(), pool_ids: vec![pool_id], new_code_id: 0, // upload new code, so set this to 0 - wasm_byte_code: TransmuterContract::get_wasm_byte_code(), + wasm_byte_code: get_prev_version_of_wasm_byte_code("v3_2"), migrate_msg: to_json_binary(&migrate_msg).unwrap().to_vec(), }, signer.address(), @@ -273,6 +277,143 @@ fn test_migrate_v3(#[case] from_version: &str) { ); } +#[test] +fn test_migrate_v4_0_0() { + let from_version = "v3_2"; + // --- setup account --- + let app = OsmosisTestApp::new(); + let signer = app + .init_account(&[ + coin(100000, "denom1"), + coin(100000, "denom2"), + coin(10000000000000, "uosmo"), + ]) + .unwrap(); + + // --- create pool ---- + + let cp = CosmwasmPool::new(&app); + let gov = GovWithAppAccess::new(&app); + gov.propose_and_execute( + UploadCosmWasmPoolCodeAndWhiteListProposal::TYPE_URL.to_string(), + UploadCosmWasmPoolCodeAndWhiteListProposal { + title: String::from("store test cosmwasm pool code"), + description: String::from("test"), + wasm_byte_code: get_prev_version_of_wasm_byte_code(from_version), + }, + signer.address(), + &signer, + ) + .unwrap(); + + let instantiate_msg = InstantiateMsg { + pool_asset_configs: vec![ + AssetConfig { + denom: "denom1".to_string(), + normalization_factor: Uint128::new(1), + }, + AssetConfig { + denom: "denom2".to_string(), + normalization_factor: Uint128::new(10000), + }, + ], + alloyed_asset_subdenom: "denomx".to_string(), + alloyed_asset_normalization_factor: Uint128::new(10), + admin: Some(signer.address()), + moderator: signer.address(), + }; + + let code_id = 1; + let res = cp + .create_cosmwasm_pool( + MsgCreateCosmWasmPool { + code_id, + instantiate_msg: to_json_binary(&instantiate_msg).unwrap().to_vec(), + sender: signer.address(), + }, + &signer, + ) + .unwrap(); + + let pool_id = res.data.pool_id; + + let ContractInfoByPoolIdResponse { + contract_address, + code_id: _, + } = cp + .contract_info_by_pool_id(&ContractInfoByPoolIdRequest { pool_id }) + .unwrap(); + + let t = TransmuterContract::new(&app, code_id, pool_id, contract_address.clone()); + + // --- migrate pool --- + let migrate_msg = MigrateMsg {}; + + gov.propose_and_execute( + MigratePoolContractsProposal::TYPE_URL.to_string(), + MigratePoolContractsProposal { + title: "migrate cosmwasmpool".to_string(), + description: "test migration".to_string(), + pool_ids: vec![pool_id], + new_code_id: 0, // upload new code, so set this to 0 + wasm_byte_code: TransmuterContract::get_wasm_byte_code(), + migrate_msg: to_json_binary(&migrate_msg).unwrap().to_vec(), + }, + signer.address(), + &signer, + ) + .unwrap(); + + let alloyed_denom = format!("factory/{contract_address}/alloyed/denomx"); + + let expected_asset_configs = instantiate_msg + .pool_asset_configs + .into_iter() + .chain(iter::once(AssetConfig { + denom: alloyed_denom, + normalization_factor: instantiate_msg.alloyed_asset_normalization_factor, + })) + .collect::>(); + + // list asset configs + let ListAssetConfigsResponse { asset_configs } = + t.query(&QueryMsg::ListAssetConfigs {}).unwrap(); + + // expect no changes in asset config + assert_eq!(asset_configs, expected_asset_configs); + + let res: QueryRawContractStateResponse = app + .query( + "/cosmwasm.wasm.v1.Query/RawContractState", + &QueryRawContractStateRequest { + address: t.contract_addr.clone(), + query_data: b"contract_info".to_vec(), + }, + ) + .unwrap(); + + let version: cw2::ContractVersion = from_json(res.data).unwrap(); + + assert_eq!( + version, + cw2::ContractVersion { + contract: "crates.io:transmuter".to_string(), + version: "4.0.0".to_string() + } + ); + + // asset configs should be the same + let ListAssetConfigsResponse { asset_configs } = + t.query(&QueryMsg::ListAssetConfigs {}).unwrap(); + + assert_eq!(asset_configs, expected_asset_configs); + + // list asset groups + let ListAssetGroupsResponse { asset_groups } = t.query(&QueryMsg::ListAssetGroups {}).unwrap(); + + assert_eq!(asset_groups, BTreeMap::new()); +} + fn get_prev_version_of_wasm_byte_code(version: &str) -> Vec { let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let wasm_path = manifest_path diff --git a/contracts/transmuter/src/test/cases/units/spot_price.rs b/contracts/transmuter/src/test/cases/units/spot_price.rs index 1f2e60d..635eeb1 100644 --- a/contracts/transmuter/src/test/cases/units/spot_price.rs +++ b/contracts/transmuter/src/test/cases/units/spot_price.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{ - testing::{mock_dependencies, mock_env, mock_info}, + coin, + testing::{message_info, mock_dependencies, mock_env}, Coin, Decimal, Uint128, }; use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx}; @@ -8,32 +9,31 @@ use crate::{asset::AssetConfig, contract::Transmuter, ContractError}; #[test] fn test_spot_price_on_balanced_liquidity_must_be_one() { - test_spot_price(&[Coin::new(100_000, "denom0"), Coin::new(100_000, "denom1")]) + test_spot_price(&[coin(100_000, "denom0"), coin(100_000, "denom1")]) } #[test] fn test_spot_price_on_unbalanced_liquidity_must_be_one() { - test_spot_price(&[ - Coin::new(999_999_999, "denom0"), - Coin::new(100_000, "denom1"), - ]) + test_spot_price(&[coin(999_999_999, "denom0"), coin(100_000, "denom1")]) } fn test_spot_price(liquidity: &[Coin]) { - let transmuter = Transmuter::default(); + let transmuter = Transmuter::new(); let mut deps = mock_dependencies(); + deps.api = deps.api.with_prefix("osmo"); // make denom has non-zero total supply - deps.querier.update_balance( - "someone", - vec![Coin::new(1, "denom0"), Coin::new(1, "denom1")], - ); + deps.querier + .bank + .update_balance("someone", vec![coin(1, "denom0"), coin(1, "denom1")]); + + let creator = deps.api.addr_make("creator"); transmuter .instantiate( InstantiateCtx { deps: deps.as_mut(), env: mock_env(), - info: mock_info("creator", &[]), + info: message_info(&creator, &[]), }, vec![ AssetConfig::from_denom_str("denom0"), @@ -54,11 +54,12 @@ fn test_spot_price(liquidity: &[Coin]) { ) .unwrap(); + let creator = deps.api.addr_make("creator"); transmuter .join_pool(ExecCtx { deps: deps.as_mut(), env: mock_env(), - info: mock_info("creator", liquidity), + info: message_info(&creator, liquidity), }) .unwrap(); diff --git a/contracts/transmuter/src/test/cases/units/swap/client_error.rs b/contracts/transmuter/src/test/cases/units/swap/client_error.rs index 29d4c17..7f5169a 100644 --- a/contracts/transmuter/src/test/cases/units/swap/client_error.rs +++ b/contracts/transmuter/src/test/cases/units/swap/client_error.rs @@ -1,3 +1,5 @@ +use cosmwasm_std::coin; + use super::*; const REMAINING_DENOM0: u128 = 1_000_000_000_000; @@ -7,8 +9,8 @@ fn pool_with_assets(app: &'_ OsmosisTestApp) -> TestEnv<'_> { pool_with_single_lp( app, vec![ - Coin::new(REMAINING_DENOM0, "denom0"), - Coin::new(REMAINING_DENOM1, "denom1"), + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1, "denom1"), ], vec![], ) @@ -18,7 +20,7 @@ test_swap! { swap_exact_amount_in_with_token_out_less_than_token_out_min_amount_should_fail [expect error] { setup = pool_with_assets, msg = SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1_000_000, "denom0"), + token_in: coin(1_000_000, "denom0"), token_out_denom: "denom1".to_string(), token_out_min_amount: 1_000_001u128.into(), }, @@ -35,7 +37,7 @@ test_swap! { msg = SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: 999_999u128.into(), - token_out: Coin::new(1_000_000, "denom1"), + token_out: coin(1_000_000, "denom1"), }, err = ContractError::ExcessiveRequiredTokenIn { limit: 999_999u128.into(), @@ -49,14 +51,14 @@ test_swap! { setup = pool_with_assets, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1_000_000, "denom0"), + token_in: coin(1_000_000, "denom0"), token_out_denom: "uosmo".to_string(), token_out_min_amount: Uint128::one(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: 1_000_000u128.into(), - token_out: Coin::new(1_000_000, "uosmo"), + token_out: coin(1_000_000, "uosmo"), }, ], err = ContractError::InvalidTransmuteDenom { @@ -71,14 +73,14 @@ test_swap! { setup = pool_with_assets, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1_000_000, "uosmo"), + token_in: coin(1_000_000, "uosmo"), token_out_denom: "denom1".to_string(), token_out_min_amount: Uint128::one(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "uosmo".to_string(), token_in_max_amount: 1_000_000u128.into(), - token_out: Coin::new(1_000_000, "denom1"), + token_out: coin(1_000_000, "denom1"), }, ], err = ContractError::InvalidTransmuteDenom { diff --git a/contracts/transmuter/src/test/cases/units/swap/mod.rs b/contracts/transmuter/src/test/cases/units/swap/mod.rs index ed93f62..e93a206 100644 --- a/contracts/transmuter/src/test/cases/units/swap/mod.rs +++ b/contracts/transmuter/src/test/cases/units/swap/mod.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{coin, Coin, Uint128}; use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_std::types::osmosis::poolmanager::v1beta1::{ @@ -27,14 +27,14 @@ macro_rules! test_swap { #[test] fn $test_name() { let app = osmosis_test_tube::OsmosisTestApp::new(); - test_swap_success_case($setup(&app), $msg, $received); + test_swap_success_case(&$setup(&app), $msg, $received); } }; ($test_name:ident [expect error] { setup = $setup:ident, msg = $msg:expr, err = $err:expr }) => { #[test] fn $test_name() { let app = osmosis_test_tube::OsmosisTestApp::new(); - test_swap_failed_case($setup(&app), $msg, $err); + test_swap_failed_case(&$setup(&app), $msg, $err); } }; ($test_name:ident [expect ok] { setup = $setup:expr, msgs = $msgs:expr, received = $received:expr }) => { @@ -42,7 +42,7 @@ macro_rules! test_swap { fn $test_name() { for msg in $msgs { let app = osmosis_test_tube::OsmosisTestApp::new(); - test_swap_success_case($setup(&app), msg, $received); + test_swap_success_case(&$setup(&app), msg, $received); } } }; @@ -51,7 +51,7 @@ macro_rules! test_swap { fn $test_name() { for msg in $msgs { let app = osmosis_test_tube::OsmosisTestApp::new(); - test_swap_failed_case($setup(&app), msg, $err); + test_swap_failed_case(&$setup(&app), msg, $err); } } }; @@ -71,7 +71,7 @@ pub enum SwapMsg { }, } -fn assert_invariants(t: TestEnv, act: impl FnOnce(&TestEnv) -> String) { +fn assert_invariants(t: &TestEnv, act: impl FnOnce(&TestEnv) -> String) { // store previous shares and pool assets let prev_shares = t .contract @@ -159,7 +159,7 @@ fn lcm_normalization_factor(configs: &[AssetConfig]) -> Uint128 { lcm_from_iter(norm_factors).unwrap() } -fn test_swap_success_case(t: TestEnv, msg: SwapMsg, received: Coin) { +fn test_swap_success_case(t: &TestEnv, msg: SwapMsg, received: Coin) { assert_invariants(t, move |t| { let cp = CosmwasmPool::new(t.app); let bank = Bank::new(t.app); @@ -422,7 +422,7 @@ pub fn test_swap_share_denom_success_case(t: &TestEnv, msg: SwapMsg, sent: Coin, ); } -fn test_swap_failed_case(t: TestEnv, msg: SwapMsg, err: ContractError) { +fn test_swap_failed_case(t: &TestEnv, msg: SwapMsg, err: ContractError) { assert_invariants(t, move |t| { let cp = CosmwasmPool::new(t.app); @@ -488,13 +488,14 @@ fn pool_with_single_lp( .collect::>(); let t = TestEnvBuilder::new() + .with_account("admin", non_zero_pool_assets.clone()) .with_account("provider", non_zero_pool_assets.clone()) .with_account( SWAPPER, non_zero_pool_assets .iter() .filter(|coin| !coin.amount.is_zero()) - .map(|coin| Coin::new(10000000000000000000000000, coin.denom.clone())) + .map(|c| coin(10000000000000000000000000, c.denom.clone())) .collect(), ) .with_instantiate_msg(crate::contract::sv::InstantiateMsg { @@ -531,3 +532,5 @@ fn pool_with_single_lp( mod client_error; mod non_empty_pool; mod swap_share_denom; + +mod swap_with_asset_group_limiters; diff --git a/contracts/transmuter/src/test/cases/units/swap/non_empty_pool.rs b/contracts/transmuter/src/test/cases/units/swap/non_empty_pool.rs index 2b2ea3a..3ba4c24 100644 --- a/contracts/transmuter/src/test/cases/units/swap/non_empty_pool.rs +++ b/contracts/transmuter/src/test/cases/units/swap/non_empty_pool.rs @@ -1,3 +1,5 @@ +use cosmwasm_std::coin; + use super::*; const REMAINING_DENOM0: u128 = 1_000_000_000_000_000_000; @@ -7,8 +9,8 @@ fn non_empty_pool(app: &'_ OsmosisTestApp) -> TestEnv<'_> { pool_with_single_lp( app, vec![ - Coin::new(REMAINING_DENOM0, "denom0"), - Coin::new(REMAINING_DENOM1, "denom1"), + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1, "denom1"), ], vec![], ) @@ -19,17 +21,17 @@ test_swap! { setup = non_empty_pool, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1, "denom0"), + token_in: coin(1, "denom0"), token_out_denom: "denom1".to_string(), token_out_min_amount: Uint128::one(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: Uint128::one(), - token_out: Coin::new(1, "denom1"), + token_out: coin(1, "denom1"), }, ], - received = Coin::new(1, "denom1") + received = coin(1, "denom1") } } @@ -38,17 +40,17 @@ test_swap! { setup = non_empty_pool, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1, "denom1"), + token_in: coin(1, "denom1"), token_out_denom: "denom0".to_string(), token_out_min_amount: Uint128::one(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom1".to_string(), token_in_max_amount: Uint128::one(), - token_out: Coin::new(1, "denom0"), + token_out: coin(1, "denom0"), }, ], - received = Coin::new(1, "denom0") + received = coin(1, "denom0") } } @@ -57,17 +59,17 @@ test_swap! { setup = non_empty_pool, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(REMAINING_DENOM0, "denom0"), + token_in: coin(REMAINING_DENOM0, "denom0"), token_out_denom: "denom1".to_string(), token_out_min_amount: REMAINING_DENOM0.into(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: REMAINING_DENOM0.into(), - token_out: Coin::new(REMAINING_DENOM0, "denom1"), + token_out: coin(REMAINING_DENOM0, "denom1"), }, ], - received = Coin::new(REMAINING_DENOM0, "denom1") + received = coin(REMAINING_DENOM0, "denom1") } } @@ -76,17 +78,17 @@ test_swap! { setup = non_empty_pool, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(REMAINING_DENOM1, "denom1"), + token_in: coin(REMAINING_DENOM1, "denom1"), token_out_denom: "denom0".to_string(), token_out_min_amount: REMAINING_DENOM1.into(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom1".to_string(), token_in_max_amount: REMAINING_DENOM1.into(), - token_out: Coin::new(REMAINING_DENOM1, "denom0"), + token_out: coin(REMAINING_DENOM1, "denom0"), }, ], - received = Coin::new(REMAINING_DENOM1, "denom0") + received = coin(REMAINING_DENOM1, "denom0") } } @@ -95,17 +97,17 @@ test_swap! { setup = non_empty_pool, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(999_999, "denom1"), + token_in: coin(999_999, "denom1"), token_out_denom: "denom0".to_string(), token_out_min_amount: 999_999u128.into(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom1".to_string(), token_in_max_amount: 999_999u128.into(), - token_out: Coin::new(999_999, "denom0"), + token_out: coin(999_999, "denom0"), }, ], - received = Coin::new(999_999, "denom0") + received = coin(999_999, "denom0") } } @@ -114,17 +116,17 @@ test_swap! { setup = non_empty_pool, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(999_999, "denom0"), + token_in: coin(999_999, "denom0"), token_out_denom: "denom1".to_string(), token_out_min_amount: 999_999u128.into(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: 999_999u128.into(), - token_out: Coin::new(999_999, "denom1"), + token_out: coin(999_999, "denom1"), }, ], - received = Coin::new(999_999, "denom1") + received = coin(999_999, "denom1") } } @@ -132,8 +134,8 @@ fn non_empty_pool_with_normalization_factor(app: &'_ OsmosisTestApp) -> TestEnv< pool_with_single_lp( app, vec![ - Coin::new(REMAINING_DENOM0, "denom0"), - Coin::new(REMAINING_DENOM1, "denom1"), + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1, "denom1"), ], vec![ AssetConfig { @@ -153,17 +155,17 @@ test_swap! { setup = non_empty_pool_with_normalization_factor, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(3u128 * 10u128.pow(2), "denom0"), + token_in: coin(3u128 * 10u128.pow(2), "denom0"), token_out_denom: "denom1".to_string(), token_out_min_amount: Uint128::one(), }, SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: (300u128).into(), - token_out: Coin::new(1, "denom1"), + token_out: coin(1, "denom1"), }, ], - received = Coin::new(1, "denom1") + received = coin(1, "denom1") } } @@ -172,13 +174,13 @@ test_swap! { setup = non_empty_pool_with_normalization_factor, msgs = [ SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1000, "denom0"), + token_in: coin(1000, "denom0"), token_out_denom: "denom1".to_string(), token_out_min_amount: Uint128::from(3u128), }, ], - received = Coin::new(3, "denom1") + received = coin(3, "denom1") } } @@ -189,9 +191,9 @@ test_swap! { SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: (900u128).into(), - token_out: Coin::new(3, "denom1"), + token_out: coin(3, "denom1"), }, ], - received = Coin::new(3, "denom1") + received = coin(3, "denom1") } } diff --git a/contracts/transmuter/src/test/cases/units/swap/swap_share_denom.rs b/contracts/transmuter/src/test/cases/units/swap/swap_share_denom.rs index 6304d48..ec79e0d 100644 --- a/contracts/transmuter/src/test/cases/units/swap/swap_share_denom.rs +++ b/contracts/transmuter/src/test/cases/units/swap/swap_share_denom.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{coin, Uint128}; use crate::{asset::AssetConfig, contract::sv::QueryMsg, contract::GetShareDenomResponse}; @@ -12,8 +12,8 @@ fn test_swap_exact_amount_in_with_share_denom() { let t = pool_with_single_lp( &app, vec![ - Coin::new(REMAINING_DENOM0, "denom0"), - Coin::new(REMAINING_DENOM1, "denom1"), + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1, "denom1"), ], vec![], ); @@ -28,23 +28,23 @@ fn test_swap_exact_amount_in_with_share_denom() { test_swap_share_denom_success_case( &t, SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1_000, "denom0".to_string()), + token_in: coin(1_000, "denom0".to_string()), token_out_denom: share_denom.clone(), token_out_min_amount: Uint128::one(), }, - Coin::new(1_000, "denom0".to_string()), - Coin::new(1_000, share_denom.clone()), + coin(1_000, "denom0".to_string()), + coin(1_000, share_denom.clone()), ); test_swap_share_denom_success_case( &t, SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1_000, share_denom.clone()), + token_in: coin(1_000, share_denom.clone()), token_out_denom: "denom1".to_string(), token_out_min_amount: Uint128::one(), }, - Coin::new(1_000, share_denom), - Coin::new(1_000, "denom1".to_string()), + coin(1_000, share_denom), + coin(1_000, "denom1".to_string()), ); } @@ -54,8 +54,8 @@ fn test_swap_exact_amount_in_with_share_denom_and_normalization_factor() { let t = pool_with_single_lp( &app, vec![ - Coin::new(REMAINING_DENOM0, "denom0"), - Coin::new(REMAINING_DENOM1 * 10u128.pow(8), "denom1"), + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1 * 10u128.pow(8), "denom1"), ], vec![AssetConfig { denom: "denom1".to_string(), @@ -73,23 +73,23 @@ fn test_swap_exact_amount_in_with_share_denom_and_normalization_factor() { test_swap_share_denom_success_case( &t, SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1_000 * 10u128.pow(8), "denom1".to_string()), + token_in: coin(1_000 * 10u128.pow(8), "denom1".to_string()), token_out_denom: share_denom.clone(), token_out_min_amount: Uint128::one(), }, - Coin::new(1_000 * 10u128.pow(8), "denom1".to_string()), - Coin::new(1_000, share_denom.clone()), + coin(1_000 * 10u128.pow(8), "denom1".to_string()), + coin(1_000, share_denom.clone()), ); test_swap_share_denom_success_case( &t, SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1000, share_denom.clone()), + token_in: coin(1000, share_denom.clone()), token_out_denom: "denom1".to_string(), token_out_min_amount: Uint128::one(), }, - Coin::new(1000, share_denom), - Coin::new(1000 * 10u128.pow(8), "denom1".to_string()), + coin(1000, share_denom), + coin(1000 * 10u128.pow(8), "denom1".to_string()), ); } @@ -99,8 +99,8 @@ fn test_swap_exact_amount_out_with_share_denom() { let t = pool_with_single_lp( &app, vec![ - Coin::new(REMAINING_DENOM0, "denom0"), - Coin::new(REMAINING_DENOM1, "denom1"), + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1, "denom1"), ], vec![], ); @@ -117,10 +117,10 @@ fn test_swap_exact_amount_out_with_share_denom() { SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: Uint128::from(1_000u128), - token_out: Coin::new(1_000, share_denom.clone()), + token_out: coin(1_000, share_denom.clone()), }, - Coin::new(1_000, "denom0".to_string()), - Coin::new(1_000, share_denom.clone()), + coin(1_000, "denom0".to_string()), + coin(1_000, share_denom.clone()), ); test_swap_share_denom_success_case( @@ -128,17 +128,17 @@ fn test_swap_exact_amount_out_with_share_denom() { SwapMsg::SwapExactAmountOut { token_in_denom: share_denom.clone(), token_in_max_amount: Uint128::from(1_000u128), - token_out: Coin::new(1_000, "denom1".to_string()), + token_out: coin(1_000, "denom1".to_string()), }, - Coin::new(1_000, share_denom), - Coin::new(1_000, "denom1".to_string()), + coin(1_000, share_denom), + coin(1_000, "denom1".to_string()), ); } #[test] fn test_swap_exact_amount_out_with_share_denom_single_asset_pool() { let app = osmosis_test_tube::OsmosisTestApp::new(); - let t = pool_with_single_lp(&app, vec![Coin::new(REMAINING_DENOM0, "denom0")], vec![]); + let t = pool_with_single_lp(&app, vec![coin(REMAINING_DENOM0, "denom0")], vec![]); // get share denom let share_denom = t @@ -152,10 +152,10 @@ fn test_swap_exact_amount_out_with_share_denom_single_asset_pool() { SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: Uint128::from(1_000u128), - token_out: Coin::new(1_000, share_denom.clone()), + token_out: coin(1_000, share_denom.clone()), }, - Coin::new(1_000, "denom0".to_string()), - Coin::new(1_000, share_denom.clone()), + coin(1_000, "denom0".to_string()), + coin(1_000, share_denom.clone()), ); test_swap_share_denom_success_case( @@ -163,10 +163,10 @@ fn test_swap_exact_amount_out_with_share_denom_single_asset_pool() { SwapMsg::SwapExactAmountOut { token_in_denom: share_denom.clone(), token_in_max_amount: Uint128::from(1_000u128), - token_out: Coin::new(1_000, "denom0".to_string()), + token_out: coin(1_000, "denom0".to_string()), }, - Coin::new(1_000, share_denom.clone()), - Coin::new(1_000, "denom0".to_string()), + coin(1_000, share_denom.clone()), + coin(1_000, "denom0".to_string()), ); } @@ -175,7 +175,7 @@ fn test_swap_exact_amount_in_with_share_denom_and_normalization_factor_single_as let app = osmosis_test_tube::OsmosisTestApp::new(); let t = pool_with_single_lp( &app, - vec![Coin::new(REMAINING_DENOM1 * 10u128.pow(8), "denom1")], + vec![coin(REMAINING_DENOM1 * 10u128.pow(8), "denom1")], vec![AssetConfig { denom: "denom1".to_string(), normalization_factor: 10u128.pow(8).into(), @@ -192,23 +192,23 @@ fn test_swap_exact_amount_in_with_share_denom_and_normalization_factor_single_as test_swap_share_denom_success_case( &t, SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1_000 * 10u128.pow(8), "denom1".to_string()), + token_in: coin(1_000 * 10u128.pow(8), "denom1".to_string()), token_out_denom: share_denom.clone(), token_out_min_amount: Uint128::one(), }, - Coin::new(1_000 * 10u128.pow(8), "denom1".to_string()), - Coin::new(1_000, share_denom.clone()), + coin(1_000 * 10u128.pow(8), "denom1".to_string()), + coin(1_000, share_denom.clone()), ); test_swap_share_denom_success_case( &t, SwapMsg::SwapExactAmountIn { - token_in: Coin::new(1000, share_denom.clone()), + token_in: coin(1000, share_denom.clone()), token_out_denom: "denom1".to_string(), token_out_min_amount: Uint128::one(), }, - Coin::new(1000, share_denom), - Coin::new(1000 * 10u128.pow(8), "denom1".to_string()), + coin(1000, share_denom), + coin(1000 * 10u128.pow(8), "denom1".to_string()), ); } @@ -218,8 +218,8 @@ fn test_swap_exact_amount_out_with_share_denom_where_token_in_max_is_exceeding_e let t = pool_with_single_lp( &app, vec![ - Coin::new(REMAINING_DENOM0, "denom0"), - Coin::new(REMAINING_DENOM1, "denom1"), + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1, "denom1"), ], vec![], ); @@ -237,10 +237,10 @@ fn test_swap_exact_amount_out_with_share_denom_where_token_in_max_is_exceeding_e SwapMsg::SwapExactAmountOut { token_in_denom: "denom0".to_string(), token_in_max_amount: Uint128::from(1_001u128), - token_out: Coin::new(1_001, share_denom.clone()), + token_out: coin(1_001, share_denom.clone()), }, - Coin::new(1_001, "denom0".to_string()), - Coin::new(1_001, share_denom.clone()), + coin(1_001, "denom0".to_string()), + coin(1_001, share_denom.clone()), ); // swap 1000 share_denom for 1000 denom1 but set token_in_max_amount to 1001 (1 extra share_denom) @@ -249,9 +249,9 @@ fn test_swap_exact_amount_out_with_share_denom_where_token_in_max_is_exceeding_e SwapMsg::SwapExactAmountOut { token_in_denom: share_denom.clone(), token_in_max_amount: Uint128::from(1_001u128), - token_out: Coin::new(1_000, "denom1".to_string()), + token_out: coin(1_000, "denom1".to_string()), }, - Coin::new(1_000, share_denom), - Coin::new(1_000, "denom1".to_string()), + coin(1_000, share_denom), + coin(1_000, "denom1".to_string()), ); } diff --git a/contracts/transmuter/src/test/cases/units/swap/swap_with_asset_group_limiters.rs b/contracts/transmuter/src/test/cases/units/swap/swap_with_asset_group_limiters.rs new file mode 100644 index 0000000..c8074fd --- /dev/null +++ b/contracts/transmuter/src/test/cases/units/swap/swap_with_asset_group_limiters.rs @@ -0,0 +1,100 @@ +use cosmwasm_std::{coin, Decimal, Uint128, Uint64}; + +use crate::{ + asset::AssetConfig, + contract::sv::ExecMsg, + limiter::{LimiterParams, WindowConfig}, + scope::Scope, + ContractError, +}; + +use super::{pool_with_single_lp, test_swap_failed_case, test_swap_success_case, SwapMsg}; + +const REMAINING_DENOM0: u128 = 1_000_000_000_000; +const REMAINING_DENOM1: u128 = 1_000_000_000_000; +const REMAINING_DENOM2: u128 = 1_000_000_000_000; + +#[test] +fn test_swap_with_asset_group_limiters() { + let app = osmosis_test_tube::OsmosisTestApp::new(); + let t = pool_with_single_lp( + &app, + vec![ + coin(REMAINING_DENOM0, "denom0"), + coin(REMAINING_DENOM1, "denom1"), + coin(REMAINING_DENOM2, "denom2"), + ], + vec![ + AssetConfig { + denom: "denom0".to_string(), + normalization_factor: Uint128::one(), + }, + AssetConfig { + denom: "denom1".to_string(), + normalization_factor: Uint128::one(), + }, + AssetConfig { + denom: "denom2".to_string(), + normalization_factor: Uint128::one(), + }, + ], + ); + + // Add asset group and limiters + t.contract + .execute( + &ExecMsg::CreateAssetGroup { + label: "group1".to_string(), + denoms: vec!["denom0".to_string(), "denom1".to_string()], + }, + &[], + &t.accounts["admin"], + ) + .unwrap(); + + t.contract + .execute( + &ExecMsg::RegisterLimiter { + scope: Scope::asset_group("group1"), + label: "limiter1".to_string(), + limiter_params: LimiterParams::ChangeLimiter { + window_config: WindowConfig { + window_size: Uint64::from(1000u64), + division_count: Uint64::from(5u64), + }, + boundary_offset: Decimal::percent(10), + }, + }, + &[], + &t.accounts["admin"], + ) + .unwrap(); + + // swap within group, even an agressive one wouldn't effect anything + test_swap_success_case( + &t, + SwapMsg::SwapExactAmountOut { + token_in_denom: "denom0".to_string(), + token_in_max_amount: Uint128::from(REMAINING_DENOM1), + token_out: coin(REMAINING_DENOM1, "denom1".to_string()), + }, + coin(REMAINING_DENOM1, "denom1".to_string()), + ); + + app.increase_time(5); + + // swap denom0 to denom2 -> increase group1 weight by adding more denom0 + test_swap_failed_case( + &t, + SwapMsg::SwapExactAmountOut { + token_in_denom: "denom0".to_string(), + token_in_max_amount: Uint128::from(REMAINING_DENOM0), + token_out: coin(REMAINING_DENOM0, "denom2".to_string()), + }, + ContractError::UpperLimitExceeded { + scope: Scope::asset_group("group1"), + upper_limit: "0.766666666666666666".parse().unwrap(), + value: Decimal::percent(100), + }, + ); +} diff --git a/contracts/transmuter/src/test/test_env.rs b/contracts/transmuter/src/test/test_env.rs index 9390cff..371f67b 100644 --- a/contracts/transmuter/src/test/test_env.rs +++ b/contracts/transmuter/src/test/test_env.rs @@ -5,7 +5,7 @@ use crate::{ ContractError, }; -use cosmwasm_std::{to_json_binary, Coin}; +use cosmwasm_std::{coin, to_json_binary, Coin}; use osmosis_std::types::{ cosmos::bank::v1beta1::QueryAllBalancesRequest, cosmwasm::wasm::v1::MsgExecuteContractResponse, @@ -45,12 +45,13 @@ impl<'a> TestEnv<'a> { .query_all_balances(&QueryAllBalancesRequest { address: self.accounts.get(account).unwrap().address(), pagination: None, + resolve_denom: false, }) .unwrap() .balances .into_iter() - .map(|coin| Coin::new(coin.amount.parse().unwrap(), coin.denom)) - .filter(|coin| !ignore_denoms.contains(&coin.denom.as_str())) + .map(|c| coin(c.amount.parse().unwrap(), c.denom)) + .filter(|c| !ignore_denoms.contains(&c.denom.as_str())) .collect(); assert_eq!(account_balances, expected_balances); @@ -61,11 +62,12 @@ impl<'a> TestEnv<'a> { .query_all_balances(&QueryAllBalancesRequest { address: self.contract.contract_addr.clone(), pagination: None, + resolve_denom: false, }) .unwrap() .balances .into_iter() - .map(|coin| Coin::new(coin.amount.parse().unwrap(), coin.denom)) + .map(|c| coin(c.amount.parse().unwrap(), c.denom)) .collect(); assert_eq!(contract_balances, expected_balances); @@ -108,7 +110,7 @@ impl TestEnvBuilder { .map(|(account, balance)| { let balance: Vec<_> = balance .into_iter() - .chain(vec![Coin::new(1000000000000, "uosmo")]) + .chain(vec![coin(1000000000000, "uosmo")]) .collect(); (account, app.init_account(&balance).unwrap()) @@ -116,7 +118,7 @@ impl TestEnvBuilder { .collect(); let creator = app - .init_account(&[Coin::new(1000000000000000u128, "uosmo")]) + .init_account(&[coin(1000000000000000u128, "uosmo")]) .unwrap(); let instantiate_msg = self.instantiate_msg.expect("instantiate msg not set"); diff --git a/contracts/transmuter/src/transmuter_pool/add_new_assets.rs b/contracts/transmuter/src/transmuter_pool/add_new_assets.rs index b245d58..307bf03 100644 --- a/contracts/transmuter/src/transmuter_pool/add_new_assets.rs +++ b/contracts/transmuter/src/transmuter_pool/add_new_assets.rs @@ -13,7 +13,9 @@ impl TransmuterPool { #[cfg(test)] mod tests { - use cosmwasm_std::{Coin, Uint128, Uint64}; + use std::collections::BTreeMap; + + use cosmwasm_std::{coin, Uint128, Uint64}; use crate::transmuter_pool::{MAX_POOL_ASSET_DENOMS, MIN_POOL_ASSET_DENOMS}; @@ -23,14 +25,13 @@ mod tests { fn test_add_new_assets() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100000000, "asset1"), - Coin::new(99999999, "asset2"), + coin(100000000, "asset1"), + coin(99999999, "asset2"), ]), + asset_groups: BTreeMap::new(), }; - let new_assets = Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, "asset3"), - Coin::new(0, "asset4"), - ]); + let new_assets = + Asset::unchecked_equal_assets_from_coins(&[coin(0, "asset3"), coin(0, "asset4")]); pool.add_new_assets(new_assets).unwrap(); assert_eq!( pool.pool_assets, @@ -47,14 +48,13 @@ mod tests { fn test_add_duplicated_assets() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100000000, "asset1"), - Coin::new(99999999, "asset2"), + coin(100000000, "asset1"), + coin(99999999, "asset2"), ]), + asset_groups: BTreeMap::new(), }; - let new_assets = Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, "asset3"), - Coin::new(0, "asset4"), - ]); + let new_assets = + Asset::unchecked_equal_assets_from_coins(&[coin(0, "asset3"), coin(0, "asset4")]); pool.add_new_assets(new_assets.clone()).unwrap(); let err = pool.add_new_assets(new_assets).unwrap_err(); assert_eq!( @@ -69,14 +69,15 @@ mod tests { fn test_add_same_new_assets() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100000000, "asset1"), - Coin::new(99999999, "asset2"), + coin(100000000, "asset1"), + coin(99999999, "asset2"), ]), + asset_groups: BTreeMap::new(), }; let err = pool .add_new_assets(Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, "asset5"), - Coin::new(0, "asset5"), + coin(0, "asset5"), + coin(0, "asset5"), ])) .unwrap_err(); assert_eq!( @@ -91,30 +92,31 @@ mod tests { fn test_pool_asset_out_of_range() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100000000, "asset1"), - Coin::new(99999999, "asset2"), + coin(100000000, "asset1"), + coin(99999999, "asset2"), ]), + asset_groups: BTreeMap::new(), }; let new_assets = Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, "asset3"), - Coin::new(0, "asset4"), - Coin::new(0, "asset5"), - Coin::new(0, "asset6"), - Coin::new(0, "asset7"), - Coin::new(0, "asset8"), - Coin::new(0, "asset9"), - Coin::new(0, "asset10"), - Coin::new(0, "asset11"), - Coin::new(0, "asset12"), - Coin::new(0, "asset13"), - Coin::new(0, "asset14"), - Coin::new(0, "asset15"), - Coin::new(0, "asset16"), - Coin::new(0, "asset17"), - Coin::new(0, "asset18"), - Coin::new(0, "asset19"), - Coin::new(0, "asset20"), - Coin::new(0, "asset21"), + coin(0, "asset3"), + coin(0, "asset4"), + coin(0, "asset5"), + coin(0, "asset6"), + coin(0, "asset7"), + coin(0, "asset8"), + coin(0, "asset9"), + coin(0, "asset10"), + coin(0, "asset11"), + coin(0, "asset12"), + coin(0, "asset13"), + coin(0, "asset14"), + coin(0, "asset15"), + coin(0, "asset16"), + coin(0, "asset17"), + coin(0, "asset18"), + coin(0, "asset19"), + coin(0, "asset20"), + coin(0, "asset21"), ]); let err = pool.add_new_assets(new_assets).unwrap_err(); assert_eq!( diff --git a/contracts/transmuter/src/transmuter_pool/asset_group.rs b/contracts/transmuter/src/transmuter_pool/asset_group.rs new file mode 100644 index 0000000..bddb4ff --- /dev/null +++ b/contracts/transmuter/src/transmuter_pool/asset_group.rs @@ -0,0 +1,326 @@ +use std::collections::{BTreeMap, HashSet}; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ensure, Decimal, Uint64}; + +use crate::{corruptable::Corruptable, transmuter_pool::MAX_ASSET_GROUPS, ContractError}; + +use super::TransmuterPool; + +#[cw_serde] +pub struct AssetGroup { + denoms: Vec, + is_corrupted: bool, +} + +impl AssetGroup { + pub fn new(denoms: Vec) -> Self { + Self { + denoms, + is_corrupted: false, + } + } + + pub fn denoms(&self) -> &[String] { + &self.denoms + } + + pub fn into_denoms(self) -> Vec { + self.denoms + } + + pub fn add_denoms(&mut self, denoms: Vec) -> &mut Self { + self.denoms.extend(denoms); + self + } + + pub fn remove_denoms(&mut self, denoms: Vec) -> &mut Self { + self.denoms.retain(|d| !denoms.contains(d)); + self + } +} + +impl Corruptable for AssetGroup { + fn is_corrupted(&self) -> bool { + self.is_corrupted + } + + fn mark_as_corrupted(&mut self) -> &mut Self { + self.is_corrupted = true; + self + } + + fn unmark_as_corrupted(&mut self) -> &mut Self { + self.is_corrupted = false; + self + } +} + +impl TransmuterPool { + pub fn has_asset_group(&self, label: &str) -> bool { + self.asset_groups.contains_key(label) + } + + pub fn mark_corrupted_asset_group(&mut self, label: &str) -> Result<&mut Self, ContractError> { + self.asset_groups + .get_mut(label) + .ok_or_else(|| ContractError::AssetGroupNotFound { + label: label.to_string(), + })? + .mark_as_corrupted(); + + Ok(self) + } + + pub fn unmark_corrupted_asset_group( + &mut self, + label: &str, + ) -> Result<&mut Self, ContractError> { + self.asset_groups + .get_mut(label) + .ok_or_else(|| ContractError::AssetGroupNotFound { + label: label.to_string(), + })? + .unmark_as_corrupted(); + + Ok(self) + } + + pub fn create_asset_group( + &mut self, + label: String, + denoms: Vec, + ) -> Result<&mut Self, ContractError> { + // ensure that asset group does not already exist + ensure!( + !self.asset_groups.contains_key(&label), + ContractError::AssetGroupAlreadyExists { + label: label.clone() + } + ); + + // ensure that asset group label is not empty string + ensure!(!label.is_empty(), ContractError::EmptyAssetGroupLabel {}); + + // ensure that all denoms are valid pool assets and has no duplicated denoms + // ensuring no duplicated denoms also ensures that it's within MAX_POOL_ASSET_DENOMS limit + let mut denoms_set = HashSet::new(); + for denom in &denoms { + ensure!( + self.has_denom(denom), + ContractError::InvalidPoolAssetDenom { + denom: denom.clone() + } + ); + ensure!( + denoms_set.insert(denom.clone()), + ContractError::DuplicatedPoolAssetDenom { + denom: denom.clone() + } + ); + } + + self.asset_groups.insert(label, AssetGroup::new(denoms)); + + ensure!( + Uint64::from(self.asset_groups.len() as u64) <= MAX_ASSET_GROUPS, + ContractError::AssetGroupCountOutOfRange { + max: MAX_ASSET_GROUPS, + actual: Uint64::new(self.asset_groups.len() as u64) + } + ); + + Ok(self) + } + + pub fn remove_asset_group(&mut self, label: &str) -> Result<&mut Self, ContractError> { + ensure!( + self.asset_groups.remove(label).is_some(), + ContractError::AssetGroupNotFound { + label: label.to_string() + } + ); + + Ok(self) + } + + pub fn asset_group_weights(&self) -> Result, ContractError> { + let denom_weights = self.asset_weights()?.unwrap_or_default(); + let mut weights = BTreeMap::new(); + for (label, asset_group) in &self.asset_groups { + let mut group_weight = Decimal::zero(); + for denom in &asset_group.denoms { + let denom_weight = denom_weights + .get(denom) + .copied() + .unwrap_or_else(Decimal::zero); + group_weight = group_weight.checked_add(denom_weight)?; + } + weights.insert(label.to_string(), group_weight); + } + + Ok(weights) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::Uint128; + + use crate::asset::Asset; + + use super::*; + + #[test] + fn test_add_remove_denoms() { + let mut group = AssetGroup::new(vec!["denom1".to_string(), "denom2".to_string()]); + + // Test initial state + assert_eq!(group.denoms(), &["denom1", "denom2"]); + + // Test adding denoms + group.add_denoms(vec!["denom3".to_string(), "denom4".to_string()]); + assert_eq!(group.denoms(), &["denom1", "denom2", "denom3", "denom4"]); + + // Test adding duplicate denom + group.add_denoms(vec!["denom2".to_string(), "denom5".to_string()]); + assert_eq!( + group.denoms(), + &["denom1", "denom2", "denom3", "denom4", "denom2", "denom5"] + ); + + // Test removing denoms + group.remove_denoms(vec!["denom2".to_string(), "denom4".to_string()]); + assert_eq!(group.denoms(), &["denom1", "denom3", "denom5"]); + + // Test removing non-existent denom + group.remove_denoms(vec!["denom6".to_string()]); + assert_eq!(group.denoms(), &["denom1", "denom3", "denom5"]); + } + + #[test] + fn test_mark_unmark_corrupted() { + let mut group = AssetGroup::new(vec!["denom1".to_string(), "denom2".to_string()]); + + // Test initial state + assert!(!group.is_corrupted()); + + // Test marking as corrupted + group.mark_as_corrupted(); + assert!(group.is_corrupted()); + + // Test unmarking as corrupted + group.unmark_as_corrupted(); + assert!(!group.is_corrupted()); + + // Test marking and unmarking multiple times + group.mark_as_corrupted().mark_as_corrupted(); + assert!(group.is_corrupted()); + group.unmark_as_corrupted().unmark_as_corrupted(); + assert!(!group.is_corrupted()); + } + + #[test] + fn test_asset_group_weights() { + let mut pool = TransmuterPool::new(vec![ + Asset::new(Uint128::new(200), "denom1", Uint128::new(2)).unwrap(), + Asset::new(Uint128::new(300), "denom2", Uint128::new(3)).unwrap(), + Asset::new(Uint128::new(500), "denom3", Uint128::new(5)).unwrap(), + ]) + .unwrap(); + + // Test with empty pool + let weights = pool.asset_group_weights().unwrap(); + assert!(weights.is_empty()); + + pool.create_asset_group( + "group1".to_string(), + vec!["denom1".to_string(), "denom2".to_string()], + ) + .unwrap(); + + pool.create_asset_group("group2".to_string(), vec!["denom3".to_string()]) + .unwrap(); + + let weights = pool.asset_group_weights().unwrap(); + assert_eq!(weights.len(), 2); + assert_eq!( + weights.get("group1").unwrap(), + &Decimal::raw(666666666666666666) + ); + assert_eq!( + weights.get("group2").unwrap(), + &Decimal::raw(333333333333333333) + ); + } + + #[test] + fn test_create_asset_group_with_empty_string() { + let mut pool = TransmuterPool::new(vec![ + Asset::new(Uint128::new(100), "denom1", Uint128::new(1)).unwrap(), + Asset::new(Uint128::new(200), "denom2", Uint128::new(1)).unwrap(), + ]) + .unwrap(); + + let err = pool + .create_asset_group("".to_string(), vec!["denom1".to_string()]) + .unwrap_err(); + + assert_eq!(err, ContractError::EmptyAssetGroupLabel {}); + } + + #[test] + fn test_create_asset_group_with_duplicated_denom() { + let mut pool = TransmuterPool::new(vec![ + Asset::new(Uint128::new(100), "denom1", Uint128::new(1)).unwrap(), + Asset::new(Uint128::new(200), "denom2", Uint128::new(1)).unwrap(), + ]) + .unwrap(); + + let err = pool + .create_asset_group( + "group1".to_string(), + vec!["denom1".to_string(), "denom1".to_string()], + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::DuplicatedPoolAssetDenom { + denom: "denom1".to_string() + } + ); + } + + #[test] + fn test_create_asset_group_within_range() { + let mut pool = TransmuterPool::new(vec![ + Asset::new(Uint128::new(100), "denom1", Uint128::new(1)).unwrap(), + Asset::new(Uint128::new(200), "denom2", Uint128::new(1)).unwrap(), + Asset::new(Uint128::new(300), "denom3", Uint128::new(1)).unwrap(), + ]) + .unwrap(); + + // Test creating groups up to the maximum allowed + for i in 1..=MAX_ASSET_GROUPS.u64() { + let group_name = format!("group{}", i); + let result = pool.create_asset_group(group_name.clone(), vec!["denom1".to_string()]); + assert!(result.is_ok(), "Failed to create group {}", i); + } + + // Attempt to create one more group, which should fail + let result = pool.create_asset_group("extra_group".to_string(), vec!["denom1".to_string()]); + assert!( + result.is_err(), + "Should not be able to create group beyond the maximum" + ); + assert!( + matches!( + result.unwrap_err(), + ContractError::AssetGroupCountOutOfRange { max, actual } + if max == MAX_ASSET_GROUPS && actual == MAX_ASSET_GROUPS + Uint64::one() + ), + "Unexpected error when exceeding max asset groups" + ); + } +} diff --git a/contracts/transmuter/src/transmuter_pool/corrupted_assets.rs b/contracts/transmuter/src/transmuter_pool/corrupted_assets.rs index 5dd3200..382125f 100644 --- a/contracts/transmuter/src/transmuter_pool/corrupted_assets.rs +++ b/contracts/transmuter/src/transmuter_pool/corrupted_assets.rs @@ -1,48 +1,44 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; -use cosmwasm_std::{ensure, Decimal}; +use cosmwasm_std::{ensure, Decimal, Uint128}; -use crate::{asset::Asset, ContractError}; - -use super::TransmuterPool; +use super::{asset_group::AssetGroup, TransmuterPool}; +use crate::{asset::Asset, corruptable::Corruptable, scope::Scope, ContractError}; impl TransmuterPool { - pub fn mark_corrupted_assets( - &mut self, - corrupted_denoms: &[String], - ) -> Result<(), ContractError> { - // check if removing_assets are in the pool_assets - for corrupted_denom in corrupted_denoms { - ensure!( - self.has_denom(corrupted_denom), - ContractError::InvalidPoolAssetDenom { - denom: corrupted_denom.to_string() - } - ); + pub fn mark_corrupted_asset(&mut self, corrupted_denom: &str) -> Result<(), ContractError> { + // check if denom is in the pool_assets + ensure!( + self.has_denom(corrupted_denom), + ContractError::InvalidPoolAssetDenom { + denom: corrupted_denom.to_string() + } + ); - self.pool_assets - .iter_mut() - .find(|asset| asset.denom() == corrupted_denom) - .map(|asset| asset.mark_as_corrupted()); + for asset in self.pool_assets.iter_mut() { + if asset.denom() == corrupted_denom { + asset.mark_as_corrupted(); + break; + } } Ok(()) } - pub fn unmark_corrupted_assets(&mut self, denoms: &[String]) -> Result<(), ContractError> { - // check if denoms are of corrupted assets - for uncorrupted_denom in denoms { - ensure!( - self.is_corrupted_asset(uncorrupted_denom), - ContractError::InvalidCorruptedAssetDenom { - denom: uncorrupted_denom.to_string() - } - ); + pub fn unmark_corrupted_asset(&mut self, uncorrupted_denom: &str) -> Result<(), ContractError> { + // check if denom is of corrupted asset + ensure!( + self.is_corrupted_asset(uncorrupted_denom), + ContractError::InvalidCorruptedAssetDenom { + denom: uncorrupted_denom.to_string() + } + ); - self.pool_assets - .iter_mut() - .find(|asset| asset.denom() == uncorrupted_denom) - .map(|asset| asset.unmark_as_corrupted()); + for asset in self.pool_assets.iter_mut() { + if asset.denom() == uncorrupted_denom { + asset.unmark_as_corrupted(); + break; + } } Ok(()) @@ -61,29 +57,33 @@ impl TransmuterPool { .any(|asset| asset.denom() == denom && asset.is_corrupted()) } - pub fn remove_corrupted_asset(&mut self, denom: &str) -> Result<(), ContractError> { + pub fn remove_asset(&mut self, denom: &str) -> Result<(), ContractError> { let asset = self.get_pool_asset_by_denom(denom)?; - // make sure that removing asset is corrupted - ensure!( - asset.is_corrupted(), - ContractError::InvalidCorruptedAssetDenom { - denom: denom.to_string() - } - ); // make sure that removing asset has 0 amount ensure!( asset.amount().is_zero(), - ContractError::InvalidCorruptedAssetRemoval {} + ContractError::InvalidAssetRemoval {} ); self.pool_assets.retain(|asset| asset.denom() != denom); + + // remove asset from asset groups + for label in self.asset_groups.clone().keys() { + let asset_group = self.asset_groups.get_mut(label).unwrap(); + asset_group.remove_denoms(vec![denom.to_string()]); + + if asset_group.denoms().is_empty() { + self.asset_groups.remove(label); + } + } + Ok(()) } - /// Enforce corrupted assets protocol on specific action. This will ensure that amount or weight - /// of corrupted assets will never be increased. - pub fn with_corrupted_asset_protocol(&mut self, action: A) -> Result + /// Enforce corrupted scopes protocol on specific action. This will ensure that amount or weight + /// of corrupted scopes will never be increased. + pub fn with_corrupted_scopes_protocol(&mut self, action: A) -> Result where A: FnOnce(&mut Self) -> Result, { @@ -100,8 +100,7 @@ impl TransmuterPool { } // if total pool value == 0 -> Empty mapping, later unwrap weight will be 0 - let weight_pre_action = self.weights()?.unwrap_or_default(); - let weight_pre_action = weight_pre_action.into_iter().collect::>(); + let weight_pre_action = self.asset_weights()?.unwrap_or_default(); let res = action(self)?; @@ -112,8 +111,7 @@ impl TransmuterPool { .filter(|asset| asset.is_corrupted()); // if total pool value == 0 -> Empty mapping, later unwrap weight will be 0 - let weight_post_action = self.weights()?.unwrap_or_default(); - let weight_post_action = weight_post_action.into_iter().collect::>(); + let weight_post_action = self.asset_weights()?.unwrap_or_default(); for post_action in corrupted_assets_post_action { let denom = post_action.denom().to_string(); @@ -130,20 +128,154 @@ impl TransmuterPool { ensure!( !has_amount_increased && !has_weight_increased, - ContractError::CorruptedAssetRelativelyIncreased { - denom: post_action.denom().to_string() + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::denom(post_action.denom()) } ); } Ok(res) } + + /// Enforce corrupted scopes protocol on specific action. This will ensure that amount or weight + /// of corrupted scopes will never be increased. + pub fn _with_corrupted_scopes_protocol( + &mut self, + asset_groups: BTreeMap, + action: A, + ) -> Result + where + A: FnOnce(&mut Self) -> Result, + { + let corrupted_assets_pre_action = self.get_corrupted_assets(); + let corrupted_asset_groups = self.get_corrupted_asset_groups(asset_groups); + + // early return result without any checks if no corrupted scope + if corrupted_assets_pre_action.is_empty() && corrupted_asset_groups.is_empty() { + return action(self); + } + + let weight_pre_action = self.asset_weights()?.unwrap_or_default(); + let corrupted_asset_groups_state_pre_action = + self.get_corrupted_asset_groups_state(&corrupted_asset_groups, &weight_pre_action)?; + + let res = action(self)?; + + let corrupted_assets_post_action = self.get_corrupted_assets(); + let weight_post_action = self.asset_weights()?.unwrap_or_default(); + let corrupted_asset_groups_state_post_action = + self.get_corrupted_asset_groups_state(&corrupted_asset_groups, &weight_post_action)?; + + self.check_corrupted_assets( + &corrupted_assets_pre_action, + &corrupted_assets_post_action, + &weight_pre_action, + &weight_post_action, + )?; + + self.check_corrupted_asset_groups( + &corrupted_asset_groups_state_pre_action, + &corrupted_asset_groups_state_post_action, + )?; + + Ok(res) + } + + fn get_corrupted_assets(&self) -> BTreeMap { + self.pool_assets + .iter() + .filter(|asset| asset.is_corrupted()) + .map(|asset| (asset.denom().to_string(), asset.clone())) + .collect() + } + + fn get_corrupted_asset_groups( + &self, + asset_groups: BTreeMap, + ) -> BTreeMap { + asset_groups + .into_iter() + .filter(|(_, asset_group)| asset_group.is_corrupted()) + .collect() + } + + /// Get the state of corrupted asset groups. + /// returns map for label -> (amount, weight) for each asset group + fn get_corrupted_asset_groups_state( + &self, + corrupted_asset_groups: &BTreeMap, + weights: &BTreeMap, + ) -> Result, ContractError> { + corrupted_asset_groups + .iter() + .map(|(label, asset_group)| -> Result<_, ContractError> { + let (amount, weight) = asset_group.denoms().iter().try_fold( + (Uint128::zero(), Decimal::zero()), + |(acc_amount, acc_weight), denom| -> Result<_, ContractError> { + let asset = self.get_pool_asset_by_denom(denom)?; + let amount = acc_amount.checked_add(asset.amount())?; + let weight = acc_weight + .checked_add(*weights.get(denom).unwrap_or(&Decimal::zero()))?; + Ok((amount, weight)) + }, + )?; + Ok((label.clone(), (amount, weight))) + }) + .collect() + } + + fn check_corrupted_assets( + &self, + pre_action: &BTreeMap, + post_action: &BTreeMap, + weight_pre_action: &BTreeMap, + weight_post_action: &BTreeMap, + ) -> Result<(), ContractError> { + let zero_dec = Decimal::zero(); + for (denom, post_asset) in post_action { + let pre_asset = pre_action.get(denom).ok_or(ContractError::Never)?; + let weight_pre = weight_pre_action.get(denom).unwrap_or(&zero_dec); + let weight_post = weight_post_action.get(denom).unwrap_or(&zero_dec); + + let has_amount_increased = pre_asset.amount() < post_asset.amount(); + let has_weight_increased = weight_pre < weight_post; + + ensure!( + !has_amount_increased && !has_weight_increased, + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::denom(post_asset.denom()) + } + ); + } + Ok(()) + } + + fn check_corrupted_asset_groups( + &self, + pre_action: &BTreeMap, + post_action: &BTreeMap, + ) -> Result<(), ContractError> { + for (label, (pre_amount, pre_weight)) in pre_action { + let (post_amount, post_weight) = post_action.get(label).ok_or(ContractError::Never)?; + + let has_amount_increased = pre_amount < post_amount; + let has_weight_increased = pre_weight < post_weight; + + ensure!( + !has_amount_increased && !has_weight_increased, + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::asset_group(label) + } + ); + } + Ok(()) + } } #[cfg(test)] mod tests { use crate::asset::Asset; - use cosmwasm_std::{Coin, Uint128}; + use cosmwasm_std::{coin, Uint128}; use super::*; @@ -151,17 +283,16 @@ mod tests { fn test_mark_corrupted_assets() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100000000, "asset1"), - Coin::new(99999999, "asset2"), - Coin::new(1, "asset3"), - Coin::new(0, "asset4"), + coin(100000000, "asset1"), + coin(99999999, "asset2"), + coin(1, "asset3"), + coin(0, "asset4"), ]), + asset_groups: BTreeMap::new(), }; // remove asset that is not in the pool - let err = pool - .mark_corrupted_assets(&["asset5".to_string()]) - .unwrap_err(); + let err = pool.mark_corrupted_asset("asset5").unwrap_err(); assert_eq!( err, ContractError::InvalidPoolAssetDenom { @@ -169,9 +300,7 @@ mod tests { } ); - let err = pool - .mark_corrupted_assets(&["asset1".to_string(), "assetx".to_string()]) - .unwrap_err(); + let err = pool.mark_corrupted_asset("assetx").unwrap_err(); assert_eq!( err, ContractError::InvalidPoolAssetDenom { @@ -179,14 +308,14 @@ mod tests { } ); - pool.mark_corrupted_assets(&["asset1".to_string()]).unwrap(); + pool.mark_corrupted_asset("asset1").unwrap(); assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100000000, "asset1"), - Coin::new(99999999, "asset2"), - Coin::new(1, "asset3"), - Coin::new(0, "asset4"), + coin(100000000, "asset1"), + coin(99999999, "asset2"), + coin(1, "asset3"), + coin(0, "asset4"), ]) .into_iter() .map(|asset| { @@ -206,16 +335,16 @@ mod tests { ] ); - pool.mark_corrupted_assets(&["asset2".to_string(), "asset3".to_string()]) - .unwrap(); + pool.mark_corrupted_asset("asset2").unwrap(); + pool.mark_corrupted_asset("asset3").unwrap(); assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100000000, "asset1"), - Coin::new(99999999, "asset2"), - Coin::new(1, "asset3"), - Coin::new(0, "asset4"), + coin(100000000, "asset1"), + coin(99999999, "asset2"), + coin(1, "asset3"), + coin(0, "asset4"), ]) .into_iter() .map(|asset| { @@ -243,100 +372,103 @@ mod tests { fn test_enforce_corrupted_asset_protocol() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(99999999, "asset1"), - Coin::new(100000000, "asset2"), - Coin::new(1, "asset3"), - Coin::new(0, "asset4"), + coin(99999999, "asset1"), + coin(100000000, "asset2"), + coin(1, "asset3"), + coin(0, "asset4"), ]), + asset_groups: BTreeMap::new(), }; - pool.mark_corrupted_assets(&["asset1".to_string()]).unwrap(); + pool.mark_corrupted_asset("asset1").unwrap(); // increase corrupted asset directly let err = pool - .with_corrupted_asset_protocol(|pool| { - pool.pool_assets - .iter_mut() - .find(|asset| asset.denom() == "asset1") - .map(|asset| asset.increase_amount(Uint128::new(1)).unwrap()) - .unwrap(); + ._with_corrupted_scopes_protocol(BTreeMap::new(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset1" { + asset.increase_amount(Uint128::new(1)).unwrap(); + } + } Ok(()) }) .unwrap_err(); assert_eq!( err, - ContractError::CorruptedAssetRelativelyIncreased { - denom: "asset1".to_string() + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::denom("asset1") } ); // decrease other asset -> increase corrupted asset weight let err = pool - .with_corrupted_asset_protocol(|pool| { - pool.pool_assets - .iter_mut() - .find(|asset| asset.denom() == "asset2") - .map(|asset| asset.decrease_amount(Uint128::new(1)).unwrap()) - .unwrap(); + ._with_corrupted_scopes_protocol(BTreeMap::new(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset2" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + } Ok(()) }) .unwrap_err(); assert_eq!( err, - ContractError::CorruptedAssetRelativelyIncreased { - denom: "asset1".to_string() + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::denom("asset1") } ); // decrease both corrupted and other asset with different weight - let err = pool.with_corrupted_asset_protocol(|pool| { - pool.pool_assets - .iter_mut() - .find(|asset| asset.denom() == "asset1") - .map(|asset| asset.decrease_amount(Uint128::new(1)).unwrap()) - .unwrap(); - pool.pool_assets - .iter_mut() - .find(|asset| asset.denom() == "asset2") - .map(|asset| asset.decrease_amount(Uint128::new(2)).unwrap()) - .unwrap(); - Ok(()) - }); + let err = pool + ._with_corrupted_scopes_protocol(BTreeMap::new(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset1" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + + if asset.denom() == "asset2" { + asset.decrease_amount(Uint128::new(2)).unwrap(); + } + } + + Ok(()) + }) + .unwrap_err(); assert_eq!( - err.unwrap_err(), - ContractError::CorruptedAssetRelativelyIncreased { - denom: "asset1".to_string() + err, + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::denom("asset1") } ); // reset the pool because pure rust test will not reset state on error let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(99999999, "asset1"), - Coin::new(100000000, "asset2"), - Coin::new(1, "asset3"), - Coin::new(0, "asset4"), + coin(99999999, "asset1"), + coin(100000000, "asset2"), + coin(1, "asset3"), + coin(0, "asset4"), ]), + asset_groups: BTreeMap::new(), }; - pool.mark_corrupted_assets(&["asset1".to_string()]).unwrap(); + pool.mark_corrupted_asset("asset1").unwrap(); // decrease both corrupted and other asset with slightly more weight on the corrupted asset // requires slightly more weight to work due to rounding error - pool.with_corrupted_asset_protocol(|pool| { - pool.pool_assets - .iter_mut() - .find(|asset| asset.denom() == "asset1") - .map(|asset| asset.decrease_amount(Uint128::new(2)).unwrap()) - .unwrap(); - pool.pool_assets - .iter_mut() - .find(|asset| asset.denom() == "asset2") - .map(|asset| asset.decrease_amount(Uint128::new(1)).unwrap()) - .unwrap(); + pool._with_corrupted_scopes_protocol(BTreeMap::new(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset1" { + asset.decrease_amount(Uint128::new(2)).unwrap(); + } + + if asset.denom() == "asset2" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + } Ok(()) }) .unwrap(); @@ -344,10 +476,10 @@ mod tests { assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(99999997, "asset1"), - Coin::new(99999999, "asset2"), - Coin::new(1, "asset3"), - Coin::new(0, "asset4"), + coin(99999997, "asset1"), + coin(99999999, "asset2"), + coin(1, "asset3"), + coin(0, "asset4"), ]) .into_iter() .map(|asset| { @@ -359,5 +491,206 @@ mod tests { }) .collect::>() ); + + let mut asset_groups = BTreeMap::from_iter(vec![( + "group1".to_string(), + AssetGroup::new(vec!["asset2".to_string(), "asset3".to_string()]), + )]); + + // increase asset in non-corrupted asset group + pool._with_corrupted_scopes_protocol(asset_groups.clone(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset2" { + asset.increase_amount(Uint128::new(1)).unwrap(); + } + } + + Ok(()) + }) + .unwrap(); + + asset_groups.get_mut("group1").unwrap().mark_as_corrupted(); + + // increase asset in corrupted asset group + let err = pool + ._with_corrupted_scopes_protocol(asset_groups.clone(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset2" { + asset.increase_amount(Uint128::new(1)).unwrap(); + } + } + Ok(()) + }) + .unwrap_err(); + + assert_eq!( + err, + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::asset_group("group1") + } + ); + + // increase all assets except asset1 + let err = pool + ._with_corrupted_scopes_protocol(asset_groups.clone(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() != "asset1" { + asset.increase_amount(Uint128::new(1)).unwrap(); + } + } + + Ok(()) + }) + .unwrap_err(); + + assert_eq!( + err, + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::asset_group("group1") + } + ); + + pool.unmark_corrupted_asset("asset1").unwrap(); + + // decrease asset 2 + pool._with_corrupted_scopes_protocol(asset_groups.clone(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset2" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + } + Ok(()) + }) + .unwrap(); + + // decrease asset 3 + pool._with_corrupted_scopes_protocol(asset_groups.clone(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset3" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + } + Ok(()) + }) + .unwrap(); + + // decrease asset 2 and 3 + pool._with_corrupted_scopes_protocol(asset_groups.clone(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset2" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + + if asset.denom() == "asset3" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + } + Ok(()) + }) + .unwrap(); + + // decrease asset 4 should fail + let err = pool + ._with_corrupted_scopes_protocol(asset_groups.clone(), |pool| { + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset4" { + asset.decrease_amount(Uint128::new(1)).unwrap(); + } + } + Ok(()) + }) + .unwrap_err(); + + assert_eq!( + err, + ContractError::CorruptedScopeRelativelyIncreased { + scope: Scope::asset_group("group1") + } + ); + } + + #[test] + fn test_remove_corrupted_asset() { + let mut pool = TransmuterPool { + pool_assets: Asset::unchecked_equal_assets_from_coins(&[ + coin(100, "asset1"), + coin(200, "asset2"), + coin(300, "asset3"), + ]), + asset_groups: BTreeMap::from_iter(vec![( + "group1".to_string(), + AssetGroup::new(vec!["asset1".to_string(), "asset2".to_string()]), + )]), + }; + + // Mark asset2 as corrupted + pool.mark_corrupted_asset("asset2").unwrap(); + + // Attempt to remove asset2 with non-zero amount (should fail) + let err = pool.remove_asset("asset2").unwrap_err(); + assert_eq!(err, ContractError::InvalidAssetRemoval {}); + + // Decrease amount of asset2 to zero + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset2" { + asset.decrease_amount(Uint128::new(200)).unwrap(); + } + } + + // Remove corrupted asset2 (should succeed) + pool.remove_asset("asset2").unwrap(); + + assert_eq!( + pool.asset_groups, + BTreeMap::from_iter(vec![( + "group1".to_string(), + AssetGroup::new(vec!["asset1".to_string()]), + )]) + ); + + // Verify asset2 is removed + assert_eq!( + pool.pool_assets, + vec![ + Asset::unchecked(Uint128::new(100), "asset1", Uint128::one()), + Asset::unchecked(Uint128::new(300), "asset3", Uint128::one()), + ] + ); + + // Attempt to remove non-existent asset (should fail) + let err = pool.remove_asset("non_existent").unwrap_err(); + assert_eq!( + err, + ContractError::InvalidTransmuteDenom { + denom: "non_existent".to_string(), + expected_denom: vec!["asset1".to_string(), "asset3".to_string()] + } + ); + + // Mark asset1 as corrupted + pool.mark_corrupted_asset("asset1").unwrap(); + + // Decrease amount of asset1 to zero + for asset in pool.pool_assets.iter_mut() { + if asset.denom() == "asset1" { + asset.decrease_amount(Uint128::new(100)).unwrap(); + } + } + + // Remove corrupted asset1 + pool.remove_asset("asset1").unwrap(); + + // Verify asset1 is removed + assert_eq!( + pool.pool_assets, + vec![Asset::unchecked( + Uint128::new(300), + "asset3", + Uint128::one() + ),] + ); + + // Verify asset groups are updated + assert!(pool.asset_groups.is_empty()); } } diff --git a/contracts/transmuter/src/transmuter_pool/exit_pool.rs b/contracts/transmuter/src/transmuter_pool/exit_pool.rs index 56114d5..6273dac 100644 --- a/contracts/transmuter/src/transmuter_pool/exit_pool.rs +++ b/contracts/transmuter/src/transmuter_pool/exit_pool.rs @@ -6,7 +6,7 @@ use super::TransmuterPool; impl TransmuterPool { pub fn exit_pool(&mut self, tokens_out: &[Coin]) -> Result<(), ContractError> { - self.with_corrupted_asset_protocol(|pool| pool.unchecked_exit_pool(tokens_out)) + self.with_corrupted_scopes_protocol(|pool| pool.unchecked_exit_pool(tokens_out)) } pub fn unchecked_exit_pool(&mut self, tokens_out: &[Coin]) -> Result<(), ContractError> { @@ -44,6 +44,10 @@ impl TransmuterPool { #[cfg(test)] mod tests { + use std::collections::BTreeMap; + + use cosmwasm_std::coin; + use crate::asset::Asset; use super::*; @@ -54,45 +58,49 @@ mod tests { fn test_exit_pool_succeed_when_has_enough_coins_in_pool() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100_000, ETH_USDC), - Coin::new(100_000, COSMOS_USDC), + coin(100_000, ETH_USDC), + coin(100_000, COSMOS_USDC), ]), + asset_groups: BTreeMap::new(), }; // exit pool with first token - pool.exit_pool(&[Coin::new(10_000, ETH_USDC)]).unwrap(); + pool.exit_pool(&[coin(10_000, ETH_USDC)]).unwrap(); assert_eq!( pool, TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(90_000, ETH_USDC), - Coin::new(100_000, COSMOS_USDC), - ]) + coin(90_000, ETH_USDC), + coin(100_000, COSMOS_USDC), + ]), + asset_groups: BTreeMap::new(), } ); // exit pool with second token - pool.exit_pool(&[Coin::new(10_000, COSMOS_USDC)]).unwrap(); + pool.exit_pool(&[coin(10_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool, TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(90_000, ETH_USDC), - Coin::new(90_000, COSMOS_USDC), - ]) + coin(90_000, ETH_USDC), + coin(90_000, COSMOS_USDC), + ]), + asset_groups: BTreeMap::new(), } ); // exit pool with both tokens - pool.exit_pool(&[Coin::new(90_000, ETH_USDC), Coin::new(90_000, COSMOS_USDC)]) + pool.exit_pool(&[coin(90_000, ETH_USDC), coin(90_000, COSMOS_USDC)]) .unwrap(); assert_eq!( pool, TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, ETH_USDC), - Coin::new(0, COSMOS_USDC), - ]) + coin(0, ETH_USDC), + coin(0, COSMOS_USDC), + ]), + asset_groups: BTreeMap::new(), } ); } @@ -101,13 +109,14 @@ mod tests { fn test_exit_pool_fail_when_token_denom_is_invalid() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100_000, ETH_USDC), - Coin::new(100_000, COSMOS_USDC), + coin(100_000, ETH_USDC), + coin(100_000, COSMOS_USDC), ]), + asset_groups: BTreeMap::new(), }; // exit pool with invalid token - let err = pool.exit_pool(&[Coin::new(10_000, "invalid")]).unwrap_err(); + let err = pool.exit_pool(&[coin(10_000, "invalid")]).unwrap_err(); assert_eq!( err, ContractError::InvalidPoolAssetDenom { @@ -117,7 +126,7 @@ mod tests { // exit pool with both valid and invalid token let err = pool - .exit_pool(&[Coin::new(10_000, ETH_USDC), Coin::new(10_000, "invalid2")]) + .exit_pool(&[coin(10_000, ETH_USDC), coin(10_000, "invalid2")]) .unwrap_err(); assert_eq!( err, @@ -131,44 +140,40 @@ mod tests { fn test_exit_pool_fail_when_not_enough_token() { let mut pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(100_000, ETH_USDC), - Coin::new(100_000, COSMOS_USDC), + coin(100_000, ETH_USDC), + coin(100_000, COSMOS_USDC), ]), + asset_groups: BTreeMap::new(), }; - let err = pool.exit_pool(&[Coin::new(100_001, ETH_USDC)]).unwrap_err(); + let err = pool.exit_pool(&[coin(100_001, ETH_USDC)]).unwrap_err(); assert_eq!( err, ContractError::InsufficientPoolAsset { - required: Coin::new(100_001, ETH_USDC), - available: Coin::new(100_000, ETH_USDC) + required: coin(100_001, ETH_USDC), + available: coin(100_000, ETH_USDC) } ); - let err = pool - .exit_pool(&[Coin::new(110_000, COSMOS_USDC)]) - .unwrap_err(); + let err = pool.exit_pool(&[coin(110_000, COSMOS_USDC)]).unwrap_err(); assert_eq!( err, ContractError::InsufficientPoolAsset { - required: Coin::new(110_000, COSMOS_USDC), - available: Coin::new(100_000, COSMOS_USDC) + required: coin(110_000, COSMOS_USDC), + available: coin(100_000, COSMOS_USDC) } ); let err = pool - .exit_pool(&[ - Coin::new(110_000, ETH_USDC), - Coin::new(110_000, COSMOS_USDC), - ]) + .exit_pool(&[coin(110_000, ETH_USDC), coin(110_000, COSMOS_USDC)]) .unwrap_err(); assert_eq!( err, ContractError::InsufficientPoolAsset { - required: Coin::new(110_000, ETH_USDC), - available: Coin::new(100_000, ETH_USDC) + required: coin(110_000, ETH_USDC), + available: coin(100_000, ETH_USDC) } ); } diff --git a/contracts/transmuter/src/transmuter_pool/join_pool.rs b/contracts/transmuter/src/transmuter_pool/join_pool.rs index 4c993b8..0e4ffe3 100644 --- a/contracts/transmuter/src/transmuter_pool/join_pool.rs +++ b/contracts/transmuter/src/transmuter_pool/join_pool.rs @@ -6,7 +6,7 @@ use super::TransmuterPool; impl TransmuterPool { pub fn join_pool(&mut self, tokens_in: &[Coin]) -> Result<(), ContractError> { - self.with_corrupted_asset_protocol(|pool| pool.unchecked_join_pool(tokens_in)) + self.with_corrupted_scopes_protocol(|pool| pool.unchecked_join_pool(tokens_in)) } fn unchecked_join_pool(&mut self, tokens_in: &[Coin]) -> Result<(), ContractError> { @@ -43,7 +43,7 @@ impl TransmuterPool { #[cfg(test)] mod tests { - use cosmwasm_std::{OverflowError, OverflowOperation}; + use cosmwasm_std::{coin, OverflowError, OverflowOperation}; use crate::asset::Asset; @@ -57,33 +57,30 @@ mod tests { TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); // join pool - pool.join_pool(&[Coin::new(1000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(1000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.pool_assets, - Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, ETH_USDC), - Coin::new(1000, COSMOS_USDC) - ]) + Asset::unchecked_equal_assets_from_coins(&[coin(0, ETH_USDC), coin(1000, COSMOS_USDC)]) ); // join pool when not empty - pool.join_pool(&[Coin::new(20000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(20000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, ETH_USDC), - Coin::new(21000, COSMOS_USDC) + coin(0, ETH_USDC), + coin(21000, COSMOS_USDC) ]) ); // join pool multiple tokens at once - pool.join_pool(&[Coin::new(1000, ETH_USDC), Coin::new(1000, COSMOS_USDC)]) + pool.join_pool(&[coin(1000, ETH_USDC), coin(1000, COSMOS_USDC)]) .unwrap(); assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(1000, ETH_USDC), - Coin::new(22000, COSMOS_USDC) + coin(1000, ETH_USDC), + coin(22000, COSMOS_USDC) ]) ); } @@ -94,7 +91,7 @@ mod tests { TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); assert_eq!( - pool.join_pool(&[Coin::new(1000, "urandom")]).unwrap_err(), + pool.join_pool(&[coin(1000, "urandom")]).unwrap_err(), ContractError::InvalidJoinPoolDenom { denom: "urandom".to_string(), expected_denom: vec![ETH_USDC.to_string(), COSMOS_USDC.to_string()] @@ -103,7 +100,7 @@ mod tests { ); assert_eq!( - pool.join_pool(&[Coin::new(1000, "urandom"), Coin::new(10000, COSMOS_USDC)]) + pool.join_pool(&[coin(1000, "urandom"), coin(10000, COSMOS_USDC)]) .unwrap_err(), ContractError::InvalidJoinPoolDenom { denom: "urandom".to_string(), @@ -120,17 +117,12 @@ mod tests { assert_eq!( { - pool.join_pool(&[Coin::new(1, COSMOS_USDC)]).unwrap(); - pool.join_pool(&[Coin::new(u128::MAX, COSMOS_USDC)]) - .unwrap_err() + pool.join_pool(&[coin(1, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(u128::MAX, COSMOS_USDC)]).unwrap_err() }, - ContractError::Std(StdError::Overflow { - source: OverflowError { - operation: OverflowOperation::Add, - operand1: 1.to_string(), - operand2: u128::MAX.to_string() - } - }), + ContractError::Std(StdError::overflow(OverflowError::new( + OverflowOperation::Add + ))), "join pool overflow" ); } diff --git a/contracts/transmuter/src/transmuter_pool/mod.rs b/contracts/transmuter/src/transmuter_pool/mod.rs index 6205219..7a2d110 100644 --- a/contracts/transmuter/src/transmuter_pool/mod.rs +++ b/contracts/transmuter/src/transmuter_pool/mod.rs @@ -1,4 +1,5 @@ mod add_new_assets; +mod asset_group; mod corrupted_assets; mod exit_pool; mod has_denom; @@ -6,13 +7,14 @@ mod join_pool; mod transmute; mod weight; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Coin, Uint128, Uint64}; use crate::{asset::Asset, ContractError}; +pub use asset_group::AssetGroup; pub use transmute::AmountConstraint; /// Minimum number of pool assets. @@ -23,14 +25,22 @@ const MIN_POOL_ASSET_DENOMS: Uint64 = Uint64::new(1u64); /// prevent the contract from running out of gas when iterating const MAX_POOL_ASSET_DENOMS: Uint64 = Uint64::new(20u64); +/// Maximum number of asset groups allowed in a pool. +/// This limit helps prevent excessive gas consumption when iterating over groups. +const MAX_ASSET_GROUPS: Uint64 = Uint64::new(10u64); + #[cw_serde] pub struct TransmuterPool { pub pool_assets: Vec, + pub asset_groups: BTreeMap, } impl TransmuterPool { pub fn new(pool_assets: Vec) -> Result { - let pool = Self { pool_assets }; + let pool = Self { + pool_assets, + asset_groups: BTreeMap::new(), + }; pool.ensure_no_duplicated_denom()?; pool.ensure_pool_asset_count_within_range()?; @@ -111,7 +121,10 @@ impl TransmuterPool { }) .collect::, ContractError>>()?; - Ok(Self { pool_assets }) + Ok(Self { + pool_assets, + ..self + }) } } @@ -138,6 +151,7 @@ mod tests { TransmuterPool::new(Asset::unchecked_equal_assets(&["a"])).unwrap(), TransmuterPool { pool_assets: Asset::unchecked_equal_assets(&["a"]), + asset_groups: BTreeMap::new(), } ); @@ -146,6 +160,7 @@ mod tests { TransmuterPool::new(Asset::unchecked_equal_assets(&["a", "b"])).unwrap(), TransmuterPool { pool_assets: Asset::unchecked_equal_assets(&["a", "b"]), + asset_groups: BTreeMap::new(), } ); @@ -156,7 +171,8 @@ mod tests { assert_eq!( TransmuterPool::new(assets.clone()).unwrap(), TransmuterPool { - pool_assets: assets + pool_assets: assets, + asset_groups: BTreeMap::new(), } ); diff --git a/contracts/transmuter/src/transmuter_pool/transmute.rs b/contracts/transmuter/src/transmuter_pool/transmute.rs index 25f8f5e..5db272b 100644 --- a/contracts/transmuter/src/transmuter_pool/transmute.rs +++ b/contracts/transmuter/src/transmuter_pool/transmute.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{ensure, Coin, Uint128}; +use cosmwasm_std::{coin, ensure, Coin, Uint128}; use crate::{ asset::{convert_amount, Asset, Rounding}, @@ -30,7 +30,7 @@ impl TransmuterPool { token_in_denom: &str, token_out_denom: &str, ) -> Result<(Coin, Coin), ContractError> { - self.with_corrupted_asset_protocol(|pool| { + self.with_corrupted_scopes_protocol(|pool| { pool.unchecked_transmute(amount_constraint, token_in_denom, token_out_denom) }) } @@ -57,8 +57,8 @@ impl TransmuterPool { &amount_constraint, )?; - let token_in = Coin::new(token_in_amount.u128(), token_in_denom); - let token_out = Coin::new(token_out_amount.u128(), token_out_denom); + let token_in = coin(token_in_amount.u128(), token_in_denom); + let token_out = coin(token_out_amount.u128(), token_out_denom); // ensure there is enough token_out_denom in the pool ensure!( @@ -150,7 +150,7 @@ impl TransmuterPool { #[cfg(test)] mod tests { - use cosmwasm_std::{testing::mock_dependencies, Uint128}; + use cosmwasm_std::{coin, testing::mock_dependencies, Uint128}; use crate::asset::{Asset, AssetConfig}; @@ -166,7 +166,7 @@ mod tests { let mut pool = TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); - pool.join_pool(&[Coin::new(70_000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(70_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.transmute( AmountConstraint::exact_in(70_000u128), @@ -174,10 +174,10 @@ mod tests { COSMOS_USDC ) .unwrap(), - (Coin::new(70_000, ETH_USDC), Coin::new(70_000, COSMOS_USDC)) + (coin(70_000, ETH_USDC), coin(70_000, COSMOS_USDC)) ); - pool.join_pool(&[Coin::new(100_000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(100_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.transmute( AmountConstraint::exact_in(60_000u128), @@ -185,7 +185,7 @@ mod tests { COSMOS_USDC ) .unwrap(), - (Coin::new(60_000, ETH_USDC), Coin::new(60_000, COSMOS_USDC)) + (coin(60_000, ETH_USDC), coin(60_000, COSMOS_USDC)) ); assert_eq!( pool.transmute( @@ -194,7 +194,7 @@ mod tests { COSMOS_USDC ) .unwrap(), - (Coin::new(20_000, ETH_USDC), Coin::new(20_000, COSMOS_USDC)) + (coin(20_000, ETH_USDC), coin(20_000, COSMOS_USDC)) ); assert_eq!( pool.transmute( @@ -203,19 +203,19 @@ mod tests { COSMOS_USDC ) .unwrap(), - (Coin::new(20_000, ETH_USDC), Coin::new(20_000, COSMOS_USDC)) + (coin(20_000, ETH_USDC), coin(20_000, COSMOS_USDC)) ); assert_eq!( pool.transmute(AmountConstraint::exact_in(0u128), ETH_USDC, COSMOS_USDC) .unwrap(), - (Coin::new(0, ETH_USDC), Coin::new(0, COSMOS_USDC)) + (coin(0, ETH_USDC), coin(0, COSMOS_USDC)) ); assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(170_000, ETH_USDC), - Coin::new(0, COSMOS_USDC), + coin(170_000, ETH_USDC), + coin(0, COSMOS_USDC), ]) ); @@ -226,17 +226,14 @@ mod tests { ETH_USDC ) .unwrap(), - ( - Coin::new(100_000, COSMOS_USDC), - Coin::new(100_000, ETH_USDC) - ) + (coin(100_000, COSMOS_USDC), coin(100_000, ETH_USDC)) ); assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(70_000, ETH_USDC), - Coin::new(100_000, COSMOS_USDC) + coin(70_000, ETH_USDC), + coin(100_000, COSMOS_USDC) ]) ); } @@ -246,7 +243,7 @@ mod tests { let mut pool = TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); - pool.join_pool(&[Coin::new(170_000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(170_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.transmute( @@ -255,20 +252,20 @@ mod tests { COSMOS_USDC ) .unwrap(), - (Coin::new(70_000, ETH_USDC), Coin::new(70_000, COSMOS_USDC)) + (coin(70_000, ETH_USDC), coin(70_000, COSMOS_USDC)) ); assert_eq!( pool.transmute(AmountConstraint::exact_out(0u128), ETH_USDC, COSMOS_USDC) .unwrap(), - (Coin::new(0, ETH_USDC), Coin::new(0, COSMOS_USDC)) + (coin(0, ETH_USDC), coin(0, COSMOS_USDC)) ); assert_eq!( pool.pool_assets, Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(70_000, ETH_USDC), - Coin::new(100_000, COSMOS_USDC), + coin(70_000, ETH_USDC), + coin(100_000, COSMOS_USDC), ]) ); } @@ -278,7 +275,7 @@ mod tests { let mut pool = TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); - pool.join_pool(&[Coin::new(70_000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(70_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.transmute( @@ -287,10 +284,7 @@ mod tests { COSMOS_USDC ) .unwrap(), - ( - Coin::new(70_000, COSMOS_USDC), - Coin::new(70_000, COSMOS_USDC) - ) + (coin(70_000, COSMOS_USDC), coin(70_000, COSMOS_USDC)) ); } @@ -299,7 +293,7 @@ mod tests { let mut pool = TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); - pool.join_pool(&[Coin::new(70_000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(70_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.transmute( AmountConstraint::exact_in(70_001u128), @@ -308,8 +302,8 @@ mod tests { ) .unwrap_err(), ContractError::InsufficientPoolAsset { - required: Coin::new(70_001, COSMOS_USDC), - available: Coin::new(70_000, COSMOS_USDC) + required: coin(70_001, COSMOS_USDC), + available: coin(70_000, COSMOS_USDC) } ); } @@ -319,7 +313,7 @@ mod tests { let mut pool = TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); - pool.join_pool(&[Coin::new(70_000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(70_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.transmute( AmountConstraint::exact_in(70_000u128), @@ -339,7 +333,7 @@ mod tests { let mut pool = TransmuterPool::new(Asset::unchecked_equal_assets(&[ETH_USDC, COSMOS_USDC])).unwrap(); - pool.join_pool(&[Coin::new(70_000, COSMOS_USDC)]).unwrap(); + pool.join_pool(&[coin(70_000, COSMOS_USDC)]).unwrap(); assert_eq!( pool.transmute( AmountConstraint::exact_in(70_000u128), @@ -357,11 +351,11 @@ mod tests { #[test] fn test_transmute_with_normalization_factor_10_power_n() { let mut deps = mock_dependencies(); - deps.querier.update_balance( + deps.querier.bank.update_balance( "creator", vec![ - Coin::new(70_000 * 10u128.pow(14), NBTC_SAT), - Coin::new(70_000 * 10u128.pow(8), WBTC_SAT), + coin(70_000 * 10u128.pow(14), NBTC_SAT), + coin(70_000 * 10u128.pow(8), WBTC_SAT), ], ); @@ -381,7 +375,7 @@ mod tests { ]) .unwrap(); - pool.join_pool(&[Coin::new(70_000 * 10u128.pow(14), NBTC_SAT)]) + pool.join_pool(&[coin(70_000 * 10u128.pow(14), NBTC_SAT)]) .unwrap(); assert_eq!( @@ -392,8 +386,8 @@ mod tests { ) .unwrap(), ( - Coin::new(70_000 * 10u128.pow(8), WBTC_SAT), - Coin::new(70_000 * 10u128.pow(14), NBTC_SAT) + coin(70_000 * 10u128.pow(8), WBTC_SAT), + coin(70_000 * 10u128.pow(14), NBTC_SAT) ) ); @@ -412,11 +406,11 @@ mod tests { fn test_transmute_exact_in_round_down_token_out() { let mut deps = mock_dependencies(); // a:b = 1:3 - deps.querier.update_balance( + deps.querier.bank.update_balance( "creator", vec![ - Coin::new(70_000 * 3 * 10u128.pow(14), "ua"), - Coin::new(70_000 * 10u128.pow(8), "ub"), + coin(70_000 * 3 * 10u128.pow(14), "ua"), + coin(70_000 * 10u128.pow(8), "ub"), ], ); @@ -436,7 +430,7 @@ mod tests { ]) .unwrap(); - pool.join_pool(&[Coin::new(70_000 * 10u128.pow(8), "ub")]) + pool.join_pool(&[coin(70_000 * 10u128.pow(8), "ub")]) .unwrap(); // Transmute with ExactIn, where the output needs to be rounded down @@ -452,8 +446,8 @@ mod tests { assert_eq!( result, ( - Coin::new(3 * 10u128.pow(14) + 1, "ua"), - Coin::new(10u128.pow(8), "ub") + coin(3 * 10u128.pow(14) + 1, "ua"), + coin(10u128.pow(8), "ub") ) ); @@ -469,8 +463,8 @@ mod tests { assert_eq!( result, ( - Coin::new(3 * 10u128.pow(14) - 1, "ua"), - Coin::new(10u128.pow(8) - 1, "ub") + coin(3 * 10u128.pow(14) - 1, "ua"), + coin(10u128.pow(8) - 1, "ub") ) ); } @@ -479,11 +473,11 @@ mod tests { fn test_transmute_exact_out_round_up_token_in() { let mut deps = mock_dependencies(); // a:b = 1:3 - deps.querier.update_balance( + deps.querier.bank.update_balance( "creator", vec![ - Coin::new(70_000 * 3 * 10u128.pow(14), "ua"), - Coin::new(70_000 * 10u128.pow(8), "ub"), + coin(70_000 * 3 * 10u128.pow(14), "ua"), + coin(70_000 * 10u128.pow(8), "ub"), ], ); @@ -503,7 +497,7 @@ mod tests { ]) .unwrap(); - pool.join_pool(&[Coin::new(70_000 * 3 * 10u128.pow(14), "ua")]) + pool.join_pool(&[coin(70_000 * 3 * 10u128.pow(14), "ua")]) .unwrap(); // Transmute with ExactOut, where the input needs to be rounded up @@ -519,8 +513,8 @@ mod tests { assert_eq!( result, ( - Coin::new(10u128.pow(8), "ub"), - Coin::new(3 * 10u128.pow(14) - 1, "ua") + coin(10u128.pow(8), "ub"), + coin(3 * 10u128.pow(14) - 1, "ua") ) ); @@ -547,8 +541,8 @@ mod tests { assert_eq!( result, ( - Coin::new(10u128.pow(8) + 1, "ub"), - Coin::new(3 * 10u128.pow(14) + 1, "ua") + coin(10u128.pow(8) + 1, "ub"), + coin(3 * 10u128.pow(14) + 1, "ua") ) ); diff --git a/contracts/transmuter/src/transmuter_pool/weight.rs b/contracts/transmuter/src/transmuter_pool/weight.rs index 5125ca8..8cb16d2 100644 --- a/contracts/transmuter/src/transmuter_pool/weight.rs +++ b/contracts/transmuter/src/transmuter_pool/weight.rs @@ -20,7 +20,7 @@ impl TransmuterPool { /// /// If total pool asset amount is zero, returns None to signify that /// it makes no sense to calculate ratios, but not an error. - pub fn weights(&self) -> Result>, ContractError> { + pub fn asset_weights(&self) -> Result>, ContractError> { let std_norm_factor = lcm_from_iter( self.pool_assets .iter() @@ -51,10 +51,6 @@ impl TransmuterPool { Ok(Some(ratios)) } - pub fn weights_map(&self) -> Result, ContractError> { - Ok(self.weights()?.unwrap_or_default().into_iter().collect()) - } - fn normalized_asset_values( &self, std_norm_factor: Uint128, @@ -78,7 +74,7 @@ impl TransmuterPool { #[cfg(test)] mod tests { use crate::asset::Asset; - use cosmwasm_std::Coin; + use cosmwasm_std::coin; use rstest::rstest; use std::str::FromStr; @@ -171,22 +167,26 @@ mod tests { .into_iter() .map(|asset| asset.unwrap()) .collect(); - let pool = TransmuterPool { pool_assets }; + let pool = TransmuterPool { + pool_assets, + asset_groups: BTreeMap::new(), + }; - let ratios = pool.weights().unwrap(); - assert_eq!(ratios, Some(expected)); + let ratios = pool.asset_weights().unwrap(); + assert_eq!(ratios, Some(expected.into_iter().collect())); } #[test] fn test_all_ratios_when_total_pool_assets_is_zero() { let pool = TransmuterPool { pool_assets: Asset::unchecked_equal_assets_from_coins(&[ - Coin::new(0, "axlusdc"), - Coin::new(0, "whusdc"), + coin(0, "axlusdc"), + coin(0, "whusdc"), ]), + asset_groups: BTreeMap::new(), }; - let ratios = pool.weights().unwrap(); + let ratios = pool.asset_weights().unwrap(); assert_eq!(ratios, None); } } diff --git a/contracts/transmuter/testdata/transmuter_v3_2.wasm b/contracts/transmuter/testdata/transmuter_v3_2.wasm new file mode 100644 index 0000000..90525a0 Binary files /dev/null and b/contracts/transmuter/testdata/transmuter_v3_2.wasm differ diff --git a/simulation/rebalance_incentive.py b/simulation/rebalance_incentive.py index c2e0490..c6a9d70 100644 --- a/simulation/rebalance_incentive.py +++ b/simulation/rebalance_incentive.py @@ -164,7 +164,6 @@ def project_point(m): Lower $𝑘$ values provide a more gradual and linear impact, leading to a more moderate fee structure. ''' -# TODO: observation, the more d (= starting point further from midpoint) with the same change in d, the less fee is taken? # we want the opposite, the further away from midpoint, it scales up the fee. # Potentially, Fee / (1-d)??????