diff --git a/Cargo.lock b/Cargo.lock index e8daeb4..ae7ff81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ checksum = "cb5f1dee23caf80904249463cc4493b6789c2250f88c8f8d9160de5c6099bfe7" [[package]] name = "anyhow" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "assert_matches" @@ -151,21 +151,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.1.34" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" -dependencies = [ - "shlex", -] +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" [[package]] name = "cfg-if" @@ -194,18 +191,18 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", @@ -245,9 +242,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.8" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58535cbcd599b3c193e3967c8292fe1dbbb5de7c2a2d87380661091dd4744044" +checksum = "eba94b9f3fb79b9f1101b3e0c61995a557828e2c0d3f5579c1d0bfbea333c19e" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -266,9 +263,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.8" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e07de16c800ac82fd188d055ecdb923ead0cf33960d3350089260bb982c09f" +checksum = "d67457e4acb04e738788d3489e343957455df2c4643f2b53050eb052ca631d19" dependencies = [ "syn 1.0.109", ] @@ -287,11 +284,11 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "1.5.8" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d388adfa9cb449557a92e9318121ac1a481fc4f599213b03a5b62699b403b4" +checksum = "13bf06bf1c7ea737f6b3d955d9cabeb8cbbe4dcb8dea392e30f6fab4493a4b7a" dependencies = [ - "cosmwasm-schema-derive 1.5.8", + "cosmwasm-schema-derive 1.5.9", "schemars 0.8.21", "serde", "serde_json", @@ -310,9 +307,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.8" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2411b389e56e6484f81ba955b758d02522d620c98fc960c4bd2251d48b7aa19f" +checksum = "077fe378f16b54e3d0a57adb3f39a65bcf7bdbda6a5eade2f8ba7755c2fb1250" dependencies = [ "proc-macro2", "quote", @@ -339,15 +336,15 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.8" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c21fde95ccd20044a23c0ac6fd8c941f3e8c158169dc94b5aa6491a2d9551a8d" +checksum = "3745e9fd9aad96236c3d6f1a6d844249ed3bb6b92fcdae16d8fe067c7a5121e8" dependencies = [ "base64 0.21.7", "bech32 0.9.1", "bnum", - "cosmwasm-crypto 1.5.8", - "cosmwasm-derive 1.5.8", + "cosmwasm-crypto 1.5.9", + "cosmwasm-derive 1.5.9", "derivative", "forward_ref", "hex", @@ -365,15 +362,15 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "serde", ] [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -448,10 +445,10 @@ dependencies = [ "secret-cosmwasm-std", "secret-cosmwasm-storage", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", - "snip20-reference-impl", + "snip20-base", "thiserror", ] @@ -461,7 +458,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", "schemars 0.8.21", @@ -475,7 +472,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "468b8f2696f625c8e15b5468f9420c8eabfaf23cb4fd7e6c660fc7e0cc8d77b8" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cosmwasm-storage", "cw-core-interface", "cw-core-macros", @@ -496,7 +493,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c93e684945473777ebed2bcaf9f0af2291653f79d5c81774c6826350ba6d88de" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cw-core-macros", "cw2 0.13.4", "schemars 0.8.21", @@ -521,9 +518,9 @@ dependencies = [ "cosmwasm-schema 1.1.11", "secret-cosmwasm-std", "secret-cosmwasm-storage", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-toolkit", - "snip20-reference-impl", + "snip20-base", "thiserror", ] @@ -539,13 +536,13 @@ dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip20-stake", "thiserror", ] @@ -591,7 +588,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b854833e07c557dee02d1b61a21bb0731743bb2e3bbdc3e446a0d8a38af40ec4" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cosmwasm-storage", "cw-storage-plus 0.13.4", "serde", @@ -602,7 +599,7 @@ name = "cw-paginate-storage" version = "2.4.0" dependencies = [ "secret-cosmwasm-std", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "serde", @@ -620,12 +617,12 @@ dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", - "snip20-reference-impl", + "snip20-base", "thiserror", "wynd-utils", ] @@ -645,7 +642,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "schemars 0.8.21", "serde", ] @@ -656,7 +653,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "schemars 0.8.21", "serde", ] @@ -670,11 +667,11 @@ dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", - "snip20-reference-impl", + "snip20-base", "thiserror", ] @@ -684,7 +681,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "schemars 0.8.21", "serde", "thiserror", @@ -696,8 +693,8 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ - "cosmwasm-schema 1.5.8", - "cosmwasm-std 1.5.8", + "cosmwasm-schema 1.5.9", + "cosmwasm-std 1.5.9", "cw2 1.1.2", "schemars 0.8.21", "semver", @@ -720,11 +717,11 @@ dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", - "snip20-reference-impl", + "snip20-base", "thiserror", "wynd-utils", ] @@ -762,7 +759,7 @@ dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", @@ -775,7 +772,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cw-storage-plus 0.13.4", "schemars 0.8.21", "serde", @@ -787,8 +784,8 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ - "cosmwasm-schema 1.5.8", - "cosmwasm-std 1.5.8", + "cosmwasm-schema 1.5.9", + "cosmwasm-std 1.5.9", "cw-storage-plus 1.2.0", "schemars 0.8.21", "semver", @@ -802,7 +799,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cw-utils 0.13.4", "schemars 0.8.21", "serde", @@ -814,8 +811,8 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" dependencies = [ - "cosmwasm-schema 1.5.8", - "cosmwasm-std 1.5.8", + "cosmwasm-schema 1.5.9", + "cosmwasm-std 1.5.9", "cw-utils 1.0.3", "schemars 0.8.21", "serde", @@ -827,7 +824,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", "cw2 0.13.4", @@ -843,7 +840,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26f0d51ce27a97b51f66d737183845bc6d82f46f4b246dc959d1265d86906ccc" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cosmwasm-storage", "cw-controllers", "cw-storage-plus 0.13.4", @@ -862,8 +859,8 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2967fbd073d4b626dd9e7148e05a84a3bebd9794e71342e12351110ffbb12395" dependencies = [ - "cosmwasm-schema 1.5.8", - "cosmwasm-std 1.5.8", + "cosmwasm-schema 1.5.9", + "cosmwasm-std 1.5.9", "cw-utils 1.0.3", "cw20 1.1.2", "schemars 0.8.21", @@ -908,7 +905,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "035818368a74c07dd9ed5c5a93340199ba251530162010b9f34c3809e3b97df1" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cw-utils 0.13.4", "schemars 0.8.21", "serde", @@ -934,13 +931,13 @@ dependencies = [ "secret-cosmwasm-std", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip721-reference-impl", "thiserror", ] @@ -1007,12 +1004,12 @@ dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "thiserror", ] @@ -1024,6 +1021,7 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-paginate-storage 2.4.0", + "cw4", "cw4-group", "dao-dao-core", "dao-hooks", @@ -1033,13 +1031,16 @@ dependencies = [ "dao-voting 2.4.0", "dao-voting-cw4", "dao-voting-snip20-staked", + "query_auth 0.1.0", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "shade-protocol", + "snip20-base", + "snip721-reference-impl", "thiserror", ] @@ -1063,12 +1064,12 @@ dependencies = [ "query_auth 0.1.0", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip721-reference-impl", ] @@ -1080,11 +1081,12 @@ dependencies = [ "cw-denom", "cw-hooks", "dao-interface", + "dao-utils", "dao-voting 2.4.0", "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -1112,10 +1114,10 @@ dependencies = [ "query_auth 0.1.0", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip721-reference-impl", ] @@ -1137,10 +1139,10 @@ dependencies = [ "query_auth 0.1.0", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip721-reference-impl", ] @@ -1155,6 +1157,7 @@ dependencies = [ "dao-dao-core", "dao-dao-macros", "dao-interface", + "dao-utils", "dao-voting 2.4.0", "dao-voting-cw4", "query_auth 0.1.0", @@ -1162,7 +1165,7 @@ dependencies = [ "secret-cosmwasm-std", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -1201,7 +1204,7 @@ dependencies = [ "secret-cosmwasm-storage", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -1241,13 +1244,13 @@ dependencies = [ "secret-cosmwasm-storage", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip20-stake", "snip721-reference-impl", "thiserror", @@ -1262,7 +1265,7 @@ dependencies = [ "dao-interface", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "thiserror", @@ -1306,12 +1309,12 @@ dependencies = [ "rand", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "serde_json", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip20-stake", "snip721-reference-impl", "snip721-roles", @@ -1335,6 +1338,7 @@ dependencies = [ "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", + "snip20-base", "snip721-roles-impl", "thiserror", ] @@ -1345,7 +1349,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442d770933e3b3ecab4cfb4d6e9d054082b007d35fda3cf0c3d3ddd1cfa91782" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "schemars 0.8.21", "serde", "thiserror", @@ -1386,7 +1390,7 @@ dependencies = [ "secret-cosmwasm-storage", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -1404,12 +1408,12 @@ dependencies = [ "dao-interface", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "thiserror", ] @@ -1429,13 +1433,13 @@ dependencies = [ "secret-cosmwasm-storage", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip20-stake", "thiserror", ] @@ -1457,7 +1461,7 @@ dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -1480,13 +1484,14 @@ dependencies = [ "dao-dao-macros", "dao-hooks", "dao-interface", + "dao-utils", "dao-voting 2.4.0", "query_auth 0.1.0", "schemars 0.8.21", "secret-cosmwasm-std", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -1512,6 +1517,7 @@ dependencies = [ "dao-hooks", "dao-interface", "dao-proposal-single", + "dao-utils", "dao-voting 2.4.0", "prost 0.12.6", "query_auth 0.1.0", @@ -1520,7 +1526,7 @@ dependencies = [ "secret-cosmwasm-storage", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -1799,6 +1805,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1828,16 +1843,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1869,9 +1885,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.161" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "log" @@ -1999,9 +2015,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2049,7 +2065,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2067,7 +2083,7 @@ version = "0.1.0" source = "git+https://github.com/securesecrets/query-authentication?branch=cosmwasm_v1_upgrade#024c13c08c8e4d25d890de2418fb1ae2c45425e9" dependencies = [ "bech32 0.8.1", - "cosmwasm-schema 1.5.8", + "cosmwasm-schema 1.5.9", "remain", "ripemd160", "schemars 0.8.21", @@ -2162,7 +2178,7 @@ checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2262,7 +2278,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2331,7 +2347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e4393b01aa6587007161a6bb193859deaa8165ab06c8a35f253d329ff99e4d" dependencies = [ "base64 0.13.1", - "cosmwasm-derive 1.5.8", + "cosmwasm-derive 1.5.9", "derivative", "forward_ref", "hex", @@ -2372,7 +2388,7 @@ name = "secret-cw2" version = "1.0.1" source = "git+https://github.com/securesecrets/secret-plus-utils?branch=main#ca5c1704375c622398811b6339711b52b8dd3ad1" dependencies = [ - "cosmwasm-schema 1.5.8", + "cosmwasm-schema 1.5.9", "schemars 0.8.21", "secret-cosmwasm-std", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -2383,8 +2399,9 @@ dependencies = [ [[package]] name = "secret-multi-test" version = "0.13.4" -source = "git+https://github.com/securesecrets/secret-plus-utils?branch=main#ca5c1704375c622398811b6339711b52b8dd3ad1" +source = "git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix#f98171e2f4dc3fdadac8fe4fac6d668b4d0276c9" dependencies = [ + "anybuf", "anyhow", "derivative", "itertools 0.10.5", @@ -2392,8 +2409,8 @@ dependencies = [ "prost 0.9.0", "schemars 0.8.21", "secret-cosmwasm-std", - "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", - "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-storage-plus 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", + "secret-utils 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "serde", "thiserror", ] @@ -2427,6 +2444,17 @@ dependencies = [ "serde", ] +[[package]] +name = "secret-storage-plus" +version = "0.13.4" +source = "git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix#f98171e2f4dc3fdadac8fe4fac6d668b4d0276c9" +dependencies = [ + "bincode2", + "schemars 0.8.21", + "secret-cosmwasm-std", + "serde", +] + [[package]] name = "secret-storage-plus" version = "0.13.4" @@ -2440,11 +2468,11 @@ dependencies = [ [[package]] name = "secret-toolkit" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338c972c0a98de51ccbb859312eb7672bc64b9050b086f058748ba26a509edbb" +checksum = "6c4dcbfc5ef7c201479020f54a9dab1fe11cb64cd3c4d875a91dd491b265ed01" dependencies = [ - "secret-toolkit-crypto 0.10.0", + "secret-toolkit-crypto 0.10.2", "secret-toolkit-permit", "secret-toolkit-serialization", "secret-toolkit-snip20", @@ -2467,10 +2495,12 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003d7d5772c67f2240b7f298f96eb73a8a501916fe18c1d730ebfd591bf7e519" +checksum = "2cead50c5e6bd178f8f1e7f7b6d3a14e86177d42b24f91a85543f1e1ae12a873" dependencies = [ + "cc", + "hkdf", "rand_chacha 0.3.1", "rand_core 0.6.4", "secp256k1", @@ -2480,24 +2510,24 @@ dependencies = [ [[package]] name = "secret-toolkit-permit" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4330571400b5959450fa37040609e6804a147d83f606783506bc2275f1527712" +checksum = "ca321f0c41ac6479f8988c88e1ace9aee2d566a8889f6b7cf45f60e741285cd6" dependencies = [ "bech32 0.9.1", "remain", "ripemd", "schemars 0.8.21", "secret-cosmwasm-std", - "secret-toolkit-crypto 0.10.0", + "secret-toolkit-crypto 0.10.2", "serde", ] [[package]] name = "secret-toolkit-serialization" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "890adaeaa710f9f7068a807eb1553edc8c30ce9907290895c9097dd642fc613b" +checksum = "2cd631b147752674789691135f536965f9118a1a69a9c6522064b71c777b8fa7" dependencies = [ "bincode2", "schemars 0.8.21", @@ -2507,9 +2537,9 @@ dependencies = [ [[package]] name = "secret-toolkit-snip20" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8144a11df9a75adf42acd645f938eb2c1ab508d6672033eb84b1e672247d2f05" +checksum = "1b9a75e321b8f6115c4b9045e100c34950186276815701197257361b0acfaacf" dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", @@ -2519,9 +2549,9 @@ dependencies = [ [[package]] name = "secret-toolkit-snip721" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2ab35fd2a52306f87ab8dceab75254cc1b87c95c43acf9e30c09372f0ee971" +checksum = "97d9f85a0dfa7d7c33b490c90419e1c9457df7bc5c0f75ff4035f45d9ad24d22" dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", @@ -2531,9 +2561,9 @@ dependencies = [ [[package]] name = "secret-toolkit-storage" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e8c5418af3e7ae1d1331c383b32d56c74a340dbc3b972d53555a768698f2a3" +checksum = "9b1a38dc584baa4f2c9aa52844fd3d06258bdae22fcd8cbe635ffab480ba53a0" dependencies = [ "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -2543,9 +2573,9 @@ dependencies = [ [[package]] name = "secret-toolkit-utils" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f1cba2e70fd701e3dfc6072807c02eeeb9776bee49e346a9c7745d84ff40c8" +checksum = "ee464d607765ab148164b154eba2ee649bf77f2855c5c2691342f426c263704f" dependencies = [ "schemars 0.8.21", "secret-cosmwasm-std", @@ -2555,15 +2585,15 @@ dependencies = [ [[package]] name = "secret-toolkit-viewing-key" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d89a0b69fa9b12735a612fa30e6e7e48130943982f1783b7ddd5c46ed09e921" +checksum = "350ab608f01fe4b61b74ccf205b251513edffe06fc432f7ecc4c762027b2bf5d" dependencies = [ "base64 0.21.7", "schemars 0.8.21", "secret-cosmwasm-std", "secret-cosmwasm-storage", - "secret-toolkit-crypto 0.10.0", + "secret-toolkit-crypto 0.10.2", "secret-toolkit-utils", "serde", "subtle", @@ -2580,6 +2610,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "secret-utils" +version = "0.13.4" +source = "git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix#f98171e2f4dc3fdadac8fe4fac6d668b4d0276c9" +dependencies = [ + "schemars 0.8.21", + "secret-cosmwasm-std", + "serde", + "thiserror", +] + [[package]] name = "secret-utils" version = "0.13.4" @@ -2593,15 +2634,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -2635,13 +2676,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2663,14 +2704,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -2723,7 +2764,7 @@ dependencies = [ "chrono", "const_format", "contract-derive", - "cosmwasm-schema 1.5.8", + "cosmwasm-schema 1.5.9", "query-authentication", "rand_chacha 0.2.2", "rand_core 0.5.1", @@ -2741,12 +2782,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signature" version = "1.6.4" @@ -2789,7 +2824,7 @@ dependencies = [ ] [[package]] -name = "snip20-reference-impl" +name = "snip20-base" version = "1.0.0" dependencies = [ "base64 0.21.7", @@ -2828,7 +2863,7 @@ dependencies = [ "secret-cosmwasm-storage", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -2836,7 +2871,7 @@ dependencies = [ "sha2 0.9.9", "shade-protocol", "snafu", - "snip20-reference-impl", + "snip20-base", "subtle", "thiserror", ] @@ -2857,13 +2892,13 @@ dependencies = [ "secret-cosmwasm-storage", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip20-stake", "stake-cw20-external-rewards", "thiserror", @@ -2881,13 +2916,13 @@ dependencies = [ "secret-cosmwasm-std", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "serde", "shade-protocol", - "snip20-reference-impl", + "snip20-base", "snip20-stake", "stake-cw20-reward-distributor", "thiserror", @@ -2910,7 +2945,7 @@ version = "1.0.0" dependencies = [ "base64 0.21.7", "bincode2", - "cosmwasm-schema 1.5.8", + "cosmwasm-schema 1.5.9", "dao-snip721-extensions", "primitive-types", "schemars 0.8.21", @@ -2934,7 +2969,7 @@ dependencies = [ "secret-cosmwasm-std", "secret-cw-controllers", "secret-cw2", - "secret-multi-test 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", + "secret-multi-test 0.13.4 (git+https://github.com/kent-3/secret-plus-utils?branch=protobuf-fix)", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", "secret-toolkit", "secret-utils 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?branch=main)", @@ -2987,7 +3022,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c9bbc1e4b7a932957a05a76921015a849b234c3f25e59fe1fd0d2eab71654bc" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cosmwasm-storage", "cw-controllers", "cw-storage-plus 0.13.4", @@ -3007,7 +3042,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4260ff7aec6dddb43cb5f1104ef5cebe2787853bc83af9172ce5b828b577c4c5" dependencies = [ - "cosmwasm-std 1.5.8", + "cosmwasm-std 1.5.9", "cosmwasm-storage", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", @@ -3073,9 +3108,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -3097,27 +3132,27 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.36", + "time 0.3.37", ] [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3133,9 +3168,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "num-conv", @@ -3152,9 +3187,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -3180,9 +3215,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-xid" @@ -3210,9 +3245,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -3221,24 +3256,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3246,22 +3280,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "winapi" @@ -3315,7 +3349,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6494435..ee0c18c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ exclude = ["ci/configs/", "wasmvm/libwasmvm"] members = [ "./contracts/dao-dao-core", "./contracts/external/cw-admin-factory", - "./contracts/external/snip20-reference-impl", + "./contracts/external/snip20-base", "./contracts/external/snip721-reference-impl", "./contracts/external/snip721-roles/", "./contracts/external/snip721-roles-impl/", @@ -136,10 +136,9 @@ secret-toolkit = { version = "0.10.0", default-features = false, features = [ secret-cw-controllers = { path = "./packages/controllers/",default-features = false } secret-storage-plus = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4", branch = "main"} -secret-multi-test = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4", branch = "main"} +secret-multi-test = { git = "https://github.com/kent-3/secret-plus-utils", branch = "protobuf-fix"} secret-utils = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4", branch = "main" } secret-cw2 = { git = "https://github.com/securesecrets/secret-plus-utils", version = "1.0.1", branch = "main"} - cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11", features = [ "stargate", "staking", @@ -152,7 +151,7 @@ schemars = {version = "0.8.12",default-features = false} serde = { version = "1.0.158", default-features = false, features = ["derive"] } thiserror = { version = "1.0.21" ,default-features = false} cosmwasm-schema = { git = "https://github.com/scrtlabs/cosmwasm/", branch = "secret",default-features = false } -snip20-reference-impl = { path = "./contracts/external/snip20-reference-impl/",default-features = false } +snip20-base = { path = "./contracts/external/snip20-base",default-features = false } snip721-reference-impl = { path = "./contracts/external/snip721-reference-impl/" ,default-features = false} snip721-roles-impl = { path = "./contracts/external/snip721-roles-impl/" ,default-features = false} cosmos-sdk-proto = { version = "0.20.0", default-features = false } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..426ad9e --- /dev/null +++ b/Makefile @@ -0,0 +1,79 @@ +# .PHONY: compile +# compile: +# docker run --rm -v .:/code \ +# --mount type=volume,source="optimizer_cache",target=/target \ +# --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ +# cosmwasm/optimizer:0.16.0 + +.PHONY: check +check: + cargo check + +.PHONY: clippy +clippy: + cargo clippy + +PHONY: test +test: unit-test + +.PHONY: unit-test +unit-test: + cargo unit-test + +# This is a local build with debug-prints activated. Debug prints only show up +# in the local development chain (see the `start-server` command below) +# and mainnet won't accept contracts built with the feature enabled. +.PHONY: build _build +build: _build compress-wasm +_build: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --features="debug-print" + +# This is a build suitable for uploading to mainnet. +# Calls to `debug_print` get removed by the compiler. +.PHONY: build-mainnet _build-mainnet +build-mainnet: _build-mainnet compress-wasm +_build-mainnet: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + +# like build-mainnet, but slower and more deterministic +.PHONY: build-mainnet-reproducible +build-mainnet-reproducible: + docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + mr7uca/wasm-contract-optimizer:0.0.10 + +.PHONY: compress-wasm +compress-wasm: + cp ./target/wasm32-unknown-unknown/release/*.wasm ./contract.wasm + @## The following line is not necessary, may work only on linux (extra size optimization) + @# wasm-opt -Os ./contract.wasm -o ./contract.wasm + cat ./contract.wasm | gzip -9 > ./contract.wasm.gz + +.PHONY: schema +schema: + cargo run --example schema + +# Run local development chain with four funded accounts (named a, b, c, and d) +.PHONY: start-server +start-server: # CTRL+C to stop + docker run -it --rm \ + -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ + -v "$$(pwd)":/root/code \ + --name localsecret ghcr.io/scrtlabs/localsecret:v1.6.0 + +# This relies on running `start-server` in another console +# You can run other commands on the secretcli inside the dev image +# by using `docker exec localsecret secretcli`. +.PHONY: store-contract-local +store-contract-local: + docker exec localsecret secretcli tx compute store -y --from a --gas auto --gas-prices 0.0125uscrt --gas-adjustment 1.3 -y -b sync /root/code/optimized-wasm/factory.wasm.gz + +.PHONY: integration-test +integration-test: + npx ts-node tests/integration.ts + +.PHONY: clean +clean: + cargo clean + -rm -f ./contract.wasm ./contract.wasm.gz \ No newline at end of file diff --git a/contracts/dao-dao-core/Cargo.toml b/contracts/dao-dao-core/Cargo.toml index d953c97..6518d44 100644 --- a/contracts/dao-dao-core/Cargo.toml +++ b/contracts/dao-dao-core/Cargo.toml @@ -28,7 +28,7 @@ dao-dao-macros = { workspace = true } cw-paginate-storage = { workspace = true } cw-core-v1 = { workspace = true, features = ["library"] } secret-toolkit = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } snip721-reference-impl = { workspace = true } schemars = { workspace = true } serde = { workspace = true } @@ -37,13 +37,12 @@ shade-protocol ={ workspace = true } dao-utils = {workspace = true} cw4 ={ workspace = true } query_auth ={ workspace = true } -dao-voting-cw4 = { workspace = true } cw4-group = { workspace = true } dao-proposal-sudo ={ workspace = true } -dao-voting-snip20-balance ={ workspace = true } [dev-dependencies] secret-multi-test = { workspace = true, features = ["stargate"] } # dao-proposal-sudo = { workspace = true } -# dao-voting-snip20-balance = { workspace = true } +dao-voting-snip20-balance = { workspace = true } +dao-voting-cw4 = { workspace = true } diff --git a/contracts/dao-dao-core/src/contract.rs b/contracts/dao-dao-core/src/contract.rs index e827352..7f71e22 100644 --- a/contracts/dao-dao-core/src/contract.rs +++ b/contracts/dao-dao-core/src/contract.rs @@ -1,8 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - from_binary, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, - Response, StdError, StdResult, SubMsg, SubMsgResult, Uint128, + from_binary, from_slice, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, StdError, StdResult, SubMsg, SubMsgResult, Uint128 }; use dao_interface::replies::parse_reply_address_from_event; use dao_interface::state::AnyContractInfo; @@ -21,6 +20,7 @@ use dao_interface::{ }; use dao_utils::msg::GroupContract; use dao_utils::msg::NftRolesContract; +use dao_utils::query::get_contract_code_hash; use secret_cw2::{get_contract_version, set_contract_version, ContractVersion}; use secret_toolkit::utils::InitCallback; use secret_toolkit::{serialization::Json, storage::Keymap, utils::HandleCallback}; @@ -28,13 +28,13 @@ use secret_utils::Duration; use shade_protocol::basic_staking::Auth; use shade_protocol::utils::asset::RawContract; use shade_protocol::Contract; -use snip20_reference_impl::msg::ExecuteAnswer; +use snip20_base::msg::ExecuteAnswer; use crate::query_auth_init::QueryAuthInstantiateMsg; use crate::state::{ ACTIVE_PROPOSAL_MODULE_COUNT, ADMIN, CONFIG, ITEMS, NOMINATED_ADMIN, PAUSED, PROPOSAL_MODULES, - QUERY_AUTH, REPLY_IDS, SNIP20_CODE_HASH, SNIP20_LIST, SNIP721_CODE_HASH, SNIP721_LIST, - SUBDAO_LIST, TOKEN_VIEWING_KEY, TOTAL_PROPOSAL_MODULE_COUNT, VOTING_MODULE, + QUERY_AUTH, REPLY_IDS, SNIP20_LIST, SNIP721_LIST, SUBDAO_LIST, TOKEN_VIEWING_KEY, + TOTAL_PROPOSAL_MODULE_COUNT, VOTING_MODULE, }; use crate::{error::ContractError, snip20_msg}; @@ -79,7 +79,6 @@ pub fn instantiate( ReplyEvent::InstantiateQueryAuth { voting_module_instantiate_info: msg.voting_module_instantiate_info.clone(), proposal_modules_instantiate_info: msg.proposal_modules_instantiate_info, - code_hash: msg.query_auth_code_hash.clone(), }, )?; @@ -108,8 +107,6 @@ pub fn instantiate( TOTAL_PROPOSAL_MODULE_COUNT.save(deps.storage, &0)?; ACTIVE_PROPOSAL_MODULE_COUNT.save(deps.storage, &0)?; - SNIP20_CODE_HASH.save(deps.storage, &msg.snip20_code_hash)?; - SNIP721_CODE_HASH.save(deps.storage, &msg.snip721_code_hash)?; Ok(Response::new() .add_attribute("action", "instantiate") @@ -342,12 +339,7 @@ pub fn execute_update_voting_module( } let wasm = module.clone().to_cosmos_msg(env.contract.address); - let reply_id = REPLY_IDS.add_event( - deps.storage, - ReplyEvent::VotingModuleInstantiate { - code_hash: module.code_hash, - }, - )?; + let reply_id = REPLY_IDS.add_event(deps.storage, ReplyEvent::VotingModuleInstantiate {})?; let submessage = SubMsg::reply_on_success(wasm, reply_id); Ok(Response::default() @@ -400,12 +392,7 @@ pub fn execute_update_proposal_modules( .map(|info| { let wasm = info.clone().into_wasm_msg(env.contract.address.clone()); let reply_id = REPLY_IDS - .add_event( - deps.storage, - ReplyEvent::ProposalModuleInstantiate { - code_hash: info.code_hash, - }, - ) + .add_event(deps.storage, ReplyEvent::ProposalModuleInstantiate {}) .unwrap(); SubMsg::reply_on_success(wasm, reply_id) }) @@ -462,8 +449,8 @@ pub fn execute_update_snip20_list( let viewing_key = TOKEN_VIEWING_KEY .get(deps.storage, addr) .unwrap_or_default(); - let snip20_code_hash = SNIP20_CODE_HASH.load(deps.storage)?; - let _info: snip20_reference_impl::msg::QueryAnswer = deps.querier.query_wasm_smart( + let snip20_code_hash = get_contract_code_hash(deps.querier, addr.to_string())?; + let _info: snip20_base::msg::QueryAnswer = deps.querier.query_wasm_smart( snip20_code_hash, addr, &secret_toolkit::snip20::QueryMsg::Balance { @@ -487,7 +474,7 @@ pub fn execute_update_snip721_list( return Err(ContractError::Unauthorized {}); } do_update_addr_list(deps, &SNIP721_LIST, to_add, to_remove, |addr, deps| { - let snip721_code_hash = SNIP721_CODE_HASH.load(deps.storage)?; + let snip721_code_hash = get_contract_code_hash(deps.querier, addr.to_string())?; let _info: secret_toolkit::snip721::query::ContractInfo = deps.querier.query_wasm_smart( snip721_code_hash, addr, @@ -567,8 +554,7 @@ pub fn execute_receive_snip20( sender: Addr, _wrapper: Snip20ReceiveMsg, ) -> Result { - println!("here"); - let code_hash = SNIP20_CODE_HASH.load(deps.storage)?; + let code_hash = get_contract_code_hash(deps.querier, sender.to_string())?; let viewing_key = TOKEN_VIEWING_KEY .get(deps.storage, &sender) .unwrap_or_default(); @@ -994,21 +980,21 @@ pub fn query_cw20_balances( let balances: StdResult> = res .into_iter() .map(|addr| { - let snip20_code_hash = SNIP20_CODE_HASH.load(deps.storage)?; + let snip20_code_hash = get_contract_code_hash(deps.querier, addr.clone())?; let viewing_key = TOKEN_VIEWING_KEY .get(deps.storage, &deps.api.addr_validate(&addr)?) .unwrap_or_default(); let mut balance_amount = Uint128::zero(); - let balance: snip20_reference_impl::msg::QueryAnswer = deps.querier.query_wasm_smart( + let balance: snip20_base::msg::QueryAnswer = deps.querier.query_wasm_smart( snip20_code_hash.clone(), addr.clone(), - &snip20_reference_impl::msg::QueryMsg::Balance { + &snip20_base::msg::QueryMsg::Balance { address: env.contract.address.to_string(), key: viewing_key, }, )?; match balance { - snip20_reference_impl::msg::QueryAnswer::Balance { amount } => { + snip20_base::msg::QueryAnswer::Balance { amount } => { balance_amount = amount; } _ => (), @@ -1101,10 +1087,11 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result { let reply_event = REPLY_IDS.get_event(deps.storage, msg.id)?; match reply_event { - ReplyEvent::ProposalModuleInstantiate { code_hash } => match msg.result { + ReplyEvent::ProposalModuleInstantiate {} => match msg.result { SubMsgResult::Err(err) => Err(ContractError::Std(StdError::GenericErr { msg: err })), SubMsgResult::Ok(res) => { let address = parse_reply_address_from_event(res.clone()); + let code_hash = get_contract_code_hash(deps.querier, address.clone())?; let total_module_count = TOTAL_PROPOSAL_MODULE_COUNT.load(deps.storage)?; @@ -1140,11 +1127,11 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result match msg.result { + ReplyEvent::VotingModuleInstantiate {} => match msg.result { SubMsgResult::Err(err) => Err(ContractError::Std(StdError::GenericErr { msg: err })), SubMsgResult::Ok(res) => { let address = parse_reply_address_from_event(res.clone()); - + let code_hash = get_contract_code_hash(deps.querier, address.clone())?; let voting_module = VotingModuleInfo { code_hash, addr: deps.api.addr_validate(&address.clone())?, @@ -1168,24 +1155,27 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result match msg.result { SubMsgResult::Ok(res) => { let token_addr = deps.api.addr_validate(&contract_address)?; - let data: snip20_reference_impl::msg::ExecuteAnswer = - from_binary(&res.data.unwrap())?; - let mut viewing_key = String::new(); - if let ExecuteAnswer::CreateViewingKey { key } = data { - viewing_key = key; - } - TOKEN_VIEWING_KEY.insert(deps.storage, &token_addr, &viewing_key)?; + let raw_data = res.data.ok_or_else(|| StdError::generic_err("No data returned"))?; + + // Extract JSON payload starting from the '{' character + let json_data = &raw_data.0[raw_data.0.iter().position(|&b| b == b'{').unwrap_or(0)..]; + let key = match from_slice::(json_data)? { + ExecuteAnswer::CreateViewingKey { key } => key, + _ => return Err(ContractError::Std(StdError::generic_err("Unexpected response type"))), + }; + + TOKEN_VIEWING_KEY.insert(deps.storage, &token_addr, &key)?; Ok(Response::new().add_attribute("action", "create_token_viewing_key")) } - SubMsgResult::Err(_) => Err(ContractError::TokenExecuteError {}), + SubMsgResult::Err(_) => Err(ContractError::Std(StdError::generic_err("Token execution error"))), }, ReplyEvent::InstantiateQueryAuth { voting_module_instantiate_info, proposal_modules_instantiate_info, - code_hash, } => match msg.result { SubMsgResult::Ok(res) => { let address = parse_reply_address_from_event(res); + let code_hash = get_contract_code_hash(deps.querier, address.clone())?; let msg = update_query_auth( voting_module_instantiate_info.clone(), RawContract { @@ -1194,12 +1184,8 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result = SubMsg::reply_on_success(msg, reply_id); let proposal_module_msgs: Vec> = proposal_modules_instantiate_info @@ -1216,12 +1202,7 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result(&info.msg) { + if let Ok(msg) = from_binary::(&info.msg) { return msg.to_cosmos_msg(Some(admin), info.label, info.code_id, info.code_hash, None); } diff --git a/contracts/dao-dao-core/src/state.rs b/contracts/dao-dao-core/src/state.rs index 493a11d..f592524 100644 --- a/contracts/dao-dao-core/src/state.rs +++ b/contracts/dao-dao-core/src/state.rs @@ -66,6 +66,3 @@ pub const TOKEN_VIEWING_KEY: Keymap = Keymap::new(b"token_vi pub const REPLY_IDS: ReplyIds = ReplyIds::new(b"reply_ids", b"reply_ids_count"); pub const QUERY_AUTH: Item = Item::new("query_auth"); - -pub const SNIP20_CODE_HASH: Item = Item::new("snip20_code_hash"); -pub const SNIP721_CODE_HASH: Item = Item::new("snip721_code_hash"); diff --git a/contracts/dao-dao-core/src/tests.rs b/contracts/dao-dao-core/src/tests.rs index b071f28..b0c4d3a 100644 --- a/contracts/dao-dao-core/src/tests.rs +++ b/contracts/dao-dao-core/src/tests.rs @@ -21,7 +21,7 @@ use secret_multi_test::{ next_block, App, Contract, ContractInstantiationInfo, ContractWrapper, Executor, }; use secret_utils::{Duration, Expiration}; -use snip20_reference_impl::msg::InitialBalance; +use snip20_base::msg::InitialBalance; use snip721_reference_impl::msg::ReceiverInfo; use crate::{ @@ -33,9 +33,9 @@ const CREATOR_ADDR: &str = "creator"; fn snip20_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -129,7 +129,7 @@ fn create_token_viewing_key( contract_info: ContractInfo, info: MessageInfo, ) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { entropy: "entropy".to_string(), padding: None, }; @@ -137,8 +137,8 @@ fn create_token_viewing_key( .execute_contract(info.sender, &contract_info, &msg, &[]) .unwrap(); let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { viewing_key = key; }; viewing_key @@ -160,7 +160,6 @@ fn test_instantiate_with_gov_modules() -> ContractInfo { }], query_auth: None, }, - dao_code_hash: "dao_code_hash".to_string(), }; let instantiate = InstantiateMsg { dao_uri: None, @@ -188,8 +187,6 @@ fn test_instantiate_with_gov_modules() -> ContractInfo { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = instantiate_gov(&mut app, gov_info, instantiate); @@ -239,7 +236,6 @@ fn test_instantiate_with_0_gov_modules() { }], query_auth: None, }, - dao_code_hash: "dao_code_hash".to_string(), }; let instantiate = InstantiateMsg { dao_uri: None, @@ -260,8 +256,6 @@ fn test_instantiate_with_0_gov_modules() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let _ = instantiate_gov(&mut app, gov_info, instantiate); } @@ -294,7 +288,6 @@ fn test_update_config() { }], query_auth: None, }, - dao_code_hash: "dao_code_hash".to_string(), }; let instantiate = InstantiateMsg { dao_uri: None, @@ -322,8 +315,6 @@ fn test_update_config() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = instantiate_gov(&mut app, gov_info, instantiate); @@ -386,7 +377,6 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { }], query_auth: None, }, - dao_code_hash: "dao_code_hash".to_string(), }; let instantiate = InstantiateMsg { dao_uri: None, @@ -414,8 +404,6 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = instantiate_gov(&mut app, gov_info, instantiate); @@ -565,8 +553,6 @@ fn test_removed_modules_can_not_execute() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = app @@ -752,8 +738,6 @@ fn test_module_already_disabled() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = app @@ -866,8 +850,6 @@ fn test_swap_voting_module() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = app @@ -992,8 +974,6 @@ fn test_permissions() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = app @@ -1051,7 +1031,6 @@ fn do_standard_instantiate(_auto_add: bool, admin: Option) -> (ContractI let voting_info = app.store_code(snip20_balances_voting()); let gov_info = app.store_code(cw_core_contract()); let snip20_info = app.store_code(snip20_contract()); - let snip721_info = app.store_code(snip721_contract()); let query_auth_info = app.store_code(query_auth_contract()); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { @@ -1100,8 +1079,6 @@ fn do_standard_instantiate(_auto_add: bool, admin: Option) -> (ContractI query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: snip20_info.code_hash.clone(), - snip721_code_hash: snip721_info.code_hash.to_string(), }; let gov_contract_info = app @@ -1834,8 +1811,6 @@ fn test_list_items() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let gov_contract_info = app @@ -1986,8 +1961,6 @@ fn test_instantiate_with_items() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; // Ensure duplicates are dissallowed. @@ -2051,7 +2024,7 @@ fn test_snip20_receive_auto_add() { .instantiate_contract( snip20_info, Addr::unchecked(CREATOR_ADDR), - &snip20_reference_impl::msg::InstantiateMsg { + &snip20_base::msg::InstantiateMsg { name: "DAO".to_string(), symbol: "DAO".to_string(), decimals: 6, @@ -2105,7 +2078,7 @@ fn test_snip20_receive_auto_add() { address: gov_token_info.addr.clone(), code_hash: gov_token_info.code_hash.clone(), }, - &snip20_reference_impl::msg::ExecuteMsg::Send { + &snip20_base::msg::ExecuteMsg::Send { recipient: gov_contract_info.address.clone().to_string(), recipient_code_hash: Some(gov_contract_info.code_hash.clone()), amount: Uint128::new(1), @@ -2116,8 +2089,7 @@ fn test_snip20_receive_auto_add() { padding: None, }, &[], - ) - .unwrap(); + ) .unwrap(); let snip20_list: Vec = app .wrap() @@ -2697,8 +2669,6 @@ fn test_module_prefixes() { let mut app = App::default(); let govmod_info = app.store_code(sudo_proposal_contract()); let gov_info = app.store_code(cw_core_contract()); - let snip20_info = app.store_code(snip20_contract()); - let snip721_info = app.store_code(snip721_contract()); let query_auth_info = app.store_code(query_auth_contract()); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { @@ -2750,8 +2720,6 @@ fn test_module_prefixes() { query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "Seeed".to_string(), - snip20_code_hash: snip20_info.code_hash, - snip721_code_hash: snip721_info.code_hash, }; let gov_contract_info = app diff --git a/contracts/external/cw-admin-factory/Cargo.toml b/contracts/external/cw-admin-factory/Cargo.toml index c376047..91a5a1d 100644 --- a/contracts/external/cw-admin-factory/Cargo.toml +++ b/contracts/external/cw-admin-factory/Cargo.toml @@ -25,7 +25,7 @@ secret-cw2 = { workspace = true } thiserror = { workspace = true } secret-utils = { workspace = true } dao-interface ={ workspace = true } -snip20-reference-impl ={ workspace = true } +snip20-base ={ workspace = true } [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/contracts/external/cw-fund-distributor/Cargo.toml b/contracts/external/cw-fund-distributor/Cargo.toml index 929b646..e9669dd 100644 --- a/contracts/external/cw-fund-distributor/Cargo.toml +++ b/contracts/external/cw-fund-distributor/Cargo.toml @@ -21,7 +21,7 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } secret-storage-plus = { workspace = true } secret-cw2 = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } thiserror = { workspace = true } secret-utils = { workspace = true } dao-voting-snip20-staked = { workspace = true } @@ -36,4 +36,4 @@ shade-protocol ={ workspace = true } [dev-dependencies] dao-dao-core = { workspace = true, features = ["library"] } secret-multi-test = { workspace = true } -snip20-reference-impl = { workspace = true} +snip20-base = { workspace = true} diff --git a/contracts/external/cw-fund-distributor/src/contract.rs b/contracts/external/cw-fund-distributor/src/contract.rs index 9c2600e..a422993 100644 --- a/contracts/external/cw-fund-distributor/src/contract.rs +++ b/contracts/external/cw-fund-distributor/src/contract.rs @@ -296,7 +296,7 @@ fn get_snip20_claim_wasm_messages( messages.push(WasmMsg::Execute { contract_addr: token_info.address.to_string(), code_hash: token_info.code_hash, - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Transfer { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Transfer { recipient: sender.to_string(), amount: entitlement, memo: None, diff --git a/contracts/external/cw-payroll-factory/Cargo.toml b/contracts/external/cw-payroll-factory/Cargo.toml index c58399a..f24c38d 100644 --- a/contracts/external/cw-payroll-factory/Cargo.toml +++ b/contracts/external/cw-payroll-factory/Cargo.toml @@ -28,11 +28,11 @@ cw-vesting = { workspace = true, features = ["library"] } secret-utils = { workspace = true } serde ={ workspace = true } schemars ={ workspace = true } -snip20-reference-impl = { workspace = true} +snip20-base = { workspace = true} secret-toolkit ={ workspace = true } dao-interface ={ workspace = true } [dev-dependencies] secret-multi-test = { workspace = true } -snip20-reference-impl = { workspace = true} +snip20-base = { workspace = true} wynd-utils = { workspace = true } diff --git a/contracts/external/cw-payroll-factory/src/contract.rs b/contracts/external/cw-payroll-factory/src/contract.rs index 7b62046..b9f53e6 100644 --- a/contracts/external/cw-payroll-factory/src/contract.rs +++ b/contracts/external/cw-payroll-factory/src/contract.rs @@ -425,7 +425,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result WasmMsg::Execute { contract_addr: contract_addr.into_string(), code_hash, - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Transfer { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Transfer { recipient: recipient.to_string(), amount, memo: None, @@ -146,7 +146,7 @@ mod tests { funds: vec![], contract_addr: "ekez_token".to_string(), code_hash: "ekez_token_code_hash".to_string(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Transfer { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Transfer { recipient: "ekez".to_string(), amount: Uint128::new(100), memo: None, diff --git a/contracts/external/cw-vesting/Cargo.toml b/contracts/external/cw-vesting/Cargo.toml index 4d6e9ea..8ac8c18 100644 --- a/contracts/external/cw-vesting/Cargo.toml +++ b/contracts/external/cw-vesting/Cargo.toml @@ -27,7 +27,7 @@ secret-storage-plus = { workspace = true } secret-utils = { workspace = true } cw-wormhole = { workspace = true } secret-cw2 = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } wynd-utils = { workspace = true } @@ -37,5 +37,5 @@ dao-interface ={ workspace = true } [dev-dependencies] anyhow = { workspace = true } secret-multi-test = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } # dao-testing = { workspace = true } diff --git a/contracts/external/cw1-whitelist/src/bin/schema.rs b/contracts/external/cw1-whitelist/src/bin/schema.rs index e72e546..9fd9335 100644 --- a/contracts/external/cw1-whitelist/src/bin/schema.rs +++ b/contracts/external/cw1-whitelist/src/bin/schema.rs @@ -1,11 +1,11 @@ -use cosmwasm_schema::write_api; +// use cosmwasm_schema::write_api; -use cw1_whitelist::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +// use cw1_whitelist::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } + // write_api! { + // instantiate: InstantiateMsg, + // execute: ExecuteMsg, + // query: QueryMsg, + // } } diff --git a/contracts/external/cw4-group/src/bin/schema.rs b/contracts/external/cw4-group/src/bin/schema.rs index aca81b1..24cc575 100644 --- a/contracts/external/cw4-group/src/bin/schema.rs +++ b/contracts/external/cw4-group/src/bin/schema.rs @@ -1,11 +1,11 @@ -use cosmwasm_schema::write_api; +// use cosmwasm_schema::write_api; -use cw4_group::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +// use cw4_group::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } + // write_api! { + // instantiate: InstantiateMsg, + // execute: ExecuteMsg, + // query: QueryMsg, + // } } diff --git a/contracts/external/dao-migrator/Cargo.toml b/contracts/external/dao-migrator/Cargo.toml index f6a1cae..e22fb23 100644 --- a/contracts/external/dao-migrator/Cargo.toml +++ b/contracts/external/dao-migrator/Cargo.toml @@ -23,7 +23,7 @@ secret-storage-plus = { workspace = true } secret-utils = { workspace = true } thiserror = { workspace = true } secret-cw2 = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } dao-interface = { workspace = true } dao-dao-core = { workspace = true, features = ["library"] } diff --git a/contracts/external/snip20-base/.cargo/config b/contracts/external/snip20-base/.cargo/config new file mode 100644 index 0000000..bbe1fc9 --- /dev/null +++ b/contracts/external/snip20-base/.cargo/config @@ -0,0 +1,8 @@ +[alias] +# Temporarily removed the backtraces feature from the unit-test run due to compilation errors in +# the cosmwasm-std package: +# cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", branch = "secret" } +# unit-test = "test --lib --features backtraces" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/contracts/external/snip20-base/.circleci/config.yml b/contracts/external/snip20-base/.circleci/config.yml new file mode 100644 index 0000000..161e927 --- /dev/null +++ b/contracts/external/snip20-base/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.46 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: make compile-optimized + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/external/snip20-base/.github/dependabot.yml b/contracts/external/snip20-base/.github/dependabot.yml new file mode 100644 index 0000000..9ffcf5b --- /dev/null +++ b/contracts/external/snip20-base/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + # Maintain dependencies for Cargo + - package-ecosystem: "cargo" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/contracts/external/snip20-base/.github/workflows/test.yml b/contracts/external/snip20-base/.github/workflows/test.yml new file mode 100644 index 0000000..7799efb --- /dev/null +++ b/contracts/external/snip20-base/.github/workflows/test.yml @@ -0,0 +1,88 @@ +# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml + +on: [push, pull_request] + +name: Tests + +jobs: + + + unit-test: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + - name: Run Unit Tests + run: cargo unit-test + + integration-tests: + name: Integration Tests + runs-on: ubuntu-latest + services: + # Label used to access the service container + secretdev: + # Docker Hub image + options: --name secretdev + image: ghcr.io/scrtlabs/localsecret:v1.6.0-alpha.4 + volumes: + - "/home/runner/work/snip20-base/snip20-base/:/root/code" + ports: + # Opens tcp port + - 5000:5000 + - 9091:9091 + - 1317:1317 + + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + - name: Compile WASM contract + run: | + sudo apt-get install -y binaryen + make compile-optimized && make compile-optimized-receiver +# - name: Compile WASM contract +# uses: actions-rs/cargo@v1 +# with: +# command: build +# args: --release --target wasm32-unknown-unknown --locked +# env: +# RUSTFLAGS: "-C link-arg=-s" + + - name: Run Integration Tests + run: | + ./tests/integration.sh + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Run cargo fmt + run: cargo fmt --all -- --check + + - name: Run cargo clippy + run: cargo clippy -- -D warnings + + # TODO: we should check + # CHANGES_IN_REPO=$(git status --porcelain) + # after this, but I don't know how + - name: Generate Schema + run: cargo schema --locked diff --git a/contracts/external/snip20-base/.gitignore b/contracts/external/snip20-base/.gitignore new file mode 100644 index 0000000..ea00b0b --- /dev/null +++ b/contracts/external/snip20-base/.gitignore @@ -0,0 +1,21 @@ +# Build results +/target +contract.wasm +contract.wasm.gz + +# Binaries +*.wasm +*.wasm.gz + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/external/snip20-base/Cargo.toml b/contracts/external/snip20-base/Cargo.toml new file mode 100644 index 0000000..b3bba25 --- /dev/null +++ b/contracts/external/snip20-base/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "snip20-base" +version = "1.0.0" +authors = ["Itzik "] +edition = "2021" +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", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +#default = ["debug-print"] +backtraces = ["cosmwasm-std/backtraces"] + +# debug-print = ["cosmwasm-std/debug-print"] +[dependencies] +cosmwasm-std = {workspace=true} +cosmwasm-storage = {workspace=true} +rand = { version = "0.8.5", default-features = false } +secret-toolkit = {workspace=true} +secret-toolkit-crypto = { git = "https://github.com/scrtlabs/secret-toolkit", features = [ + "rand", + "hash", +], rev = "9b74bdac71c2fedcc12246f18cdfdd94b8991282" } +getrandom = { version = "0.2", features = ["js"] } + +schemars = "0.8.12" +serde = { version = "1.0.158", default-features = false, features = ["derive"] } +base64 = "0.21.0" +cosmwasm-schema = {workspace=true} +dao-interface ={ workspace = true } \ No newline at end of file diff --git a/contracts/external/snip20-base/Developing.md b/contracts/external/snip20-base/Developing.md new file mode 100644 index 0000000..45baccd --- /dev/null +++ b/contracts/external/snip20-base/Developing.md @@ -0,0 +1,153 @@ +# Developing + +If you have recently created a contract with this template, you probably could use some +help on how to build and test the contract, as well as prepare it for production. This +file attempts to provide a brief overview, assuming you have installed a recent +version of Rust already (eg. 1.41+). + +## Prerequisites + +Before starting, make sure you have [rustup](https://rustup.rs/) along with a +recent `rustc` and `cargo` version installed. Currently, we are testing on 1.41+. + +And you need to have the `wasm32-unknown-unknown` target installed as well. + +You can check that via: + +```sh +rustc --version +cargo --version +rustup target list --installed +# if wasm32 is not listed above, run this +rustup target add wasm32-unknown-unknown +``` + +### Using macos? + +You'll need to install LLVM using Homebrew: +```sh +brew install llvm +echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.profile +echo 'export CC=/usr/local/opt/llvm/bin/clang' >> ~/.profile +echo 'export AR=/usr/local/opt/llvm/bin/llvm-ar' >> ~/.profile +source ~/.profile +``` + +## Compiling and running tests + +Now that you created your custom contract, make sure you can compile and run it before +making any changes. Go into the + +```sh +# this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOUR_NAME_HERE.wasm +cargo wasm + +# this runs unit tests with helpful backtraces +RUST_BACKTRACE=1 cargo unit-test + +# this runs integration tests with cranelift backend (uses rust stable) +cargo integration-test + +# this runs integration tests with singlepass backend (needs rust nightly) +cargo integration-test --no-default-features --features singlepass + +# auto-generate json schema +cargo schema +``` + +The wasmer engine, embedded in `cosmwasm-vm` supports multiple backends: +singlepass and cranelift. Singlepass has fast compile times and slower run times, +and supportes gas metering. It also requires rust `nightly`. This is used as default +when embedding `cosmwasm-vm` in `go-cosmwasm` and is needed to use if you want to +check the gas usage. + +However, when just building contacts, if you don't want to worry about installing +two rust toolchains, you can run all tests with cranelift. The integration tests +may take a small bit longer, but the results will be the same. The only difference +is that you can not check gas usage here, so if you wish to optimize gas, you must +switch to nightly and run with cranelift. + +### Understanding the tests + +The main code is in `src/contract.rs` and the unit tests there run in pure rust, +which makes them very quick to execute and give nice output on failures, especially +if you do `RUST_BACKTRACE=1 cargo unit-test`. + +However, we don't just want to test the logic rust, but also the compiled Wasm artifact +inside a VM. You can look in `tests/integration.rs` to see some examples there. They +load the Wasm binary into the vm and call the contract externally. Effort has been +made that the syntax is very similar to the calls in the native rust contract and +quite easy to code. In fact, usually you can just copy a few unit tests and modify +a few lines to make an integration test (this should get even easier in a future release). + +To run the latest integration tests, you need to explicitely rebuild the Wasm file with +`cargo wasm` and then run `cargo integration-test`. + +We consider testing critical for anything on a blockchain, and recommend to always keep +the tests up to date. While doing active development, it is often simplest to disable +the integration tests completely and iterate rapidly on the code in `contract.rs`, +both the logic and the tests. Once the code is finalized, you can copy over some unit +tests into the integration.rs and make the needed changes. This ensures the compiled +Wasm also behaves as desired in the real system. + +## Generating JSON Schema + +While the Wasm calls (`init`, `handle`, `query`) accept JSON, this is not enough +information to use it. We need to expose the schema for the expected messages to the +clients. You can generate this schema by calling `cargo schema`, which will output +4 files in `./schema`, corresponding to the 3 message types the contract accepts, +as well as the internal `State`. + +These files are in standard json-schema format, which should be usable by various +client side tools, either to auto-generate codecs, or just to validate incoming +json wrt. the defined schema. + +## Preparing the Wasm bytecode for production + +Before we upload it to a chain, we need to ensure the smallest output size possible, +as this will be included in the body of a transaction. We also want to have a +reproducible build process, so third parties can verify that the uploaded Wasm +code did indeed come from the claimed rust code. + +To solve both these issues, we have produced `rust-optimizer`, a docker image to +produce an extremely small build output in a consistent manner. The suggest way +to run it is this: + +```sh +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.8.0 +``` + +We must mount the contract code to `/code`. You can use a absolute path instead +of `$(pwd)` if you don't want to `cd` to the directory first. The other two +volumes are nice for speedup. Mounting `/code/target` in particular is useful +to avoid docker overwriting your local dev files with root permissions. +Note the `/code/target` cache is unique for each contract being compiled to limit +interference, while the registry cache is global. + +This is rather slow compared to local compilations, especially the first compile +of a given contract. The use of the two volume caches is very useful to speed up +following compiles of the same contract. + +This produces a `contract.wasm` file in the current directory (which must be the root +directory of your rust project, the one with `Cargo.toml` inside). As well as +`hash.txt` containing the Sha256 hash of `contract.wasm`, and it will rebuild +your schema files as well. + +### Testing production build + +Once we have this compressed `contract.wasm`, we may want to ensure it is actually +doing everything it is supposed to (as it is about 4% of the original size). +If you update the "WASM" line in `tests/integration.rs`, it will run the integration +steps on the optimized build, not just the normal build. I have never seen a different +behavior, but it is nice to verify sometimes. + +```rust +static WASM: &[u8] = include_bytes!("../contract.wasm"); +``` + +Note that this is the same (deterministic) code you will be uploading to +a blockchain to test it out, as we need to shrink the size and produce a +clear mapping from wasm hash back to the source code. diff --git a/contracts/external/snip20-base/Importing.md b/contracts/external/snip20-base/Importing.md new file mode 100644 index 0000000..e367b65 --- /dev/null +++ b/contracts/external/snip20-base/Importing.md @@ -0,0 +1,62 @@ +# Importing + +In [Publishing](./Publishing.md), we discussed how you can publish your contract to the world. +This looks at the flip-side, how can you use someone else's contract (which is the same +question as how they will use your contract). Let's go through the various stages. + +## Verifying Artifacts + +Before using remote code, you most certainly want to verify it is honest. + +The simplest audit of the repo is to simply check that the artifacts in the repo +are correct. This involves recompiling the claimed source with the claimed builder +and validating that the locally compiled code (hash) matches the code hash that was +uploaded. This will verify that the source code is the correct preimage. Which allows +one to audit the original (Rust) source code, rather than looking at wasm bytecode. + +We have a script to do this automatic verification steps that can +easily be run by many individuals. Please check out +[`cosmwasm-verify`](https://github.com/CosmWasm/cosmwasm-verify/blob/master/README.md) +to see a simple shell script that does all these steps and easily allows you to verify +any uploaded contract. + +## Reviewing + +Once you have done the quick programatic checks, it is good to give at least a quick +look through the code. A glance at `examples/schema.rs` to make sure it is outputing +all relevant structs from `contract.rs`, and also ensure `src/lib.rs` is just the +default wrapper (nothing funny going on there). After this point, we can dive into +the contract code itself. Check the flows for the handle methods, any invariants and +permission checks that should be there, and a reasonable data storage format. + +You can dig into the contract as far as you want, but it is important to make sure there +are no obvious backdoors at least. + +## Decentralized Verification + +It's not very practical to do a deep code review on every dependency you want to use, +which is a big reason for the popularity of code audits in the blockchain world. We trust +some experts review in lieu of doing the work ourselves. But wouldn't it be nice to do this +in a decentralized manner and peer-review each other's contracts? Bringing in deeper domain +knowledge and saving fees. + +Luckily, there is an amazing project called [crev](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/README.md) +that provides `A cryptographically verifiable code review system for the cargo (Rust) package manager`. + +I highly recommend that CosmWasm contract developers get set up with this. At minimum, we +can all add a review on a package that programmatically checked out that the json schemas +and wasm bytecode do match the code, and publish our claim, so we don't all rely on some +central server to say it validated this. As we go on, we can add deeper reviews on standard +packages. + +If you want to use `cargo-crev`, please follow their +[getting started guide](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/src/doc/getting_started.md) +and once you have made your own *proof repository* with at least one *trust proof*, +please make a PR to the [`cawesome-wasm`]() repo with a link to your repo and +some public name or pseudonym that people know you by. This allows people who trust you +to also reuse your proofs. + +There is a [standard list of proof repos](https://github.com/crev-dev/cargo-crev/wiki/List-of-Proof-Repositories) +with some strong rust developers in there. This may cover dependencies like `serde` and `snafu` +but will not hit any CosmWasm-related modules, so we look to bootstrap a very focused +review community. diff --git a/contracts/external/snip20-base/LICENSE b/contracts/external/snip20-base/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/contracts/external/snip20-base/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contracts/external/snip20-base/Makefile b/contracts/external/snip20-base/Makefile new file mode 100644 index 0000000..eee7909 --- /dev/null +++ b/contracts/external/snip20-base/Makefile @@ -0,0 +1,89 @@ +SECRETCLI = docker exec -it secretdev /usr/bin/secretcli + +.PHONY: all +all: clippy test + +.PHONY: check +check: + cargo check + +.PHONY: check-receiver +check-receiver: + $(MAKE) -C tests/example-receiver check + +.PHONY: clippy +clippy: + cargo clippy + +.PHONY: clippy-receiver +clippy-receiver: + $(MAKE) -C tests/example-receiver clippy + +.PHONY: test +test: unit-test unit-test-receiver integration-test + +.PHONY: unit-test +unit-test: + RUST_BACKTRACE=1 cargo test + +.PHONY: unit-test-nocapture +unit-test-nocapture: + RUST_BACKTRACE=1 cargo test -- --nocapture + +.PHONY: unit-test-receiver +unit-test-receiver: + $(MAKE) -C tests/example-receiver unit-test + +.PHONY: integration-test +integration-test: compile-optimized compile-optimized-receiver + if tests/integration.sh; then echo -n '\a'; else echo -n '\a'; sleep 0.125; echo -n '\a'; fi + +compile-optimized-receiver: + $(MAKE) -C tests/example-receiver compile-optimized + +.PHONY: list-code +list-code: + $(SECRETCLI) query compute list-code + +.PHONY: compile _compile +compile: _compile contract.wasm.gz +_compile: + cargo build --target wasm32-unknown-unknown --locked + cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm + +.PHONY: compile-optimized _compile-optimized +compile-optimized: _compile-optimized contract.wasm.gz +_compile-optimized: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + @# The following line is not necessary, may work only on linux (extra size optimization) + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm + +.PHONY: compile-optimized-reproducible +compile-optimized-reproducible: + docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + enigmampc/secret-contract-optimizer:1.0.7 + +contract.wasm.gz: contract.wasm + cat ./contract.wasm | gzip -9 > ./contract.wasm.gz + +contract.wasm: + cp ./target/wasm32-unknown-unknown/release/snip20_reference_impl.wasm ./contract.wasm + +.PHONY: start-server +start-server: # CTRL+C to stop + docker run -it --rm \ + -p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ + -v $$(pwd):/root/code \ + --name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-alpha.4 + +.PHONY: schema +schema: + cargo run --example schema + +.PHONY: clean +clean: + cargo clean + rm -f ./contract.wasm ./contract.wasm.gz + $(MAKE) -C tests/example-receiver clean diff --git a/contracts/external/snip20-base/NOTICE b/contracts/external/snip20-base/NOTICE new file mode 100644 index 0000000..f18150b --- /dev/null +++ b/contracts/external/snip20-base/NOTICE @@ -0,0 +1,13 @@ +Copyright 2020 Itzik + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/contracts/external/snip20-base/Publishing.md b/contracts/external/snip20-base/Publishing.md new file mode 100644 index 0000000..35f5212 --- /dev/null +++ b/contracts/external/snip20-base/Publishing.md @@ -0,0 +1,115 @@ +# Publishing Contracts + +This is an overview of how to publish the contract's source code in this repo. +We use Cargo's default registry [crates.io](https://crates.io/) for publishing contracts written in Rust. + +## Preparation + +Ensure the `Cargo.toml` file in the repo is properly configured. In particular, you want to +choose a name starting with `cw-`, which will help a lot finding CosmWasm contracts when +searching on crates.io. For the first publication, you will probably want version `0.1.0`. +If you have tested this on a public net already and/or had an audit on the code, +you can start with `1.0.0`, but that should imply some level of stability and confidence. +You will want entries like the following in `Cargo.toml`: + +```toml +name = "cw-escrow" +version = "0.1.0" +description = "Simple CosmWasm contract for an escrow with arbiter and timeout" +repository = "https://github.com/confio/cosmwasm-examples" +``` + +You will also want to add a valid [SPDX license statement](https://spdx.org/licenses/), +so others know the rules for using this crate. You can use any license you wish, +even a commercial license, but we recommend choosing one of the following, unless you have +specific requirements. + +* Permissive: [`Apache-2.0`](https://spdx.org/licenses/Apache-2.0.html#licenseText) or [`MIT`](https://spdx.org/licenses/MIT.html#licenseText) +* Copyleft: [`GPL-3.0-or-later`](https://spdx.org/licenses/GPL-3.0-or-later.html#licenseText) or [`AGPL-3.0-or-later`](https://spdx.org/licenses/AGPL-3.0-or-later.html#licenseText) +* Commercial license: `Commercial` (not sure if this works, I cannot find examples) + +It is also helpful to download the LICENSE text (linked to above) and store this +in a LICENSE file in your repo. Now, you have properly configured your crate for use +in a larger ecosystem. + +### Updating schema + +To allow easy use of the contract, we can publish the schema (`schema/*.json`) together +with the source code. + +```sh +cargo schema +``` + +Ensure you check in all the schema files, and make a git commit with the final state. +This commit will be published and should be tagged. Generally, you will want to +tag with the version (eg. `v0.1.0`), but in the `cosmwasm-examples` repo, we have +multiple contracts and label it like `escrow-0.1.0`. Don't forget a +`git push && git push --tags` + +### Note on build results + +Build results like Wasm bytecode or expected hash don't need to be updated since +the don't belong to the source publication. However, they are excluded from packaging +in `Cargo.toml` which allows you to commit them to your git repository if you like. + +```toml +exclude = ["contract.wasm", "hash.txt"] +``` + +A single source code can be built with multiple different optimizers, so +we should not make any strict assumptions on the tooling that will be used. + +## Publishing + +Now that your package is properly configured and all artifacts are committed, it +is time to share it with the world. +Please refer to the [complete instructions for any questions](https://rurust.github.io/cargo-docs-ru/crates-io.html), +but I will try to give a quick overview of the happy path here. + +### Registry + +You will need an account on [crates.io](https://crates.io) to publish a rust crate. +If you don't have one already, just click on "Log in with GitHub" in the top-right +to quickly set up a free account. Once inside, click on your username (top-right), +then "Account Settings". On the bottom, there is a section called "API Access". +If you don't have this set up already, create a new token and use `cargo login` +to set it up. This will now authenticate you with the `cargo` cli tool and allow +you to publish. + +### Uploading + +Once this is set up, make sure you commit the current state you want to publish. +Then try `cargo publish --dry-run`. If that works well, review the files that +will be published via `cargo package --list`. If you are satisfied, you can now +officially publish it via `cargo publish`. + +Congratulations, your package is public to the world. + +### Sharing + +Once you have published your package, people can now find it by +[searching for "cw-" on crates.io](https://crates.io/search?q=cw). +But that isn't exactly the simplest way. To make things easier and help +keep the ecosystem together, we suggest making a PR to add your package +to the [`cawesome-wasm`](https://github.com/cosmwasm/cawesome-wasm) list. + +### Organizations + +Many times you are writing a contract not as a solo developer, but rather as +part of an organization. You will want to allow colleagues to upload new +versions of the contract to crates.io when you are on holiday. +[These instructions show how]() you can set up your crate to allow multiple maintainers. + +You can add another owner to the crate by specifying their github user. Note, you will +now both have complete control of the crate, and they can remove you: + +`cargo owner --add ethanfrey` + +You can also add an existing github team inside your organization: + +`cargo owner --add github:confio:developers` + +The team will allow anyone who is currently in the team to publish new versions of the crate. +And this is automatically updated when you make changes on github. However, it will not allow +anyone in the team to add or remove other owners. diff --git a/contracts/external/snip20-base/README.md b/contracts/external/snip20-base/README.md new file mode 100644 index 0000000..1e530ec --- /dev/null +++ b/contracts/external/snip20-base/README.md @@ -0,0 +1,177 @@ +# SNIP-20 Reference Implementation + +This is an implementation of a [SNIP-20](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md), [SNIP-21](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-21.md), [SNIP-22](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-22.md), [SNIP-23](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-23.md), [SNIP-24](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md), [SNIP-25](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-25.md) and [SNIP-26](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-26.md) compliant token contract. + +> **Note:** +> The master branch contains new features not covered by officially-released SNIPs and may be subject to change. When releasing a token on mainnet, we recommend you start with a [tagged release](https://github.com/scrtlabs/snip20-base/tags) to ensure compatibility with SNIP standards. + +At the time of token creation you may configure: +* Public Total Supply: If you enable this, the token's total supply will be displayed whenever a TokenInfo query is performed. DEFAULT: false +* Enable Deposit: If you enable this, you will be able to convert from SCRT to the token.* DEFAULT: false +* Enable Redeem: If you enable this, you will be able to redeem your token for SCRT.* It should be noted that if you have redeem enabled, but deposit disabled, all redeem attempts will fail unless someone has sent SCRT to the token contract. DEFAULT: false +* Enable Mint: If you enable this, any address in the list of minters will be able to mint new tokens. The admin address is the default minter, but can use the set/add/remove_minters functions to change the list of approved minting addresses. DEFAULT: false +* Enable Burn: If you enable this, addresses will be able to burn tokens. DEFAULT: false + + +\*:The conversion rate will be 1 uscrt for 1 minimum denomination of the token. This means that if your token has 6 decimal places, it will convert 1:1 with SCRT. If your token has 10 decimal places, it will have an exchange rate of 10000 SCRT for 1 token. If your token has 3 decimal places, it will have an exchange rate of 1000 tokens for 1 SCRT. You can use the exchange_rate query to view the exchange rate for the token. The query response will display either how many tokens are worth 1 SCRT, or how many SCRT are worth 1 token. That is, the response lists the symbol of the coin that has less value (either SCRT or the token), and the number of those coins that are worth 1 of the other. + +## Usage examples: + +To create a new token: + +```secretcli tx compute instantiate '{"name":"","symbol":"","admin":"","decimals":,"initial_balances":[{"address":"","amount":""}],"prng_seed":"","config":{"public_total_supply":,"enable_deposit":,"enable_redeem":,"enable_mint":,"enable_burn":}}' --label --from ``` + +The `admin` field is optional and will default to the "--from" address if you do not specify it. The `initial_balances` field is optional, and you can specify as many addresses/balances as you like. The `config` field as well as every field in the `config` is optional. Any `config` fields not specified will default to `false`. + +To deposit: ***(This is public)*** + +```secretcli tx compute execute '{"deposit": {}}' --amount 1000000uscrt --from ``` + +To send SSCRT: + +```secretcli tx compute execute '{"transfer": {"recipient": "", "amount": ""}}' --from ``` + +To set your viewing key: + +```secretcli tx compute execute '{"create_viewing_key": {"entropy": ""}}' --from ``` + +To check your balance: + +```secretcli q compute query '{"balance": {"address":"", "key":"your_viewing_key"}}'``` + +To view your transfer history: + +```secretcli q compute query '{"transfer_history": {"address": "", "key": "", "page": , "page_size": , "should_filter_decoys":}}'``` + +To view your transaction history: + +```secretcli q compute query '{"transaction_history": {"address": "", "key": "", "page": , "page_size": , "should_filter_decoys":}}'``` + +To withdraw: ***(This is public)*** + +```secretcli tx compute execute '{"redeem": {"amount": ""}}' --from ``` + +To view the token contract's configuration: + +```secretcli q compute query '{"token_config": {}}'``` + +To view the deposit/redeem exchange rate: + +```secretcli q compute query '{"exchange_rate": {}}'``` + + +## Troubleshooting + +All transactions are encrypted, so if you want to see the error returned by a failed transaction, you need to use the command + +`secretcli q compute tx ` + +# SNIP 25 Security Update + +## Security Changes +1. Implemented the ability to have decoy addresses for every operation that access account's balance +2. Converted every add operation related to account's balance and total supply +3. Started using u128 instead of Uint128 + +## Decoys +### Transaction That Support Decoys +1. Redeem +2. Deposit +3. Transfer +4. TransferFrom +5. Send +6. SendFrom +7. Burn +8. BurnFrom +9. Mint +10. BatchTransfer - For every action (The strength of the decoys will be the minimal strength of all of the actions) +11. BatchSend - For every action (The strength of the decoys will be the minimal strength of all of the actions) +12. BatchTransferFrom - For every action (The strength of the decoys will be the minimal strength of all of the actions) +13. BatchSendFrom - For every action (The strength of the decoys will be the minimal strength of all of the actions) +14. BatchMint - For every action (The strength of the decoys will be the minimal strength of all of the actions) +15. BatchBurnFrom - For every action (The strength of the decoys will be the minimal strength of all of the actions) + +### Example +```secretcli tx compute execute '{"transfer":{"recipient":"
","amount":"", "entropy":"", "decoys":<[addresses_list]>}}' --from ``` + +## Future Work +| Topic | Immediate-term solution | Medium-term solution | Long-term solution | +| --- | --- | --- | --- | +| Receiver privacy | Decoys - offer limited privacy, since it depends a lot on how you choose decoys. There’s probably no way to select decoys effectively enough, and thus it only makes it a bit harder but effectively doesn’t provide receiver privacy to a sophisticated long-term attacker | Some sort of bucketing? - still no clear path forward| ORAM? - still no clear path forward | +| Transfer amount privacy - subtractions (Transfer/Send/Burn) | None | None | Merkle proofs for storage reads - will make it very difficult to simulate transactions and play with storage. | + +# SNIP 25 Other Updates + +## All Allowances +Adds the ability for an owner to query for all allowances they have given out, as well as for a spender to query for all allowances they have received. + +## Queries + +### AllowancesGiven + +This query MUST be authenticated. + +Returns the list of allowances given out by the current account as an owner, as well as the total count of allowances given out. + +Results SHOULD be paginated. Results MUST be sorted in reverse chronological order by the datetime at which the allowance was first created (i.e., order is not determined by expiration, nor by last modified). + +#### Request + +| Name | Type | Description | optional | +| ---- | ---- | ----------- | -------- | +| [with_permit].query.allowances_given.owner | string | Account from which tokens are allowed to be taken | no | +| [with_permit].query.allowances_given.page_size | number | Number of allowances to return, starting from the latest. i.e. n=1 will return only the latest allowance | no | +| [with_permit].query.allowances_given.page | number | Defaults to 0. Specifying a positive number will skip page * page_size txs from the start. | yes | + +#### Response +```json +{ + "allowances_given": { + "owner": "
", + "allowances": [ + { + "spender": "
", + "allowance": "Uint128", + "expiration": 1234, + }, + { "...": "..." } + ], + "count": 200 + } +} +``` + +### AllowancesReceived + +This query MUST be authenticated. + +Returns the list of allowances given to the current account as a spender, as well as the total count of allowances received. + +Results SHOULD be paginated. Results MUST be sorted in reverse chronological order by the datetime at which the allowance was first created (i.e., order is not determined by expiration). + +#### Request + +| Name | Type | Description | optional | +| ---- | ---- | ----------- | -------- | +| [with_permit.]query.allowances_received.spender | string | Account which is allowed to spend tokens on behalf of the owner | no | +| [with_permit.]query.allowances_received.page_size | number | Number of allowances to return, starting from the latest. i.e. n=1 will return only the latest allowance | no | +| [with_permit.]query.allowances_received.page | number | Defaults to 0. Specifying a positive number will skip page * page_size txs from the start. | yes | + +#### Response + +```json +{ + "allowances_received": { + "spender": "
", + "allowances": [ + { + "owner": "
", + "allowance": "Uint128", + "expiration": 1234, + }, + { "...": "..." } + ], + "count": 200 + } +} +``` diff --git a/contracts/external/snip20-base/examples/schema.rs b/contracts/external/snip20-base/examples/schema.rs new file mode 100644 index 0000000..053dd08 --- /dev/null +++ b/contracts/external/snip20-base/examples/schema.rs @@ -0,0 +1,21 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use snip20_base::msg::{ + ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, +}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(ExecuteAnswer), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(QueryAnswer), &out_dir); +} diff --git a/contracts/external/snip20-base/rustfmt.toml b/contracts/external/snip20-base/rustfmt.toml new file mode 100644 index 0000000..11a85e6 --- /dev/null +++ b/contracts/external/snip20-base/rustfmt.toml @@ -0,0 +1,15 @@ +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" + diff --git a/contracts/external/snip20-base/schema/handle_answer.json b/contracts/external/snip20-base/schema/handle_answer.json new file mode 100644 index 0000000..675fdfa --- /dev/null +++ b/contracts/external/snip20-base/schema/handle_answer.json @@ -0,0 +1,534 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HandleAnswer", + "anyOf": [ + { + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "redeem" + ], + "properties": { + "redeem": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_transfer" + ], + "properties": { + "batch_transfer": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_send" + ], + "properties": { + "batch_send": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "register_receive" + ], + "properties": { + "register_receive": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "create_viewing_key" + ], + "properties": { + "create_viewing_key": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "$ref": "#/definitions/ViewingKey" + } + } + } + } + }, + { + "type": "object", + "required": [ + "set_viewing_key" + ], + "properties": { + "set_viewing_key": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "increase_allowance" + ], + "properties": { + "increase_allowance": { + "type": "object", + "required": [ + "allowance", + "owner", + "spender" + ], + "properties": { + "allowance": { + "$ref": "#/definitions/Uint128" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "decrease_allowance" + ], + "properties": { + "decrease_allowance": { + "type": "object", + "required": [ + "allowance", + "owner", + "spender" + ], + "properties": { + "allowance": { + "$ref": "#/definitions/Uint128" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer_from" + ], + "properties": { + "transfer_from": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_transfer_from" + ], + "properties": { + "batch_transfer_from": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_send_from" + ], + "properties": { + "batch_send_from": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn_from" + ], + "properties": { + "burn_from": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_burn_from" + ], + "properties": { + "batch_burn_from": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_mint" + ], + "properties": { + "batch_mint": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "add_minters" + ], + "properties": { + "add_minters": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "remove_minters" + ], + "properties": { + "remove_minters": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "set_minters" + ], + "properties": { + "set_minters": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "change_admin" + ], + "properties": { + "change_admin": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "set_contract_status" + ], + "properties": { + "set_contract_status": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + }, + { + "type": "object", + "required": [ + "revoke_pemit" + ], + "properties": { + "revoke_pemit": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + } + } + } + } + ], + "definitions": { + "HumanAddr": { + "type": "string" + }, + "ResponseStatus": { + "type": "string", + "enum": [ + "success", + "failure" + ] + }, + "Uint128": { + "type": "string" + }, + "ViewingKey": { + "type": "string" + } + } +} diff --git a/contracts/external/snip20-base/schema/handle_msg.json b/contracts/external/snip20-base/schema/handle_msg.json new file mode 100644 index 0000000..10ba231 --- /dev/null +++ b/contracts/external/snip20-base/schema/handle_msg.json @@ -0,0 +1,993 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HandleMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "redeem" + ], + "properties": { + "redeem": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": [ + "string", + "null" + ] + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object", + "properties": { + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient_code_hash": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_transfer" + ], + "properties": { + "batch_transfer": { + "type": "object", + "required": [ + "actions" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/TransferAction" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_send" + ], + "properties": { + "batch_send": { + "type": "object", + "required": [ + "actions" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/SendAction" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "register_receive" + ], + "properties": { + "register_receive": { + "type": "object", + "required": [ + "code_hash" + ], + "properties": { + "code_hash": { + "type": "string" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "create_viewing_key" + ], + "properties": { + "create_viewing_key": { + "type": "object", + "required": [ + "entropy" + ], + "properties": { + "entropy": { + "type": "string" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "set_viewing_key" + ], + "properties": { + "set_viewing_key": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "increase_allowance" + ], + "properties": { + "increase_allowance": { + "type": "object", + "required": [ + "amount", + "spender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "expiration": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "decrease_allowance" + ], + "properties": { + "decrease_allowance": { + "type": "object", + "required": [ + "amount", + "spender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "expiration": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer_from" + ], + "properties": { + "transfer_from": { + "type": "object", + "required": [ + "amount", + "owner", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "amount", + "owner", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient_code_hash": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_transfer_from" + ], + "properties": { + "batch_transfer_from": { + "type": "object", + "required": [ + "actions" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/TransferFromAction" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_send_from" + ], + "properties": { + "batch_send_from": { + "type": "object", + "required": [ + "actions" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/SendFromAction" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "burn_from" + ], + "properties": { + "burn_from": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_burn_from" + ], + "properties": { + "batch_burn_from": { + "type": "object", + "required": [ + "actions" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/BurnFromAction" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "batch_mint" + ], + "properties": { + "batch_mint": { + "type": "object", + "required": [ + "actions" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/MintAction" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "add_minters" + ], + "properties": { + "add_minters": { + "type": "object", + "required": [ + "minters" + ], + "properties": { + "minters": { + "type": "array", + "items": { + "$ref": "#/definitions/HumanAddr" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "remove_minters" + ], + "properties": { + "remove_minters": { + "type": "object", + "required": [ + "minters" + ], + "properties": { + "minters": { + "type": "array", + "items": { + "$ref": "#/definitions/HumanAddr" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "set_minters" + ], + "properties": { + "set_minters": { + "type": "object", + "required": [ + "minters" + ], + "properties": { + "minters": { + "type": "array", + "items": { + "$ref": "#/definitions/HumanAddr" + } + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "change_admin" + ], + "properties": { + "change_admin": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "set_contract_status" + ], + "properties": { + "set_contract_status": { + "type": "object", + "required": [ + "level" + ], + "properties": { + "level": { + "$ref": "#/definitions/ContractStatusLevel" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "revoke_permit" + ], + "properties": { + "revoke_permit": { + "type": "object", + "required": [ + "permit_name" + ], + "properties": { + "permit_name": { + "type": "string" + } + } + } + } + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "BurnFromAction": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + "ContractStatusLevel": { + "type": "string", + "enum": [ + "normal_run", + "stop_all_but_redeems", + "stop_all" + ] + }, + "HumanAddr": { + "type": "string" + }, + "MintAction": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + "SendAction": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient_code_hash": { + "type": [ + "string", + "null" + ] + } + } + }, + "SendFromAction": { + "type": "object", + "required": [ + "amount", + "owner", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient_code_hash": { + "type": [ + "string", + "null" + ] + } + } + }, + "TransferAction": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + "TransferFromAction": { + "type": "object", + "required": [ + "amount", + "owner", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/external/snip20-base/schema/init_msg.json b/contracts/external/snip20-base/schema/init_msg.json new file mode 100644 index 0000000..49dab42 --- /dev/null +++ b/contracts/external/snip20-base/schema/init_msg.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InitMsg", + "type": "object", + "required": [ + "decimals", + "name", + "prng_seed", + "symbol" + ], + "properties": { + "admin": { + "anyOf": [ + { + "$ref": "#/definitions/HumanAddr" + }, + { + "type": "null" + } + ] + }, + "config": { + "anyOf": [ + { + "$ref": "#/definitions/InitConfig" + }, + { + "type": "null" + } + ] + }, + "decimals": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "initial_balances": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/InitialBalance" + } + }, + "name": { + "type": "string" + }, + "prng_seed": { + "$ref": "#/definitions/Binary" + }, + "symbol": { + "type": "string" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "HumanAddr": { + "type": "string" + }, + "InitConfig": { + "description": "This type represents optional configuration values which can be overridden. All values are optional and have defaults which are more private by default, but can be overridden if necessary", + "type": "object", + "properties": { + "enable_burn": { + "description": "Indicates whether burn functionality should be enabled default: False", + "type": [ + "boolean", + "null" + ] + }, + "enable_deposit": { + "description": "Indicates whether deposit functionality should be enabled default: False", + "type": [ + "boolean", + "null" + ] + }, + "enable_mint": { + "description": "Indicates whether mint functionality should be enabled default: False", + "type": [ + "boolean", + "null" + ] + }, + "enable_redeem": { + "description": "Indicates whether redeem functionality should be enabled default: False", + "type": [ + "boolean", + "null" + ] + }, + "public_total_supply": { + "description": "Indicates whether the total supply is public or should be kept secret. default: False", + "type": [ + "boolean", + "null" + ] + } + } + }, + "InitialBalance": { + "type": "object", + "required": [ + "address", + "amount" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "amount": { + "$ref": "#/definitions/Uint128" + } + } + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/external/snip20-base/schema/query_answer.json b/contracts/external/snip20-base/schema/query_answer.json new file mode 100644 index 0000000..44087cb --- /dev/null +++ b/contracts/external/snip20-base/schema/query_answer.json @@ -0,0 +1,394 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryAnswer", + "anyOf": [ + { + "type": "object", + "required": ["token_info"], + "properties": { + "token_info": { + "type": "object", + "required": ["decimals", "name", "symbol"], + "properties": { + "decimals": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "total_supply": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + } + } + } + } + }, + { + "type": "object", + "required": ["token_config"], + "properties": { + "token_config": { + "type": "object", + "required": [ + "burn_enabled", + "deposit_enabled", + "mint_enabled", + "public_total_supply", + "redeem_enabled" + ], + "properties": { + "burn_enabled": { + "type": "boolean" + }, + "deposit_enabled": { + "type": "boolean" + }, + "mint_enabled": { + "type": "boolean" + }, + "public_total_supply": { + "type": "boolean" + }, + "redeem_enabled": { + "type": "boolean" + } + } + } + } + }, + { + "type": "object", + "required": ["contract_status"], + "properties": { + "contract_status": { + "type": "object", + "required": ["status"], + "properties": { + "status": { + "$ref": "#/definitions/ContractStatusLevel" + } + } + } + } + }, + { + "type": "object", + "required": ["exchange_rate"], + "properties": { + "exchange_rate": { + "type": "object", + "required": ["denom", "rate"], + "properties": { + "denom": { + "type": "string" + }, + "rate": { + "$ref": "#/definitions/Uint128" + } + } + } + } + }, + { + "type": "object", + "required": ["allowance"], + "properties": { + "allowance": { + "type": "object", + "required": ["allowance", "owner", "spender"], + "properties": { + "allowance": { + "$ref": "#/definitions/Uint128" + }, + "expiration": { + "type": ["integer", "null"], + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": ["balance"], + "properties": { + "balance": { + "type": "object", + "required": ["amount"], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + } + }, + { + "type": "object", + "required": ["transfer_history"], + "properties": { + "transfer_history": { + "type": "object", + "required": ["txs"], + "properties": { + "total": { + "type": ["integer", "null"], + "format": "uint64", + "minimum": 0.0 + }, + "txs": { + "type": "array", + "items": { + "$ref": "#/definitions/Tx" + } + } + } + } + } + }, + { + "type": "object", + "required": ["transaction_history"], + "properties": { + "transaction_history": { + "type": "object", + "required": ["txs"], + "properties": { + "total": { + "type": ["integer", "null"], + "format": "uint64", + "minimum": 0.0 + }, + "txs": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtendedTx" + } + } + } + } + } + }, + { + "type": "object", + "required": ["viewing_key_error"], + "properties": { + "viewing_key_error": { + "type": "object", + "required": ["msg"], + "properties": { + "msg": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": ["minters"], + "properties": { + "minters": { + "type": "object", + "required": ["minters"], + "properties": { + "minters": { + "type": "array", + "items": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + } + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": ["amount", "denom"], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ContractStatusLevel": { + "type": "string", + "enum": ["normal_run", "stop_all_but_redeems", "stop_all"] + }, + "HumanAddr": { + "type": "string" + }, + "ExtendedTx": { + "type": "object", + "required": ["action", "block_height", "block_time", "coins", "id"], + "properties": { + "action": { + "$ref": "#/definitions/TxAction" + }, + "block_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "block_time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "coins": { + "$ref": "#/definitions/Coin" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "memo": { + "type": ["string", "null"] + } + } + }, + "Tx": { + "type": "object", + "required": ["coins", "from", "id", "receiver", "sender"], + "properties": { + "block_height": { + "type": ["integer", "null"], + "format": "uint64", + "minimum": 0.0 + }, + "block_time": { + "type": ["integer", "null"], + "format": "uint64", + "minimum": 0.0 + }, + "coins": { + "$ref": "#/definitions/Coin" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "memo": { + "type": ["string", "null"] + }, + "receiver": { + "$ref": "#/definitions/HumanAddr" + }, + "sender": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + "TxAction": { + "anyOf": [ + { + "type": "object", + "required": ["transfer"], + "properties": { + "transfer": { + "type": "object", + "required": ["from", "recipient", "sender"], + "properties": { + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + }, + "sender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": ["mint"], + "properties": { + "mint": { + "type": "object", + "required": ["minter", "recipient"], + "properties": { + "minter": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": ["burn"], + "properties": { + "burn": { + "type": "object", + "required": ["burner", "owner"], + "properties": { + "burner": { + "$ref": "#/definitions/HumanAddr" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": ["deposit"], + "properties": { + "deposit": { + "type": "object" + } + } + }, + { + "type": "object", + "required": ["redeem"], + "properties": { + "redeem": { + "type": "object" + } + } + } + ] + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/external/snip20-base/schema/query_msg.json b/contracts/external/snip20-base/schema/query_msg.json new file mode 100644 index 0000000..6979c18 --- /dev/null +++ b/contracts/external/snip20-base/schema/query_msg.json @@ -0,0 +1,402 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "token_config" + ], + "properties": { + "token_config": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "contract_status" + ], + "properties": { + "contract_status": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "exchange_rate" + ], + "properties": { + "exchange_rate": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "allowance" + ], + "properties": { + "allowance": { + "type": "object", + "required": [ + "key", + "owner", + "spender" + ], + "properties": { + "key": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "address", + "key" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "key": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "transfer_history" + ], + "properties": { + "transfer_history": { + "type": "object", + "required": [ + "address", + "key", + "page_size" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "key": { + "type": "string" + }, + "page": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "page_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } + }, + { + "type": "object", + "required": [ + "transaction_history" + ], + "properties": { + "transaction_history": { + "type": "object", + "required": [ + "address", + "key", + "page_size" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + }, + "key": { + "type": "string" + }, + "page": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "page_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } + }, + { + "type": "object", + "required": [ + "minters" + ], + "properties": { + "minters": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "with_permit" + ], + "properties": { + "with_permit": { + "type": "object", + "required": [ + "permit", + "query" + ], + "properties": { + "permit": { + "$ref": "#/definitions/Permit" + }, + "query": { + "$ref": "#/definitions/QueryWithPermit" + } + } + } + } + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "HumanAddr": { + "type": "string" + }, + "Permission": { + "type": "string", + "enum": [ + "allowance", + "balance", + "history", + "owner" + ] + }, + "Permit": { + "type": "object", + "required": [ + "params", + "signature" + ], + "properties": { + "params": { + "$ref": "#/definitions/PermitParams" + }, + "signature": { + "$ref": "#/definitions/PermitSignature" + } + } + }, + "PermitParams": { + "type": "object", + "required": [ + "allowed_tokens", + "chain_id", + "permissions", + "permit_name" + ], + "properties": { + "allowed_tokens": { + "type": "array", + "items": { + "$ref": "#/definitions/HumanAddr" + } + }, + "chain_id": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + }, + "permit_name": { + "type": "string" + } + } + }, + "PermitSignature": { + "type": "object", + "required": [ + "pub_key", + "signature" + ], + "properties": { + "pub_key": { + "$ref": "#/definitions/PubKey" + }, + "signature": { + "$ref": "#/definitions/Binary" + } + } + }, + "PubKey": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "description": "ignored, but must be \"tendermint/PubKeySecp256k1\" otherwise the verification will fail", + "type": "string" + }, + "value": { + "description": "Secp256k1 PubKey", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "QueryWithPermit": { + "anyOf": [ + { + "type": "object", + "required": [ + "allowance" + ], + "properties": { + "allowance": { + "type": "object", + "required": [ + "owner", + "spender" + ], + "properties": { + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "transfer_history" + ], + "properties": { + "transfer_history": { + "type": "object", + "required": [ + "page_size" + ], + "properties": { + "page": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "page_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } + }, + { + "type": "object", + "required": [ + "transaction_history" + ], + "properties": { + "transaction_history": { + "type": "object", + "required": [ + "page_size" + ], + "properties": { + "page": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "page_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } + } + ] + } + } +} diff --git a/contracts/external/snip20-base/src/batch.rs b/contracts/external/snip20-base/src/batch.rs new file mode 100644 index 0000000..dbe47fb --- /dev/null +++ b/contracts/external/snip20-base/src/batch.rs @@ -0,0 +1,87 @@ +//! Types used in batch operations + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, Binary, Uint128}; + +pub trait HasDecoy { + fn decoys(&self) -> &Option>; +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct TransferAction { + pub recipient: String, + pub amount: Uint128, + pub memo: Option, + pub decoys: Option>, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct SendAction { + pub recipient: String, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, + pub decoys: Option>, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct TransferFromAction { + pub owner: String, + pub recipient: String, + pub amount: Uint128, + pub memo: Option, + pub decoys: Option>, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct SendFromAction { + pub owner: String, + pub recipient: String, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, + pub decoys: Option>, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct MintAction { + pub recipient: String, + pub amount: Uint128, + pub memo: Option, + pub decoys: Option>, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct BurnFromAction { + pub owner: String, + pub amount: Uint128, + pub memo: Option, + pub decoys: Option>, +} + +macro_rules! impl_decoyable { + ($struct:ty) => { + impl HasDecoy for $struct { + fn decoys(&self) -> &Option> { + &self.decoys + } + } + }; +} + +impl_decoyable!(BurnFromAction); +impl_decoyable!(MintAction); +impl_decoyable!(SendFromAction); +impl_decoyable!(TransferFromAction); +impl_decoyable!(TransferAction); +impl_decoyable!(SendAction); diff --git a/contracts/external/snip20-base/src/contract.rs b/contracts/external/snip20-base/src/contract.rs new file mode 100644 index 0000000..1f3084b --- /dev/null +++ b/contracts/external/snip20-base/src/contract.rs @@ -0,0 +1,6338 @@ +/// This contract implements SNIP-20 standard: +/// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md +use cosmwasm_std::{ + to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, + MessageInfo, Response, StdError, StdResult, Storage, Uint128, +}; +use rand::RngCore; +use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; +use secret_toolkit::utils::{pad_handle_result, pad_query_result}; +use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; +use secret_toolkit_crypto::{sha_256, Prng, SHA256_HASH_SIZE}; + +use crate::batch; +use crate::msg::{ + AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, Decoyable, ExecuteAnswer, + ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, +}; +use crate::receiver::Snip20ReceiveMsg; +use crate::state::{ + safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, + CONFIG, CONTRACT_STATUS, TOTAL_SUPPLY, +}; +use crate::transaction_history::{ + store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredExtendedTx, + StoredLegacyTransfer, +}; + +/// We make sure that responses from `handle` are padded to a multiple of this size. +pub const RESPONSE_BLOCK_SIZE: usize = 256; +pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits"; + +// #[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + // Check name, symbol, decimals + if !is_valid_name(&msg.name) { + return Err(StdError::generic_err( + "Name is not in the expected format (3-30 UTF-8 bytes)", + )); + } + if !is_valid_symbol(&msg.symbol) { + return Err(StdError::generic_err( + "Ticker symbol is not in expected format [A-Z]{3,20}", + )); + } + if msg.decimals > 18 { + return Err(StdError::generic_err("Decimals must not exceed 18")); + } + + let init_config = msg.config.unwrap_or_default(); + + let admin = match msg.admin { + Some(admin_addr) => deps.api.addr_validate(admin_addr.as_str())?, + None => info.sender, + }; + + let mut total_supply: u128 = 0; + + let prng_seed_hashed = sha_256(&msg.prng_seed.0); + PrngStore::save(deps.storage, prng_seed_hashed)?; + + { + let initial_balances = msg.initial_balances.unwrap_or_default(); + for balance in initial_balances { + let amount = balance.amount.u128(); + let balance_address = deps.api.addr_validate(balance.address.as_str())?; + // Here amount is also the amount to be added because the account has no prior balance + BalancesStore::update_balance( + deps.storage, + &balance_address, + amount, + true, + "", + &None, + &None, + )?; + + if let Some(new_total_supply) = total_supply.checked_add(amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err( + "The sum of all initial balances exceeds the maximum possible total supply", + )); + } + + store_mint( + deps.storage, + admin.clone(), + balance_address, + balance.amount, + msg.symbol.clone(), + Some("Initial Balance".to_string()), + &env.block, + &None, + &None, + )?; + } + } + + let supported_denoms = match msg.supported_denoms { + None => vec![], + Some(x) => x, + }; + + CONFIG.save( + deps.storage, + &Config { + name: msg.name, + symbol: msg.symbol, + decimals: msg.decimals, + admin: admin.clone(), + total_supply_is_public: init_config.public_total_supply(), + deposit_is_enabled: init_config.deposit_enabled(), + redeem_is_enabled: init_config.redeem_enabled(), + mint_is_enabled: init_config.mint_enabled(), + burn_is_enabled: init_config.burn_enabled(), + contract_address: env.contract.address.clone(), + supported_denoms, + can_modify_denoms: init_config.can_modify_denoms(), + }, + )?; + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + CONTRACT_STATUS.save(deps.storage, &ContractStatusLevel::NormalRun)?; + let minters = if init_config.mint_enabled() { + Vec::from([admin]) + } else { + Vec::new() + }; + MintersStore::save(deps.storage, minters)?; + + ViewingKey::set_seed(deps.storage, &prng_seed_hashed); + + Ok(Response::default()) +} + +fn get_address_position( + store: &mut dyn Storage, + decoys_size: usize, + entropy: &[u8; SHA256_HASH_SIZE], +) -> StdResult { + let mut rng = Prng::new(&PrngStore::load(store)?, entropy); + + let mut new_contract_entropy = [0u8; 20]; + rng.rng.fill_bytes(&mut new_contract_entropy); + + let new_prng_seed = sha_256(&new_contract_entropy); + PrngStore::save(store, new_prng_seed)?; + + // decoys_size is also an accepted output which means: set the account balance after you've set decoys' balanace + Ok(rng.rng.next_u64() as usize % (decoys_size + 1)) +} + +// #[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + let contract_status = CONTRACT_STATUS.load(deps.storage)?; + + let mut account_random_pos: Option = None; + + let entropy = match msg.clone().get_entropy() { + None => [0u8; SHA256_HASH_SIZE], + Some(e) => sha_256(&e.0), + }; + + let decoys_size = msg.get_minimal_decoys_size(); + if decoys_size != 0 { + account_random_pos = Some(get_address_position(deps.storage, decoys_size, &entropy)?); + } + + match contract_status { + ContractStatusLevel::StopAll | ContractStatusLevel::StopAllButRedeems => { + let response = match msg { + ExecuteMsg::SetContractStatus { level, .. } => { + set_contract_status(deps, info, level) + } + ExecuteMsg::Redeem { + amount, + denom, + decoys, + .. + } if contract_status == ContractStatusLevel::StopAllButRedeems => { + try_redeem(deps, env, info, amount, denom, decoys, account_random_pos) + } + _ => Err(StdError::generic_err( + "This contract is stopped and this action is not allowed", + )), + }; + return pad_handle_result(response, RESPONSE_BLOCK_SIZE); + } + ContractStatusLevel::NormalRun => {} // If it's a normal run just continue + } + + let response = match msg.clone() { + // Native + ExecuteMsg::Deposit { decoys, .. } => { + try_deposit(deps, env, info, decoys, account_random_pos) + } + ExecuteMsg::Redeem { + amount, + denom, + decoys, + .. + } => try_redeem(deps, env, info, amount, denom, decoys, account_random_pos), + + // Base + ExecuteMsg::Transfer { + recipient, + amount, + memo, + decoys, + .. + } => try_transfer( + deps, + env, + info, + recipient, + amount, + memo, + decoys, + account_random_pos, + ), + ExecuteMsg::Send { + recipient, + recipient_code_hash, + amount, + msg, + memo, + decoys, + .. + } => try_send( + deps, + env, + info, + recipient, + recipient_code_hash, + amount, + memo, + msg, + decoys, + account_random_pos, + ), + ExecuteMsg::BatchTransfer { actions, .. } => { + try_batch_transfer(deps, env, info, actions, account_random_pos) + } + ExecuteMsg::BatchSend { actions, .. } => { + try_batch_send(deps, env, info, actions, account_random_pos) + } + ExecuteMsg::Burn { + amount, + memo, + decoys, + .. + } => try_burn(deps, env, info, amount, memo, decoys, account_random_pos), + ExecuteMsg::RegisterReceive { code_hash, .. } => { + try_register_receive(deps, info, code_hash) + } + ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy), + ExecuteMsg::SetViewingKey { key, .. } => try_set_key(deps, info, key), + + // Allowance + ExecuteMsg::IncreaseAllowance { + spender, + amount, + expiration, + .. + } => try_increase_allowance(deps, env, info, spender, amount, expiration), + ExecuteMsg::DecreaseAllowance { + spender, + amount, + expiration, + .. + } => try_decrease_allowance(deps, env, info, spender, amount, expiration), + ExecuteMsg::TransferFrom { + owner, + recipient, + amount, + memo, + decoys, + .. + } => try_transfer_from( + deps, + &env, + info, + owner, + recipient, + amount, + memo, + decoys, + account_random_pos, + ), + ExecuteMsg::SendFrom { + owner, + recipient, + recipient_code_hash, + amount, + msg, + memo, + decoys, + .. + } => try_send_from( + deps, + env, + &info, + owner, + recipient, + recipient_code_hash, + amount, + memo, + msg, + decoys, + account_random_pos, + ), + ExecuteMsg::BatchTransferFrom { actions, .. } => { + try_batch_transfer_from(deps, &env, info, actions, account_random_pos) + } + ExecuteMsg::BatchSendFrom { actions, .. } => { + try_batch_send_from(deps, env, &info, actions, account_random_pos) + } + ExecuteMsg::BurnFrom { + owner, + amount, + memo, + decoys, + .. + } => try_burn_from( + deps, + &env, + info, + owner, + amount, + memo, + decoys, + account_random_pos, + ), + ExecuteMsg::BatchBurnFrom { actions, .. } => { + try_batch_burn_from(deps, &env, info, actions, account_random_pos) + } + + // Mint + ExecuteMsg::Mint { + recipient, + amount, + memo, + decoys, + .. + } => try_mint( + deps, + env, + info, + recipient, + amount, + memo, + decoys, + account_random_pos, + ), + ExecuteMsg::BatchMint { actions, .. } => { + try_batch_mint(deps, env, info, actions, account_random_pos) + } + + // Other + ExecuteMsg::ChangeAdmin { address, .. } => change_admin(deps, info, address), + ExecuteMsg::SetContractStatus { level, .. } => set_contract_status(deps, info, level), + ExecuteMsg::AddMinters { minters, .. } => add_minters(deps, info, minters), + ExecuteMsg::RemoveMinters { minters, .. } => remove_minters(deps, info, minters), + ExecuteMsg::SetMinters { minters, .. } => set_minters(deps, info, minters), + ExecuteMsg::RevokePermit { permit_name, .. } => revoke_permit(deps, info, permit_name), + ExecuteMsg::AddSupportedDenoms { denoms, .. } => add_supported_denoms(deps, info, denoms), + ExecuteMsg::RemoveSupportedDenoms { denoms, .. } => { + remove_supported_denoms(deps, info, denoms) + } + }; + + pad_handle_result(response, RESPONSE_BLOCK_SIZE) +} + +// #[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + pad_query_result( + match msg { + QueryMsg::TokenInfo {} => query_token_info(deps.storage), + QueryMsg::TokenConfig {} => query_token_config(deps.storage), + QueryMsg::ContractStatus {} => query_contract_status(deps.storage), + QueryMsg::ExchangeRate {} => query_exchange_rate(deps.storage), + QueryMsg::Minters { .. } => query_minters(deps), + QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), + _ => viewing_keys_queries(deps, msg), + }, + RESPONSE_BLOCK_SIZE, + ) +} + +fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result { + // Validate permit content + let token_address = CONFIG.load(deps.storage)?.contract_address; + + let account = secret_toolkit::permit::validate( + deps, + PREFIX_REVOKED_PERMITS, + &permit, + token_address.into_string(), + None, + )?; + + // Permit validated! We can now execute the query. + match query { + QueryWithPermit::Balance {} => { + if !permit.check_permission(&TokenPermissions::Balance) { + return Err(StdError::generic_err(format!( + "No permission to query balance, got permissions {:?}", + permit.params.permissions + ))); + } + + query_balance(deps, account) + } + QueryWithPermit::TransferHistory { + page, + page_size, + should_filter_decoys, + } => { + if !permit.check_permission(&TokenPermissions::History) { + return Err(StdError::generic_err(format!( + "No permission to query history, got permissions {:?}", + permit.params.permissions + ))); + } + + query_transfers( + deps, + account, + page.unwrap_or(0), + page_size, + should_filter_decoys, + ) + } + QueryWithPermit::TransactionHistory { + page, + page_size, + should_filter_decoys, + } => { + if !permit.check_permission(&TokenPermissions::History) { + return Err(StdError::generic_err(format!( + "No permission to query history, got permissions {:?}", + permit.params.permissions + ))); + } + + query_transactions( + deps, + account, + page.unwrap_or(0), + page_size, + should_filter_decoys, + ) + } + QueryWithPermit::Allowance { owner, spender } => { + if !permit.check_permission(&TokenPermissions::Allowance) { + return Err(StdError::generic_err(format!( + "No permission to query allowance, got permissions {:?}", + permit.params.permissions + ))); + } + + if account != owner && account != spender { + return Err(StdError::generic_err(format!( + "Cannot query allowance. Requires permit for either owner {:?} or spender {:?}, got permit for {:?}", + owner.as_str(), spender.as_str(), account.as_str() + ))); + } + + query_allowance(deps, owner, spender) + } + QueryWithPermit::AllowancesGiven { + owner, + page, + page_size, + } => { + if account != owner { + return Err(StdError::generic_err( + "Cannot query allowance. Requires permit for owner", + )); + } + + // we really should add a check_permission(s) function.. an owner permit should + // just give you permissions to do everything + if !permit.check_permission(&TokenPermissions::Allowance) + && !permit.check_permission(&TokenPermissions::Owner) + { + return Err(StdError::generic_err(format!( + "No permission to query all allowances, got permissions {:?}", + permit.params.permissions + ))); + } + query_allowances_given(deps, account, page.unwrap_or(0), page_size) + } + QueryWithPermit::AllowancesReceived { + spender, + page, + page_size, + } => { + if account != spender { + return Err(StdError::generic_err( + "Cannot query allowance. Requires permit for spender", + )); + } + + if !permit.check_permission(&TokenPermissions::Allowance) + && !permit.check_permission(&TokenPermissions::Owner) + { + return Err(StdError::generic_err(format!( + "No permission to query all allowed, got permissions {:?}", + permit.params.permissions + ))); + } + query_allowances_received(deps, account, page.unwrap_or(0), page_size) + } + } +} + +pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult { + let (addresses, key) = msg.get_validation_params(deps.api)?; + + for address in addresses { + let result = ViewingKey::check(deps.storage, address.as_str(), key.as_str()); + if result.is_ok() { + return match msg { + // Base + QueryMsg::Balance { address, .. } => query_balance(deps, address), + QueryMsg::TransferHistory { + address, + page, + page_size, + should_filter_decoys, + .. + } => query_transfers( + deps, + address, + page.unwrap_or(0), + page_size, + should_filter_decoys, + ), + QueryMsg::TransactionHistory { + address, + page, + page_size, + should_filter_decoys, + .. + } => query_transactions( + deps, + address, + page.unwrap_or(0), + page_size, + should_filter_decoys, + ), + QueryMsg::Allowance { owner, spender, .. } => query_allowance(deps, owner, spender), + QueryMsg::AllowancesGiven { + owner, + page, + page_size, + .. + } => query_allowances_given(deps, owner, page.unwrap_or(0), page_size), + QueryMsg::AllowancesReceived { + spender, + page, + page_size, + .. + } => query_allowances_received(deps, spender, page.unwrap_or(0), page_size), + _ => panic!("This query type does not require authentication"), + }; + } + } + + to_binary(&QueryAnswer::ViewingKeyError { + msg: "Wrong viewing key for this address or viewing key not set".to_string(), + }) +} + +fn query_exchange_rate(storage: &dyn Storage) -> StdResult { + let constants = CONFIG.load(storage)?; + + if constants.deposit_is_enabled || constants.redeem_is_enabled { + let rate: Uint128; + let denom: String; + // if token has more decimals than SCRT, you get magnitudes of SCRT per token + if constants.decimals >= 6 { + rate = Uint128::new(10u128.pow(constants.decimals as u32 - 6)); + denom = "SCRT".to_string(); + // if token has less decimals, you get magnitudes token for SCRT + } else { + rate = Uint128::new(10u128.pow(6 - constants.decimals as u32)); + denom = constants.symbol; + } + return to_binary(&QueryAnswer::ExchangeRate { rate, denom }); + } + to_binary(&QueryAnswer::ExchangeRate { + rate: Uint128::zero(), + denom: String::new(), + }) +} + +fn query_token_info(storage: &dyn Storage) -> StdResult { + let constants = CONFIG.load(storage)?; + + let total_supply = if constants.total_supply_is_public { + Some(Uint128::new(TOTAL_SUPPLY.load(storage)?)) + } else { + None + }; + + to_binary(&QueryAnswer::TokenInfo { + name: constants.name, + symbol: constants.symbol, + decimals: constants.decimals, + total_supply, + }) +} + +fn query_token_config(storage: &dyn Storage) -> StdResult { + let constants = CONFIG.load(storage)?; + + to_binary(&QueryAnswer::TokenConfig { + public_total_supply: constants.total_supply_is_public, + deposit_enabled: constants.deposit_is_enabled, + redeem_enabled: constants.redeem_is_enabled, + mint_enabled: constants.mint_is_enabled, + burn_enabled: constants.burn_is_enabled, + supported_denoms: constants.supported_denoms, + }) +} + +fn query_contract_status(storage: &dyn Storage) -> StdResult { + let contract_status = CONTRACT_STATUS.load(storage)?; + + to_binary(&QueryAnswer::ContractStatus { + status: contract_status, + }) +} + +pub fn query_transfers( + deps: Deps, + account: String, + page: u32, + page_size: u32, + should_filter_decoys: bool, +) -> StdResult { + // Notice that if query_transfers() was called by a viewking-key call, the address of 'account' + // has already been validated. + // The address of 'account' should not be validated if query_transfers() was called by a permit + // call, for compatibility with non-Secret addresses. + let account = Addr::unchecked(account); + + let (txs, total) = StoredLegacyTransfer::get_transfers( + deps.storage, + account, + page, + page_size, + should_filter_decoys, + )?; + + let result = QueryAnswer::TransferHistory { + txs, + total: Some(total), + }; + to_binary(&result) +} + +pub fn query_transactions( + deps: Deps, + account: String, + page: u32, + page_size: u32, + should_filter_decoys: bool, +) -> StdResult { + // Notice that if query_transactions() was called by a viewking-key call, the address of + // 'account' has already been validated. + // The address of 'account' should not be validated if query_transactions() was called by a + // permit call, for compatibility with non-Secret addresses. + let account = Addr::unchecked(account); + + let (txs, total) = + StoredExtendedTx::get_txs(deps.storage, account, page, page_size, should_filter_decoys)?; + + let result = QueryAnswer::TransactionHistory { + txs, + total: Some(total), + }; + to_binary(&result) +} + +pub fn query_balance(deps: Deps, account: String) -> StdResult { + // Notice that if query_balance() was called by a viewking-key call, the address of 'account' + // has already been validated. + // The address of 'account' should not be validated if query_balance() was called by a permit + // call, for compatibility with non-Secret addresses. + let account = Addr::unchecked(account); + + let amount = Uint128::new(BalancesStore::load(deps.storage, &account)); + let response = QueryAnswer::Balance { amount }; + to_binary(&response) +} + +fn query_minters(deps: Deps) -> StdResult { + let minters = MintersStore::load(deps.storage)?; + + let response = QueryAnswer::Minters { minters }; + to_binary(&response) +} + +fn change_admin(deps: DepsMut, info: MessageInfo, address: String) -> StdResult { + let address = deps.api.addr_validate(address.as_str())?; + + let mut constants = CONFIG.load(deps.storage)?; + check_if_admin(&constants.admin, &info.sender)?; + + constants.admin = address; + CONFIG.save(deps.storage, &constants)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ChangeAdmin { status: Success })?)) +} + +fn add_supported_denoms( + deps: DepsMut, + info: MessageInfo, + denoms: Vec, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + check_if_admin(&config.admin, &info.sender)?; + if !config.can_modify_denoms { + return Err(StdError::generic_err( + "Cannot modify denoms for this contract", + )); + } + + for denom in denoms.iter() { + if !config.supported_denoms.contains(denom) { + config.supported_denoms.push(denom.clone()); + } + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddSupportedDenoms { + status: Success, + })?), + ) +} + +fn remove_supported_denoms( + deps: DepsMut, + info: MessageInfo, + denoms: Vec, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + check_if_admin(&config.admin, &info.sender)?; + if !config.can_modify_denoms { + return Err(StdError::generic_err( + "Cannot modify denoms for this contract", + )); + } + + for denom in denoms.iter() { + config.supported_denoms.retain(|x| x != denom); + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemoveSupportedDenoms { + status: Success, + })?), + ) +} + +#[allow(clippy::too_many_arguments)] +fn try_mint_impl( + deps: &mut DepsMut, + minter: Addr, + recipient: Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &cosmwasm_std::BlockInfo, + decoys: Option>, + account_random_pos: Option, +) -> StdResult<()> { + let raw_amount = amount.u128(); + + BalancesStore::update_balance( + deps.storage, + &recipient, + raw_amount, + true, + "", + &decoys, + &account_random_pos, + )?; + + store_mint( + deps.storage, + minter, + recipient, + amount, + denom, + memo, + block, + &decoys, + &account_random_pos, + )?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_mint( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: String, + amount: Uint128, + memo: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let recipient = deps.api.addr_validate(recipient.as_str())?; + + let constants = CONFIG.load(deps.storage)?; + + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + let minters = MintersStore::load(deps.storage)?; + if !minters.contains(&info.sender) { + return Err(StdError::generic_err( + "Minting is allowed to minter accounts only", + )); + } + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + let minted_amount = safe_add(&mut total_supply, amount.u128()); + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency + try_mint_impl( + &mut deps, + info.sender, + recipient, + Uint128::new(minted_amount), + constants.symbol, + memo, + &env.block, + decoys, + account_random_pos, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?)) +} + +fn try_batch_mint( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, + account_random_pos: Option, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + let minters = MintersStore::load(deps.storage)?; + if !minters.contains(&info.sender) { + return Err(StdError::generic_err( + "Minting is allowed to minter accounts only", + )); + } + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + + // Quick loop to check that the total of amounts is valid + for action in actions { + let actual_amount = safe_add(&mut total_supply, action.amount.u128()); + + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + try_mint_impl( + &mut deps, + info.sender.clone(), + recipient, + Uint128::new(actual_amount), + constants.symbol.clone(), + action.memo, + &env.block, + action.decoys, + account_random_pos, + )?; + } + + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?)) +} + +pub fn try_set_key(deps: DepsMut, info: MessageInfo, key: String) -> StdResult { + ViewingKey::set(deps.storage, info.sender.as_str(), key.as_str()); + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { + status: Success, + })?), + ) +} + +pub fn try_create_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + entropy: String, +) -> StdResult { + let key = ViewingKey::create( + deps.storage, + &info, + &env, + info.sender.as_str(), + entropy.as_ref(), + ); + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key })?)) +} + +fn set_contract_status( + deps: DepsMut, + info: MessageInfo, + status_level: ContractStatusLevel, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + check_if_admin(&constants.admin, &info.sender)?; + + CONTRACT_STATUS.save(deps.storage, &status_level)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus { + status: Success, + })?), + ) +} + +pub fn query_allowance(deps: Deps, owner: String, spender: String) -> StdResult { + // Notice that if query_allowance() was called by a viewing-key call, the addresses of 'owner' + // and 'spender' have already been validated. + // The addresses of 'owner' and 'spender' should not be validated if query_allowance() was + // called by a permit call, for compatibility with non-Secret addresses. + let owner = Addr::unchecked(owner); + let spender = Addr::unchecked(spender); + + let allowance = AllowancesStore::load(deps.storage, &owner, &spender); + + let response = QueryAnswer::Allowance { + owner, + spender, + allowance: Uint128::new(allowance.amount), + expiration: allowance.expiration, + }; + to_binary(&response) +} + +pub fn query_allowances_given( + deps: Deps, + owner: String, + page: u32, + page_size: u32, +) -> StdResult { + // Notice that if query_all_allowances_given() was called by a viewing-key call, + // the address of 'owner' has already been validated. + // The addresses of 'owner' should not be validated if query_all_allowances_given() was + // called by a permit call, for compatibility with non-Secret addresses. + let owner = Addr::unchecked(owner); + + let all_allowances = + AllowancesStore::all_allowances(deps.storage, &owner, page, page_size).unwrap_or_default(); + + let allowances_result = all_allowances + .into_iter() + .map(|(spender, allowance)| AllowanceGivenResult { + spender, + allowance: Uint128::from(allowance.amount), + expiration: allowance.expiration, + }) + .collect(); + + let response = QueryAnswer::AllowancesGiven { + owner: owner.clone(), + allowances: allowances_result, + count: AllowancesStore::num_allowances(deps.storage, &owner), + }; + to_binary(&response) +} + +pub fn query_allowances_received( + deps: Deps, + spender: String, + page: u32, + page_size: u32, +) -> StdResult { + // Notice that if query_all_allowances_received() was called by a viewing-key call, + // the address of 'spender' has already been validated. + // The addresses of 'spender' should not be validated if query_all_allowances_received() was + // called by a permit call, for compatibility with non-Secret addresses. + let spender = Addr::unchecked(spender); + + let all_allowed = + AllowancesStore::all_allowed(deps.storage, &spender, page, page_size).unwrap_or_default(); + + let allowances = all_allowed + .into_iter() + .map(|(owner, allowance)| AllowanceReceivedResult { + owner, + allowance: Uint128::from(allowance.amount), + expiration: allowance.expiration, + }) + .collect(); + + let response = QueryAnswer::AllowancesReceived { + spender: spender.clone(), + allowances, + count: AllowancesStore::num_allowed(deps.storage, &spender), + }; + to_binary(&response) +} + +fn try_deposit( + deps: DepsMut, + env: Env, + info: MessageInfo, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + + let mut amount = Uint128::zero(); + + for coin in &info.funds { + if constants.supported_denoms.contains(&coin.denom) { + amount += coin.amount + } else { + return Err(StdError::generic_err(format!( + "Tried to deposit an unsupported coin {}", + coin.denom + ))); + } + } + + if amount.is_zero() { + return Err(StdError::generic_err("No funds were sent to be deposited")); + } + + let mut raw_amount = amount.u128(); + + if !constants.deposit_is_enabled { + return Err(StdError::generic_err( + "Deposit functionality is not enabled.", + )); + } + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + raw_amount = safe_add(&mut total_supply, raw_amount); + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + let sender_address = &info.sender; + + BalancesStore::update_balance( + deps.storage, + sender_address, + raw_amount, + true, + "", + &decoys, + &account_random_pos, + )?; + + store_deposit( + deps.storage, + sender_address, + Uint128::new(raw_amount), + "uscrt".to_string(), + &env.block, + &decoys, + &account_random_pos, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?)) +} + +fn try_redeem( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, + denom: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + if !constants.redeem_is_enabled { + return Err(StdError::generic_err( + "Redeem functionality is not enabled for this token.", + )); + } + + // if denom is none and there is only 1 supported denom then we don't need to check anything + let withdraw_denom = if denom.is_none() && constants.supported_denoms.len() == 1 { + constants.supported_denoms.first().unwrap().clone() + // if denom is specified make sure it's on the list before trying to withdraw with it + } else if denom.is_some() && constants.supported_denoms.contains(denom.as_ref().unwrap()) { + denom.unwrap() + // error handling + } else if denom.is_none() { + return Err(StdError::generic_err( + "Tried to redeem without specifying denom, but multiple coins are supported", + )); + } else { + return Err(StdError::generic_err( + "Tried to redeem for an unsupported coin", + )); + }; + + let sender_address = &info.sender; + let amount_raw = amount.u128(); + + BalancesStore::update_balance( + deps.storage, + sender_address, + amount_raw, + false, + "redeem", + &decoys, + &account_random_pos, + )?; + + let total_supply = TOTAL_SUPPLY.load(deps.storage)?; + if let Some(total_supply) = total_supply.checked_sub(amount_raw) { + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + } else { + return Err(StdError::generic_err( + "You are trying to redeem more tokens than what is available in the total supply", + )); + } + + let token_reserve = deps + .querier + .query_balance(&env.contract.address, &withdraw_denom)? + .amount; + if amount > token_reserve { + return Err(StdError::generic_err(format!( + "You are trying to redeem for more {withdraw_denom} than the contract has in its reserve", + ))); + } + + let withdrawal_coins: Vec = vec![Coin { + denom: withdraw_denom, + amount, + }]; + + store_redeem( + deps.storage, + sender_address, + amount, + constants.symbol, + &env.block, + &decoys, + &account_random_pos, + )?; + + let message = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.clone().into_string(), + amount: withdrawal_coins, + }); + let data = to_binary(&ExecuteAnswer::Redeem { status: Success })?; + let res = Response::new().add_message(message).set_data(data); + Ok(res) +} + +#[allow(clippy::too_many_arguments)] +fn try_transfer_impl( + deps: &mut DepsMut, + sender: &Addr, + recipient: &Addr, + amount: Uint128, + memo: Option, + block: &cosmwasm_std::BlockInfo, + decoys: Option>, + account_random_pos: Option, +) -> StdResult<()> { + perform_transfer( + deps.storage, + sender, + recipient, + amount.u128(), + &decoys, + &account_random_pos, + )?; + + let symbol = CONFIG.load(deps.storage)?.symbol; + store_transfer( + deps.storage, + sender, + sender, + recipient, + amount, + symbol, + memo, + block, + &decoys, + &account_random_pos, + )?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_transfer( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: String, + amount: Uint128, + memo: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let recipient = deps.api.addr_validate(recipient.as_str())?; + + try_transfer_impl( + &mut deps, + &info.sender, + &recipient, + amount, + memo, + &env.block, + decoys, + account_random_pos, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?)) +} + +fn try_batch_transfer( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, + account_random_pos: Option, +) -> StdResult { + for action in actions { + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + try_transfer_impl( + &mut deps, + &info.sender, + &recipient, + action.amount, + action.memo, + &env.block, + action.decoys, + account_random_pos, + )?; + } + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { + status: Success, + })?), + ) +} + +#[allow(clippy::too_many_arguments)] +fn try_add_receiver_api_callback( + storage: &dyn Storage, + messages: &mut Vec, + recipient: Addr, + recipient_code_hash: Option, + msg: Option, + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, +) -> StdResult<()> { + if let Some(receiver_hash) = recipient_code_hash { + let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); + let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; + + messages.push(callback_msg); + return Ok(()); + } + + let receiver_hash = ReceiverHashStore::may_load(storage, &recipient)?; + if let Some(receiver_hash) = receiver_hash { + let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); + let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; + + messages.push(callback_msg); + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_impl( + deps: &mut DepsMut, + messages: &mut Vec, + sender: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, + block: &cosmwasm_std::BlockInfo, + decoys: Option>, + account_random_pos: Option, +) -> StdResult<()> { + try_transfer_impl( + deps, + &sender, + &recipient, + amount, + memo.clone(), + block, + decoys, + account_random_pos, + )?; + + try_add_receiver_api_callback( + deps.storage, + messages, + recipient, + recipient_code_hash, + msg, + sender.clone(), + sender, + amount, + memo, + )?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_send( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let recipient = deps.api.addr_validate(recipient.as_str())?; + + let mut messages = vec![]; + try_send_impl( + &mut deps, + &mut messages, + info.sender, + recipient, + recipient_code_hash, + amount, + memo, + msg, + &env.block, + decoys, + account_random_pos, + )?; + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?)) +} + +fn try_batch_send( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, + account_random_pos: Option, +) -> StdResult { + let mut messages = vec![]; + for action in actions { + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + try_send_impl( + &mut deps, + &mut messages, + info.sender.clone(), + recipient, + action.recipient_code_hash, + action.amount, + action.memo, + action.msg, + &env.block, + action.decoys, + account_random_pos, + )?; + } + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?)) +} + +fn try_register_receive( + deps: DepsMut, + info: MessageInfo, + code_hash: String, +) -> StdResult { + ReceiverHashStore::save(deps.storage, &info.sender, code_hash)?; + + let data = to_binary(&ExecuteAnswer::RegisterReceive { status: Success })?; + Ok(Response::new() + .add_attribute("register_status", "success") + .set_data(data)) +} + +fn insufficient_allowance(allowance: u128, required: u128) -> StdError { + StdError::generic_err(format!( + "insufficient allowance: allowance={allowance}, required={required}", + )) +} + +fn use_allowance( + storage: &mut dyn Storage, + env: &Env, + owner: &Addr, + spender: &Addr, + amount: u128, +) -> StdResult<()> { + let mut allowance = AllowancesStore::load(storage, owner, spender); + + if allowance.is_expired_at(&env.block) { + return Err(insufficient_allowance(0, amount)); + } + if let Some(new_allowance) = allowance.amount.checked_sub(amount) { + allowance.amount = new_allowance; + } else { + return Err(insufficient_allowance(allowance.amount, amount)); + } + + AllowancesStore::save(storage, owner, spender, &allowance)?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_transfer_from_impl( + deps: &mut DepsMut, + env: &Env, + spender: &Addr, + owner: &Addr, + recipient: &Addr, + amount: Uint128, + memo: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult<()> { + let raw_amount = amount.u128(); + + use_allowance(deps.storage, env, owner, spender, raw_amount)?; + + perform_transfer( + deps.storage, + owner, + recipient, + raw_amount, + &decoys, + &account_random_pos, + )?; + + let symbol = CONFIG.load(deps.storage)?.symbol; + store_transfer( + deps.storage, + owner, + spender, + recipient, + amount, + symbol, + memo, + &env.block, + &decoys, + &account_random_pos, + )?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_transfer_from( + mut deps: DepsMut, + env: &Env, + info: MessageInfo, + owner: String, + recipient: String, + amount: Uint128, + memo: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let owner = deps.api.addr_validate(owner.as_str())?; + let recipient = deps.api.addr_validate(recipient.as_str())?; + try_transfer_from_impl( + &mut deps, + env, + &info.sender, + &owner, + &recipient, + amount, + memo, + decoys, + account_random_pos, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?)) +} + +fn try_batch_transfer_from( + mut deps: DepsMut, + env: &Env, + info: MessageInfo, + actions: Vec, + account_random_pos: Option, +) -> StdResult { + for action in actions { + let owner = deps.api.addr_validate(action.owner.as_str())?; + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + try_transfer_from_impl( + &mut deps, + env, + &info.sender, + &owner, + &recipient, + action.amount, + action.memo, + action.decoys, + account_random_pos, + )?; + } + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransferFrom { + status: Success, + })?), + ) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_from_impl( + deps: &mut DepsMut, + env: Env, + info: &MessageInfo, + messages: &mut Vec, + owner: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult<()> { + let spender = info.sender.clone(); + try_transfer_from_impl( + deps, + &env, + &spender, + &owner, + &recipient, + amount, + memo.clone(), + decoys, + account_random_pos, + )?; + + try_add_receiver_api_callback( + deps.storage, + messages, + recipient, + recipient_code_hash, + msg, + info.sender.clone(), + owner, + amount, + memo, + )?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_from( + mut deps: DepsMut, + env: Env, + info: &MessageInfo, + owner: String, + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let owner = deps.api.addr_validate(owner.as_str())?; + let recipient = deps.api.addr_validate(recipient.as_str())?; + let mut messages = vec![]; + try_send_from_impl( + &mut deps, + env, + info, + &mut messages, + owner, + recipient, + recipient_code_hash, + amount, + memo, + msg, + decoys, + account_random_pos, + )?; + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?)) +} + +fn try_batch_send_from( + mut deps: DepsMut, + env: Env, + info: &MessageInfo, + actions: Vec, + account_random_pos: Option, +) -> StdResult { + let mut messages = vec![]; + + for action in actions { + let owner = deps.api.addr_validate(action.owner.as_str())?; + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + try_send_from_impl( + &mut deps, + env.clone(), + info, + &mut messages, + owner, + recipient, + action.recipient_code_hash, + action.amount, + action.memo, + action.msg, + action.decoys, + account_random_pos, + )?; + } + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { + status: Success, + })?)) +} + +#[allow(clippy::too_many_arguments)] +fn try_burn_from( + deps: DepsMut, + env: &Env, + info: MessageInfo, + owner: String, + amount: Uint128, + memo: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let owner = deps.api.addr_validate(owner.as_str())?; + let constants = CONFIG.load(deps.storage)?; + if !constants.burn_is_enabled { + return Err(StdError::generic_err( + "Burn functionality is not enabled for this token.", + )); + } + + let raw_amount = amount.u128(); + use_allowance(deps.storage, env, &owner, &info.sender, raw_amount)?; + + BalancesStore::update_balance( + deps.storage, + &owner, + raw_amount, + false, + "burn", + &decoys, + &account_random_pos, + )?; + + // remove from supply + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + + if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err( + "You're trying to burn more than is available in the total supply", + )); + } + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + store_burn( + deps.storage, + owner, + info.sender, + amount, + constants.symbol, + memo, + &env.block, + &decoys, + &account_random_pos, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?)) +} + +fn try_batch_burn_from( + deps: DepsMut, + env: &Env, + info: MessageInfo, + actions: Vec, + account_random_pos: Option, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + if !constants.burn_is_enabled { + return Err(StdError::generic_err( + "Burn functionality is not enabled for this token.", + )); + } + + let spender = info.sender; + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + + for action in actions { + let owner = deps.api.addr_validate(action.owner.as_str())?; + let amount = action.amount.u128(); + use_allowance(deps.storage, env, &owner, &spender, amount)?; + + BalancesStore::update_balance( + deps.storage, + &owner, + amount, + false, + "burn", + &action.decoys, + &account_random_pos, + )?; + + // remove from supply + if let Some(new_total_supply) = total_supply.checked_sub(amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err(format!( + "You're trying to burn more than is available in the total supply: {action:?}", + ))); + } + + store_burn( + deps.storage, + owner, + spender.clone(), + action.amount, + constants.symbol.clone(), + action.memo, + &env.block, + &action.decoys, + &account_random_pos, + )?; + } + + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BatchBurnFrom { + status: Success, + })?), + ) +} + +fn try_increase_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: String, + amount: Uint128, + expiration: Option, +) -> StdResult { + let spender = deps.api.addr_validate(spender.as_str())?; + let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); + + // If the previous allowance has expired, reset the allowance. + // Without this users can take advantage of an expired allowance given to + // them long ago. + if allowance.is_expired_at(&env.block) { + allowance.amount = amount.u128(); + allowance.expiration = None; + } else { + allowance.amount = allowance.amount.saturating_add(amount.u128()); + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + let new_amount = allowance.amount; + AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { + owner: info.sender, + spender, + allowance: Uint128::from(new_amount), + })?), + ) +} + +fn try_decrease_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: String, + amount: Uint128, + expiration: Option, +) -> StdResult { + let spender = deps.api.addr_validate(spender.as_str())?; + let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); + + // If the previous allowance has expired, reset the allowance. + // Without this users can take advantage of an expired allowance given to + // them long ago. + if allowance.is_expired_at(&env.block) { + allowance.amount = 0; + allowance.expiration = None; + } else { + allowance.amount = allowance.amount.saturating_sub(amount.u128()); + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + let new_amount = allowance.amount; + AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { + owner: info.sender, + spender, + allowance: Uint128::from(new_amount), + })?), + ) +} + +fn add_minters( + deps: DepsMut, + info: MessageInfo, + minters_to_add: Vec, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + check_if_admin(&constants.admin, &info.sender)?; + + let minters_to_add: Vec = minters_to_add + .iter() + .map(|minter| deps.api.addr_validate(minter.as_str()).unwrap()) + .collect(); + MintersStore::add_minters(deps.storage, minters_to_add)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddMinters { status: Success })?)) +} + +fn remove_minters( + deps: DepsMut, + info: MessageInfo, + minters_to_remove: Vec, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + check_if_admin(&constants.admin, &info.sender)?; + + let minters_to_remove: StdResult> = minters_to_remove + .iter() + .map(|minter| deps.api.addr_validate(minter.as_str())) + .collect(); + MintersStore::remove_minters(deps.storage, minters_to_remove?)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemoveMinters { + status: Success, + })?), + ) +} + +fn set_minters( + deps: DepsMut, + info: MessageInfo, + minters_to_set: Vec, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + check_if_admin(&constants.admin, &info.sender)?; + + let minters_to_set: Vec = minters_to_set + .iter() + .map(|minter| deps.api.addr_validate(minter.as_str()).unwrap()) + .collect(); + MintersStore::save(deps.storage, minters_to_set)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetMinters { status: Success })?)) +} + +/// Burn tokens +/// +/// Remove `amount` tokens from the system irreversibly, from signer account +/// +/// @param amount the amount of money to burn +fn try_burn( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, + memo: Option, + decoys: Option>, + account_random_pos: Option, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + if !constants.burn_is_enabled { + return Err(StdError::generic_err( + "Burn functionality is not enabled for this token.", + )); + } + + let raw_amount = amount.u128(); + + BalancesStore::update_balance( + deps.storage, + &info.sender, + raw_amount, + false, + "burn", + &decoys, + &account_random_pos, + )?; + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err( + "You're trying to burn more than is available in the total supply", + )); + } + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + store_burn( + deps.storage, + info.sender.clone(), + info.sender, + amount, + constants.symbol, + memo, + &env.block, + &decoys, + &account_random_pos, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?)) +} + +fn perform_transfer( + store: &mut dyn Storage, + from: &Addr, + to: &Addr, + amount: u128, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + BalancesStore::update_balance(store, from, amount, false, "transfer", &None, &None)?; + BalancesStore::update_balance( + store, + to, + amount, + true, + "transfer", + decoys, + account_random_pos, + )?; + + Ok(()) +} + +fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult { + RevokedPermits::revoke_permit( + deps.storage, + PREFIX_REVOKED_PERMITS, + info.sender.as_str(), + &permit_name, + ); + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?)) +} + +fn check_if_admin(config_admin: &Addr, account: &Addr) -> StdResult<()> { + if config_admin != account { + return Err(StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + )); + } + + Ok(()) +} + +fn is_valid_name(name: &str) -> bool { + let len = name.len(); + (3..=30).contains(&len) +} + +fn is_valid_symbol(symbol: &str) -> bool { + let len = symbol.len(); + let len_is_valid = (3..=20).contains(&len); + + len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_alphabetic()) +} + +// pub fn migrate( +// _deps: DepsMut, +// _env: Env, +// _msg: MigrateMsg, +// ) -> StdResult { +// Ok(MigrateResponse::default()) +// Ok(MigrateResponse::default()) +// } + +#[cfg(test)] +mod tests { + use std::any::Any; + + use cosmwasm_std::testing::*; + use cosmwasm_std::{ + from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, + SubMsg, Timestamp, TransactionInfo, WasmMsg, + }; + use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; + + use crate::msg::ResponseStatus; + use crate::msg::{InitConfig, InitialBalance}; + + use super::*; + + pub const VIEWING_KEY_SIZE: usize = 32; + + // Helper functions + + fn init_helper( + initial_balances: Vec, + ) -> ( + StdResult, + OwnedDeps, + ) { + let mut deps = mock_dependencies_with_balance(&[]); + let env = mock_env(); + let info = mock_info("instantiator", &[]); + + let init_msg = InstantiateMsg { + name: "sec-sec".to_string(), + admin: Some("admin".to_string()), + symbol: "SECSEC".to_string(), + decimals: 8, + initial_balances: Some(initial_balances), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: None, + supported_denoms: None, + }; + + (instantiate(deps.as_mut(), env, info, init_msg), deps) + } + + fn init_helper_with_config( + initial_balances: Vec, + enable_deposit: bool, + enable_redeem: bool, + enable_mint: bool, + enable_burn: bool, + contract_bal: u128, + supported_denoms: Vec, + ) -> ( + StdResult, + OwnedDeps, + ) { + let mut deps = mock_dependencies_with_balance(&[Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(contract_bal), + }]); + + let env = mock_env(); + let info = mock_info("instantiator", &[]); + + let init_config: InitConfig = from_binary(&Binary::from( + format!( + "{{\"public_total_supply\":false, + \"enable_deposit\":{}, + \"enable_redeem\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", + enable_deposit, enable_redeem, enable_mint, enable_burn + ) + .as_bytes(), + )) + .unwrap(); + let init_msg = InstantiateMsg { + name: "sec-sec".to_string(), + admin: Some("admin".to_string()), + symbol: "SECSEC".to_string(), + decimals: 8, + initial_balances: Some(initial_balances), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + supported_denoms: Some(supported_denoms), + }; + + (instantiate(deps.as_mut(), env, info, init_msg), deps) + } + + fn extract_error_msg(error: StdResult) -> String { + match error { + Ok(response) => { + let bin_err = (&response as &dyn Any) + .downcast_ref::() + .expect("An error was expected, but no error could be extracted"); + match from_binary(bin_err).unwrap() { + QueryAnswer::ViewingKeyError { msg } => msg, + _ => panic!("Unexpected query answer"), + } + } + Err(err) => match err { + StdError::GenericErr { msg, .. } => msg, + _ => panic!("Unexpected result from init"), + }, + } + } + + fn ensure_success(handle_result: Response) -> bool { + let handle_result: ExecuteAnswer = from_binary(&handle_result.data.unwrap()).unwrap(); + + match handle_result { + ExecuteAnswer::Deposit { status } + | ExecuteAnswer::Redeem { status } + | ExecuteAnswer::Transfer { status } + | ExecuteAnswer::Send { status } + | ExecuteAnswer::Burn { status } + | ExecuteAnswer::RegisterReceive { status } + | ExecuteAnswer::SetViewingKey { status } + | ExecuteAnswer::TransferFrom { status } + | ExecuteAnswer::SendFrom { status } + | ExecuteAnswer::BurnFrom { status } + | ExecuteAnswer::Mint { status } + | ExecuteAnswer::ChangeAdmin { status } + | ExecuteAnswer::SetContractStatus { status } + | ExecuteAnswer::SetMinters { status } + | ExecuteAnswer::AddMinters { status } + | ExecuteAnswer::RemoveMinters { status } => { + matches!(status, ResponseStatus::Success { .. }) + } + _ => panic!( + "HandleAnswer not supported for success extraction: {:?}", + handle_result + ), + } + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_submsg( + msg: Snip20ReceiveMsg, + code_hash: String, + contract_addr: Addr, + id: u64, + ) -> StdResult { + let msg = msg.into_binary()?; + let execute = SubMsg { + id, + msg: WasmMsg::Execute { + contract_addr: contract_addr.into_string(), + code_hash, + msg, + funds: vec![], + } + .into(), + // TODO: Discuss the wanted behavior + reply_on: match id { + 0 => ReplyOn::Never, + _ => ReplyOn::Always, + }, + gas_limit: None, + }; + + Ok(execute) + } + + // Init tests + + #[test] + fn test_init_sanity() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }]); + assert_eq!(init_result.unwrap(), Response::default()); + + let constants = CONFIG.load(&deps.storage).unwrap(); + assert_eq!(TOTAL_SUPPLY.load(&deps.storage).unwrap(), 5000); + assert_eq!( + CONTRACT_STATUS.load(&deps.storage).unwrap(), + ContractStatusLevel::NormalRun + ); + assert_eq!(constants.name, "sec-sec".to_string()); + assert_eq!(constants.admin, Addr::unchecked("admin".to_string())); + assert_eq!(constants.symbol, "SECSEC".to_string()); + assert_eq!(constants.decimals, 8); + assert!(!constants.total_supply_is_public); + + ViewingKey::set(deps.as_mut().storage, "lebron", "lolz fun yay"); + let is_vk_correct = ViewingKey::check(&deps.storage, "lebron", "lolz fun yay"); + assert!( + is_vk_correct.is_ok(), + "Viewing key verification failed!: {}", + is_vk_correct.err().unwrap() + ); + } + + #[test] + fn test_init_with_config_sanity() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }], + true, + true, + true, + true, + 0, + vec!["uscrt".to_string()], + ); + assert_eq!(init_result.unwrap(), Response::default()); + + let constants = CONFIG.load(&deps.storage).unwrap(); + assert_eq!(TOTAL_SUPPLY.load(&deps.storage).unwrap(), 5000); + assert_eq!( + CONTRACT_STATUS.load(&deps.storage).unwrap(), + ContractStatusLevel::NormalRun + ); + assert_eq!(constants.name, "sec-sec".to_string()); + assert_eq!(constants.admin, Addr::unchecked("admin".to_string())); + assert_eq!(constants.symbol, "SECSEC".to_string()); + assert_eq!(constants.decimals, 8); + assert!(constants.total_supply_is_public); + assert!(!constants.deposit_is_enabled); + assert!(!constants.redeem_is_enabled); + assert!(!constants.mint_is_enabled); + assert!(!constants.burn_is_enabled); + + ViewingKey::set(deps.as_mut().storage, "lebron", "lolz fun yay"); + let is_vk_correct = ViewingKey::check(&deps.storage, "lebron", "lolz fun yay"); + assert!( + is_vk_correct.is_ok(), + "Viewing key verification failed!: {}", + is_vk_correct.err().unwrap() + ); + } + + #[test] + fn test_total_supply_overflow() { + let (init_result, _deps) = init_helper(vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(u128::max_value()), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let (init_result, _deps) = init_helper(vec![ + InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(u128::max_value()), + }, + InitialBalance { + address: "giannis".to_string(), + amount: Uint128::new(1), + }, + ]); + let error = extract_error_msg(init_result); + assert_eq!( + error, + "The sum of all initial balances exceeds the maximum possible total supply" + ); + } + + // Handle tests + + #[test] + fn test_execute_transfer() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(1000), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let bob_addr = Addr::unchecked("bob".to_string()); + let alice_addr = Addr::unchecked("alice".to_string()); + + assert_eq!(5000 - 1000, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(1000, BalancesStore::load(&deps.storage, &alice_addr)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(10000), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient funds")); + } + + #[test] + fn test_decoys_balance_stays_on_transfer() { + let (init_result, mut deps) = init_helper(vec![ + InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }, + InitialBalance { + address: "lior".to_string(), + amount: Uint128::new(7000), + }, + ]); + + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let bob_addr = Addr::unchecked("bob".to_string()); + let alice_addr = Addr::unchecked("alice".to_string()); + let lior_addr = Addr::unchecked("lior".to_string()); + let jhon_addr = Addr::unchecked("jhon".to_string()); + + let bob_balance = BalancesStore::load(&deps.storage, &bob_addr); + let alice_balance = BalancesStore::load(&deps.storage, &alice_addr); + let lior_balance = BalancesStore::load(&deps.storage, &lior_addr); + let jhon_balance = BalancesStore::load(&deps.storage, &jhon_addr); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(1000), + memo: None, + decoys: Some(vec![lior_addr.clone(), jhon_addr.clone()]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + assert_eq!( + bob_balance - 1000, + BalancesStore::load(&deps.storage, &bob_addr) + ); + assert_eq!( + alice_balance + 1000, + BalancesStore::load(&deps.storage, &alice_addr) + ); + assert_eq!(lior_balance, BalancesStore::load(&deps.storage, &lior_addr)); + assert_eq!(jhon_balance, BalancesStore::load(&deps.storage, &jhon_addr)); + } + + #[test] + fn test_handle_send() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::RegisterReceive { + code_hash: "this_is_a_hash_of_a_code".to_string(), + padding: None, + }; + let info = mock_info("contract", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Send { + recipient: "contract".to_string(), + recipient_code_hash: None, + amount: Uint128::new(100), + memo: Some("my memo".to_string()), + padding: None, + msg: Some(to_binary("hey hey you you").unwrap()), + decoys: None, + entropy: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result.clone())); + let id = 0; + assert!(result.messages.contains(&SubMsg { + id, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "contract".to_string(), + code_hash: "this_is_a_hash_of_a_code".to_string(), + msg: Snip20ReceiveMsg::new( + Addr::unchecked("bob".to_string()), + Addr::unchecked("bob".to_string()), + Uint128::new(100), + Some("my memo".to_string()), + Some(to_binary("hey hey you you").unwrap()) + ) + .into_binary() + .unwrap(), + funds: vec![], + }), + reply_on: match id { + 0 => ReplyOn::Never, + _ => ReplyOn::Always, + }, + gas_limit: None, + })); + } + + #[test] + fn test_handle_register_receive() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::RegisterReceive { + code_hash: "this_is_a_hash_of_a_code".to_string(), + padding: None, + }; + let info = mock_info("contract", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let hash = + ReceiverHashStore::may_load(&deps.storage, &Addr::unchecked("contract".to_string())) + .unwrap() + .unwrap(); + assert_eq!(hash, "this_is_a_hash_of_a_code".to_string()); + } + + #[test] + fn test_handle_create_viewing_key() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::CreateViewingKey { + entropy: "".to_string(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let answer: ExecuteAnswer = from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + + let key = match answer { + ExecuteAnswer::CreateViewingKey { key } => key, + _ => panic!("NOPE"), + }; + // let bob_canonical = deps.as_mut().api.addr_canonicalize("bob").unwrap(); + + let result = ViewingKey::check(&deps.storage, "bob", key.as_str()); + assert!(result.is_ok()); + + // let saved_vk = read_viewing_key(&deps.storage, &bob_canonical).unwrap(); + // assert!(key.check_viewing_key(saved_vk.as_slice())); + } + + #[test] + fn test_handle_set_viewing_key() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Set VK + let handle_msg = ExecuteMsg::SetViewingKey { + key: "hi lol".to_string(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let unwrapped_result: ExecuteAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&ExecuteAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + // Set valid VK + let actual_vk = "x".to_string().repeat(VIEWING_KEY_SIZE); + let handle_msg = ExecuteMsg::SetViewingKey { + key: actual_vk.clone(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let unwrapped_result: ExecuteAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&ExecuteAnswer::SetViewingKey { status: Success }).unwrap(), + ); + + let result = ViewingKey::check(&deps.storage, "bob", actual_vk.as_str()); + assert!(result.is_ok()); + } + + fn revoke_permit( + permit_name: &str, + user_address: &str, + deps: &mut OwnedDeps, + ) -> Result { + let handle_msg = ExecuteMsg::RevokePermit { + permit_name: permit_name.to_string(), + padding: None, + }; + let info = mock_info(user_address, &[]); + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + handle_result + } + + fn get_balance_with_permit_qry_msg( + permit_name: &str, + chain_id: &str, + pub_key_value: &str, + signature: &str, + ) -> QueryMsg { + let permit = gen_permit_obj( + permit_name, + chain_id, + pub_key_value, + signature, + TokenPermissions::Balance, + ); + + QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Balance {}, + } + } + + fn gen_permit_obj( + permit_name: &str, + chain_id: &str, + pub_key_value: &str, + signature: &str, + permit_type: TokenPermissions, + ) -> Permit { + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![MOCK_CONTRACT_ADDR.to_string()], + permit_name: permit_name.to_string(), + chain_id: chain_id.to_string(), + permissions: vec![permit_type], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64(pub_key_value).unwrap(), + }, + signature: Binary::from_base64(signature).unwrap(), + }, + }; + permit + } + + fn get_allowances_given_permit( + permit_name: &str, + chain_id: &str, + pub_key_value: &str, + signature: &str, + spender: String, + ) -> QueryMsg { + let permit = gen_permit_obj( + permit_name, + chain_id, + pub_key_value, + signature, + TokenPermissions::Owner, + ); + + QueryMsg::WithPermit { + permit, + query: QueryWithPermit::AllowancesReceived { + spender, + page: None, + page_size: 0, + }, + } + } + + #[test] + #[allow(clippy::nonminimal_bool)] + fn test_permit_query_allowances_given_should_fail() { + let user_address = "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y"; + let permit_name = "default"; + let chain_id = "secretdev-1"; + let pub_key = "AkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZG"; + let signature = "ZXyFMlAy6guMG9Gj05rFvcMi5/JGfClRtJpVTHiDtQY3GtSfBHncY70kmYiTXkKIxSxdnh/kS8oXa+GSX5su6Q=="; + + // Init the contract + let (init_result, deps) = init_helper(vec![InitialBalance { + address: user_address.to_string(), + amount: Uint128::new(50000000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let msg = get_allowances_given_permit( + permit_name, + chain_id, + pub_key, + signature, + "secret1kmgdagt5efcz2kku0ak9ezfgntg29g2vr88q0e".to_string(), + ); + let query_result = query(deps.as_ref(), mock_env(), msg); + + assert!(!query_result.is_err()); + } + + #[test] + #[allow(clippy::nonminimal_bool)] + fn test_permit_query_allowances_given() { + let user_address = "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y"; + let permit_name = "default"; + let chain_id = "secretdev-1"; + let pub_key = "AkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZG"; + let signature = "ZXyFMlAy6guMG9Gj05rFvcMi5/JGfClRtJpVTHiDtQY3GtSfBHncY70kmYiTXkKIxSxdnh/kS8oXa+GSX5su6Q=="; + + // Init the contract + let (init_result, deps) = init_helper(vec![InitialBalance { + address: user_address.to_string(), + amount: Uint128::new(50000000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let msg = get_allowances_given_permit( + permit_name, + chain_id, + pub_key, + signature, + "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y".to_string(), + ); + let query_result = query(deps.as_ref(), mock_env(), msg); + + assert!(!query_result.is_ok()); + } + + #[test] + fn test_permit_revoke() { + let user_address = "secret1kmgdagt5efcz2kku0ak9ezfgntg29g2vr88q0e"; + let permit_name = "to_be_revoked"; + let chain_id = "blabla"; + + // Note that 'signature'was generated with the specific values of the above: + // user_address, permit_name, chain_id, pub_key_value + let pub_key_value = "Ahlb7vwjo4aTY6dqfgpPmPYF7XhTAIReVwncQwlq8Sct"; + let signature = "VS13F7iv1qxKABxrCAvZQPy2IruLQsIyfTewy/PIhNtybtq417lr3FxsWjV/i9YTqCUxg7weoZwHmYs0YgYX4w=="; + + // Init the contract + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: user_address.to_string(), + amount: Uint128::new(50000000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Query the account's balance + let balance_with_permit_msg = + get_balance_with_permit_qry_msg(permit_name, chain_id, pub_key_value, signature); + let query_result = query(deps.as_ref(), mock_env(), balance_with_permit_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected result from query"), + }; + assert_eq!(balance.u128(), 50000000); + + // Revoke the Balance permit + let handle_result = revoke_permit(permit_name, user_address, &mut deps); + let status = match from_binary(&handle_result.unwrap().data.unwrap()).unwrap() { + ExecuteAnswer::RevokePermit { status } => status, + _ => panic!("NOPE"), + }; + assert_eq!(status, ResponseStatus::Success); + + // Try to query the balance with permit and fail because the permit is now revoked + let balance_with_permit_msg = + get_balance_with_permit_qry_msg(permit_name, chain_id, pub_key_value, signature); + let query_result = query(deps.as_ref(), mock_env(), balance_with_permit_msg); + let error = extract_error_msg(query_result); + assert!( + error.contains(format!("Permit \"{}\" was revoked by account", permit_name).as_str()) + ); + } + + #[test] + fn test_execute_transfer_from() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Transfer before allowance + let handle_msg = ExecuteMsg::TransferFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Transfer more than allowance + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: Some(1_571_797_420), + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::TransferFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Transfer after allowance expired + let handle_msg = ExecuteMsg::TransferFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + amount: Uint128::new(2000), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + + let info = MessageInfo { + sender: Addr::unchecked("bob".to_string()), + funds: vec![], + }; + + let handle_result = execute( + deps.as_mut(), + Env { + block: BlockInfo { + height: 12_345, + time: Timestamp::from_seconds(1_571_797_420), + chain_id: "cosmos-testnet-14002".to_string(), + random: None, + }, + transaction: Some(TransactionInfo { + index: 3, + hash: "hash".to_string(), + }), + contract: ContractInfo { + address: Addr::unchecked(MOCK_CONTRACT_ADDR.to_string()), + code_hash: "".to_string(), + }, + }, + info, + handle_msg, + ); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Sanity check + let handle_msg = ExecuteMsg::TransferFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + amount: Uint128::new(2000), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let bob_canonical = Addr::unchecked("bob".to_string()); + let alice_canonical = Addr::unchecked("alice".to_string()); + + let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); + let alice_balance = BalancesStore::load(&deps.storage, &alice_canonical); + assert_eq!(bob_balance, 5000 - 2000); + assert_eq!(alice_balance, 2000); + let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + assert_eq!(total_supply, 5000); + + // Second send more than allowance + let handle_msg = ExecuteMsg::TransferFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + amount: Uint128::new(1), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + } + + #[test] + fn test_handle_send_from() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Send before allowance + let handle_msg = ExecuteMsg::SendFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + recipient_code_hash: None, + amount: Uint128::new(2500), + memo: None, + msg: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Send more than allowance + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::SendFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + recipient_code_hash: None, + amount: Uint128::new(2500), + memo: None, + msg: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Sanity check + let handle_msg = ExecuteMsg::RegisterReceive { + code_hash: "lolz".to_string(), + padding: None, + }; + let info = mock_info("contract", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let send_msg = Binary::from(r#"{ "some_msg": { "some_key": "some_val" } }"#.as_bytes()); + let snip20_msg = Snip20ReceiveMsg::new( + Addr::unchecked("alice".to_string()), + Addr::unchecked("bob".to_string()), + Uint128::new(2000), + Some("my memo".to_string()), + Some(send_msg.clone()), + ); + let handle_msg = ExecuteMsg::SendFrom { + owner: "bob".to_string(), + recipient: "contract".to_string(), + recipient_code_hash: None, + amount: Uint128::new(2000), + memo: Some("my memo".to_string()), + msg: Some(send_msg), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + assert!(handle_result.unwrap().messages.contains( + &into_cosmos_submsg( + snip20_msg, + "lolz".to_string(), + Addr::unchecked("contract".to_string()), + 0 + ) + .unwrap() + )); + let bob_canonical = Addr::unchecked("bob".to_string()); + let contract_canonical = Addr::unchecked("contract".to_string()); + let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); + let contract_balance = BalancesStore::load(&deps.storage, &contract_canonical); + assert_eq!(bob_balance, 5000 - 2000); + assert_eq!(contract_balance, 2000); + let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + assert_eq!(total_supply, 5000); + + // Second send more than allowance + let handle_msg = ExecuteMsg::SendFrom { + owner: "bob".to_string(), + recipient: "alice".to_string(), + recipient_code_hash: None, + amount: Uint128::new(1), + memo: None, + msg: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + } + + #[test] + fn test_handle_burn_from() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(10000), + }], + false, + false, + false, + true, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(10000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // test when burn disabled + let handle_msg = ExecuteMsg::BurnFrom { + owner: "bob".to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Burn functionality is not enabled for this token.")); + + // Burn before allowance + let handle_msg = ExecuteMsg::BurnFrom { + owner: "bob".to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Burn more than allowance + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::BurnFrom { + owner: "bob".to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Sanity check + let handle_msg = ExecuteMsg::BurnFrom { + owner: "bob".to_string(), + amount: Uint128::new(2000), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let bob_canonical = Addr::unchecked("bob".to_string()); + let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); + assert_eq!(bob_balance, 10000 - 2000); + let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + assert_eq!(total_supply, 10000 - 2000); + + // Second burn more than allowance + let handle_msg = ExecuteMsg::BurnFrom { + owner: "bob".to_string(), + amount: Uint128::new(1), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + } + + #[test] + fn test_handle_batch_burn_from() { + let (init_result, mut deps) = init_helper_with_config( + vec![ + InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(10000), + }, + InitialBalance { + address: "jerry".to_string(), + amount: Uint128::new(10000), + }, + InitialBalance { + address: "mike".to_string(), + amount: Uint128::new(10000), + }, + ], + false, + false, + false, + true, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(10000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // test when burn disabled + let actions: Vec<_> = ["bob", "jerry", "mike"] + .iter() + .map(|name| batch::BurnFromAction { + owner: name.to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + }) + .collect(); + let handle_msg = ExecuteMsg::BatchBurnFrom { + actions, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + let handle_result = execute( + deps_for_failure.as_mut(), + mock_env(), + info, + handle_msg.clone(), + ); + let error = extract_error_msg(handle_result); + assert!(error.contains("Burn functionality is not enabled for this token.")); + + // Burn before allowance + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Burn more than allowance + let allowance_size = 2000; + for name in &["bob", "jerry", "mike"] { + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(allowance_size), + padding: None, + expiration: None, + }; + let info = mock_info(name, &[]); + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::BurnFrom { + owner: "name".to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + } + + // Burn some of the allowance + let actions: Vec<_> = [("bob", 200_u128), ("jerry", 300), ("mike", 400)] + .iter() + .map(|(name, amount)| batch::BurnFromAction { + owner: name.to_string(), + amount: Uint128::new(*amount), + memo: None, + decoys: None, + }) + .collect(); + + let handle_msg = ExecuteMsg::BatchBurnFrom { + actions, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + for (name, amount) in &[("bob", 200_u128), ("jerry", 300), ("mike", 400)] { + let name_canon = Addr::unchecked(name.to_string()); + let balance = BalancesStore::load(&deps.storage, &name_canon); + assert_eq!(balance, 10000 - amount); + } + let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + assert_eq!(total_supply, 10000 * 3 - (200 + 300 + 400)); + + // Burn the rest of the allowance + let actions: Vec<_> = [("bob", 200_u128), ("jerry", 300), ("mike", 400)] + .iter() + .map(|(name, amount)| batch::BurnFromAction { + owner: name.to_string(), + amount: Uint128::new(allowance_size - *amount), + memo: None, + decoys: None, + }) + .collect(); + + let handle_msg = ExecuteMsg::BatchBurnFrom { + actions, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + for name in &["bob", "jerry", "mike"] { + let name_canon = Addr::unchecked(name.to_string()); + let balance = BalancesStore::load(&deps.storage, &name_canon); + assert_eq!(balance, 10000 - allowance_size); + } + let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + assert_eq!(total_supply, 3 * (10000 - allowance_size)); + + // Second burn more than allowance + let actions: Vec<_> = ["bob", "jerry", "mike"] + .iter() + .map(|name| batch::BurnFromAction { + owner: name.to_string(), + amount: Uint128::new(1), + memo: None, + decoys: None, + }) + .collect(); + let handle_msg = ExecuteMsg::BatchBurnFrom { + actions, + entropy: None, + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + } + + #[test] + fn test_handle_decrease_allowance() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::DecreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let bob_canonical = Addr::unchecked("bob".to_string()); + let alice_canonical = Addr::unchecked("alice".to_string()); + + let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical); + assert_eq!( + allowance, + crate::state::Allowance { + amount: 0, + expiration: None + } + ); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::DecreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(50), + padding: None, + expiration: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical); + assert_eq!( + allowance, + crate::state::Allowance { + amount: 1950, + expiration: None + } + ); + } + + #[test] + fn test_handle_increase_allowance() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let bob_canonical = Addr::unchecked("bob".to_string()); + let alice_canonical = Addr::unchecked("alice".to_string()); + + let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical); + assert_eq!( + allowance, + crate::state::Allowance { + amount: 2000, + expiration: None + } + ); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "alice".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical); + assert_eq!( + allowance, + crate::state::Allowance { + amount: 4000, + expiration: None + } + ); + } + + #[test] + fn test_handle_change_admin() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::ChangeAdmin { + address: "bob".to_string(), + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let admin = CONFIG.load(&deps.storage).unwrap().admin; + assert_eq!(admin, Addr::unchecked("bob".to_string())); + } + + #[test] + fn test_handle_set_contract_status() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "admin".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let contract_status = CONTRACT_STATUS.load(&deps.storage).unwrap(); + assert!(matches!( + contract_status, + ContractStatusLevel::StopAll { .. } + )); + } + + #[test] + fn test_handle_redeem() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "butler".to_string(), + amount: Uint128::new(5000), + }], + false, + true, + false, + false, + 1000, + vec!["uscrt".to_string()], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let (init_result_no_reserve, mut deps_no_reserve) = init_helper_with_config( + vec![InitialBalance { + address: "butler".to_string(), + amount: Uint128::new(5000), + }], + false, + true, + false, + false, + 0, + vec!["uscrt".to_string()], + ); + assert!( + init_result_no_reserve.is_ok(), + "Init failed: {}", + init_result_no_reserve.err().unwrap() + ); + + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "butler".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // test when redeem disabled + let handle_msg = ExecuteMsg::Redeem { + amount: Uint128::new(1000), + denom: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("butler", &[]); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Redeem functionality is not enabled for this token.")); + + // try to redeem when contract has 0 balance + let handle_msg = ExecuteMsg::Redeem { + amount: Uint128::new(1000), + denom: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("butler", &[]); + + let handle_result = execute(deps_no_reserve.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert_eq!( + error, + "You are trying to redeem for more uscrt than the contract has in its reserve" + ); + + // test without denom + let handle_msg = ExecuteMsg::Redeem { + amount: Uint128::new(1000), + denom: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("butler", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + // test with denom specified + let handle_msg = ExecuteMsg::Redeem { + amount: Uint128::new(1000), + denom: Option::from("uscrt".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("butler", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let canonical = Addr::unchecked("butler".to_string()); + assert_eq!(BalancesStore::load(&deps.storage, &canonical), 3000) + } + + #[test] + fn test_handle_deposit() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }], + true, + false, + false, + false, + 0, + vec!["uscrt".to_string()], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // test when deposit disabled + let handle_msg = ExecuteMsg::Deposit { + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info( + "lebron", + &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(1000), + }], + ); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("Tried to deposit an unsupported coin uscrt")); + + let handle_msg = ExecuteMsg::Deposit { + decoys: None, + entropy: None, + padding: None, + }; + + let info = mock_info( + "lebron", + &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(1000), + }], + ); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let canonical = Addr::unchecked("lebron".to_string()); + assert_eq!(BalancesStore::load(&deps.storage, &canonical), 6000) + } + + #[test] + fn test_handle_burn() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }], + false, + false, + false, + true, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // test when burn disabled + let handle_msg = ExecuteMsg::Burn { + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("lebron", &[]); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Burn functionality is not enabled for this token.")); + + let supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + let burn_amount: u128 = 100; + let handle_msg = ExecuteMsg::Burn { + amount: Uint128::new(burn_amount), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("lebron", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "Pause handle failed: {}", + handle_result.err().unwrap() + ); + + let new_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + assert_eq!(new_supply, supply - burn_amount); + } + + #[test] + fn test_handle_mint() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }], + false, + false, + true, + false, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // try to mint when mint is disabled + let mint_amount: u128 = 100; + let handle_msg = ExecuteMsg::Mint { + recipient: "lebron".to_string(), + amount: Uint128::new(mint_amount), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Mint functionality is not enabled for this token")); + + let supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + let mint_amount: u128 = 100; + let handle_msg = ExecuteMsg::Mint { + recipient: "lebron".to_string(), + amount: Uint128::new(mint_amount), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "Pause handle failed: {}", + handle_result.err().unwrap() + ); + + let new_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); + assert_eq!(new_supply, supply + mint_amount); + } + + #[test] + fn test_handle_admin_commands() { + let admin_err = "Admin commands can only be run from admin address".to_string(); + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }], + false, + false, + true, + false, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let pause_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAllButRedeems, + padding: None, + }; + let info = mock_info("not_admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, pause_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains(&admin_err.clone())); + + let mint_msg = ExecuteMsg::AddMinters { + minters: vec!["not_admin".to_string()], + padding: None, + }; + let info = mock_info("not_admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, mint_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains(&admin_err.clone())); + + let mint_msg = ExecuteMsg::RemoveMinters { + minters: vec!["admin".to_string()], + padding: None, + }; + let info = mock_info("not_admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, mint_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains(&admin_err.clone())); + + let mint_msg = ExecuteMsg::SetMinters { + minters: vec!["not_admin".to_string()], + padding: None, + }; + let info = mock_info("not_admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, mint_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains(&admin_err.clone())); + + let change_admin_msg = ExecuteMsg::ChangeAdmin { + address: "not_admin".to_string(), + padding: None, + }; + let info = mock_info("not_admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, change_admin_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains(&admin_err.clone())); + } + + #[test] + fn test_handle_pause_with_withdrawals() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }], + false, + true, + false, + false, + 5000, + vec!["uscrt".to_string()], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let pause_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAllButRedeems, + padding: None, + }; + + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, pause_msg); + + assert!( + handle_result.is_ok(), + "Pause handle failed: {}", + handle_result.err().unwrap() + ); + + let send_msg = ExecuteMsg::Transfer { + recipient: "account".to_string(), + amount: Uint128::new(123), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, send_msg); + + let error = extract_error_msg(handle_result); + assert_eq!( + error, + "This contract is stopped and this action is not allowed".to_string() + ); + + let withdraw_msg = ExecuteMsg::Redeem { + amount: Uint128::new(5000), + denom: Option::from("uscrt".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("lebron", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, withdraw_msg); + + assert!( + handle_result.is_ok(), + "Withdraw failed: {}", + handle_result.err().unwrap() + ); + } + + #[test] + fn test_handle_pause_all() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "lebron".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let pause_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None, + }; + + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, pause_msg); + + assert!( + handle_result.is_ok(), + "Pause handle failed: {}", + handle_result.err().unwrap() + ); + + let send_msg = ExecuteMsg::Transfer { + recipient: "account".to_string(), + amount: Uint128::new(123), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, send_msg); + + let error = extract_error_msg(handle_result); + assert_eq!( + error, + "This contract is stopped and this action is not allowed".to_string() + ); + + let withdraw_msg = ExecuteMsg::Redeem { + amount: Uint128::new(5000), + denom: Option::from("uscrt".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("lebron", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, withdraw_msg); + + let error = extract_error_msg(handle_result); + assert_eq!( + error, + "This contract is stopped and this action is not allowed".to_string() + ); + } + + #[test] + fn test_handle_set_minters() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }], + false, + false, + true, + false, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // try when mint disabled + let handle_msg = ExecuteMsg::SetMinters { + minters: vec!["bob".to_string()], + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Mint functionality is not enabled for this token")); + + let handle_msg = ExecuteMsg::SetMinters { + minters: vec!["bob".to_string()], + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Admin commands can only be run from admin address")); + + let handle_msg = ExecuteMsg::SetMinters { + minters: vec!["bob".to_string()], + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("allowed to minter accounts only")); + } + + #[test] + fn test_handle_add_minters() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }], + false, + false, + true, + false, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // try when mint disabled + let handle_msg = ExecuteMsg::AddMinters { + minters: vec!["bob".to_string()], + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Mint functionality is not enabled for this token")); + + let handle_msg = ExecuteMsg::AddMinters { + minters: vec!["bob".to_string()], + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Admin commands can only be run from admin address")); + + let handle_msg = ExecuteMsg::AddMinters { + minters: vec!["bob".to_string()], + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + } + + #[test] + fn test_handle_remove_minters() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }], + false, + false, + true, + false, + 0, + vec![], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result_for_failure.is_ok(), + "Init failed: {}", + init_result_for_failure.err().unwrap() + ); + // try when mint disabled + let handle_msg = ExecuteMsg::RemoveMinters { + minters: vec!["bob".to_string()], + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Mint functionality is not enabled for this token")); + + let handle_msg = ExecuteMsg::RemoveMinters { + minters: vec!["admin".to_string()], + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("Admin commands can only be run from admin address")); + + let handle_msg = ExecuteMsg::RemoveMinters { + minters: vec!["admin".to_string()], + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("allowed to minter accounts only")); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("allowed to minter accounts only")); + + // Removing another extra time to ensure nothing funky happens + let handle_msg = ExecuteMsg::RemoveMinters { + minters: vec!["admin".to_string()], + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("allowed to minter accounts only")); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let error = extract_error_msg(handle_result); + assert!(error.contains("allowed to minter accounts only")); + } + + // Query tests + + #[test] + fn test_authenticated_queries() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "giannis".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let no_vk_yet_query_msg = QueryMsg::Balance { + address: "giannis".to_string(), + key: "no_vk_yet".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), no_vk_yet_query_msg); + let error = extract_error_msg(query_result); + assert_eq!( + error, + "Wrong viewing key for this address or viewing key not set".to_string() + ); + + let create_vk_msg = ExecuteMsg::CreateViewingKey { + entropy: "34".to_string(), + padding: None, + }; + let info = mock_info("giannis", &[]); + let handle_response = execute(deps.as_mut(), mock_env(), info, create_vk_msg).unwrap(); + let vk = match from_binary(&handle_response.data.unwrap()).unwrap() { + ExecuteAnswer::CreateViewingKey { key } => key, + _ => panic!("Unexpected result from handle"), + }; + + let query_balance_msg = QueryMsg::Balance { + address: "giannis".to_string(), + key: vk, + }; + + let query_response = query(deps.as_ref(), mock_env(), query_balance_msg).unwrap(); + let balance = match from_binary(&query_response).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected result from query"), + }; + assert_eq!(balance, Uint128::new(5000)); + + let wrong_vk_query_msg = QueryMsg::Balance { + address: "giannis".to_string(), + key: "wrong_vk".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), wrong_vk_query_msg); + let error = extract_error_msg(query_result); + assert_eq!( + error, + "Wrong viewing key for this address or viewing key not set".to_string() + ); + } + + #[test] + fn test_query_token_info() { + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 8; + let init_config: InitConfig = from_binary(&Binary::from( + r#"{ "public_total_supply": true }"#.as_bytes(), + )) + .unwrap(); + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.into_string()), + symbol: init_symbol.clone(), + decimals: init_decimals, + initial_balances: Some(vec![InitialBalance { + address: "giannis".to_string(), + amount: init_supply, + }]), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + supported_denoms: None, + }; + let init_result = instantiate(deps.as_mut(), env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let query_msg = QueryMsg::TokenInfo {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::TokenInfo { + name, + symbol, + decimals, + total_supply, + } => { + assert_eq!(name, init_name); + assert_eq!(symbol, init_symbol); + assert_eq!(decimals, init_decimals); + assert_eq!(total_supply, Some(Uint128::new(5000))); + } + _ => panic!("unexpected"), + } + } + + #[test] + fn test_query_token_config() { + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 8; + let init_config: InitConfig = from_binary(&Binary::from( + format!( + "{{\"public_total_supply\":{}, + \"enable_deposit\":{}, + \"enable_redeem\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", + true, false, false, true, false + ) + .as_bytes(), + )) + .unwrap(); + + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.into_string()), + symbol: init_symbol.clone(), + decimals: init_decimals, + initial_balances: Some(vec![InitialBalance { + address: "giannis".to_string(), + amount: init_supply, + }]), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + supported_denoms: None, + }; + let init_result = instantiate(deps.as_mut(), env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let query_msg = QueryMsg::TokenConfig {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::TokenConfig { + public_total_supply, + deposit_enabled, + redeem_enabled, + mint_enabled, + burn_enabled, + supported_denoms, + } => { + assert!(!public_total_supply); + assert!(!deposit_enabled); + assert!(!redeem_enabled); + assert!(!mint_enabled); + assert!(!burn_enabled); + assert_eq!(supported_denoms.len(), 0); + } + _ => panic!("unexpected"), + } + } + + #[test] + fn test_query_exchange_rate() { + // test more dec than SCRT + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 8; + + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + let init_config: InitConfig = from_binary(&Binary::from( + format!( + "{{\"public_total_supply\":{}, + \"enable_deposit\":{}, + \"enable_redeem\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", + true, true, false, false, false + ) + .as_bytes(), + )) + .unwrap(); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.into_string()), + symbol: init_symbol.clone(), + decimals: init_decimals, + initial_balances: Some(vec![InitialBalance { + address: "giannis".to_string(), + amount: init_supply, + }]), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + supported_denoms: Some(vec!["uscrt".to_string()]), + }; + let init_result = instantiate(deps.as_mut(), env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let query_msg = QueryMsg::ExchangeRate {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::ExchangeRate { rate, denom } => { + assert_eq!(rate, Uint128::new(100)); + assert_eq!(denom, "SCRT"); + } + _ => panic!("unexpected"), + } + + // test same number of decimals as SCRT + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 6; + + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + let init_config: InitConfig = from_binary(&Binary::from( + format!( + "{{\"public_total_supply\":{}, + \"enable_deposit\":{}, + \"enable_redeem\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", + true, true, false, false, false + ) + .as_bytes(), + )) + .unwrap(); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.into_string()), + symbol: init_symbol.clone(), + decimals: init_decimals, + initial_balances: Some(vec![InitialBalance { + address: "giannis".to_string(), + amount: init_supply, + }]), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + supported_denoms: Some(vec!["uscrt".to_string()]), + }; + let init_result = instantiate(deps.as_mut(), env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let query_msg = QueryMsg::ExchangeRate {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::ExchangeRate { rate, denom } => { + assert_eq!(rate, Uint128::new(1)); + assert_eq!(denom, "SCRT"); + } + _ => panic!("unexpected"), + } + + // test less decimal places than SCRT + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 3; + + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + let init_config: InitConfig = from_binary(&Binary::from( + format!( + "{{\"public_total_supply\":{}, + \"enable_deposit\":{}, + \"enable_redeem\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", + true, true, false, false, false + ) + .as_bytes(), + )) + .unwrap(); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.into_string()), + symbol: init_symbol.clone(), + decimals: init_decimals, + initial_balances: Some(vec![InitialBalance { + address: "giannis".to_string(), + amount: init_supply, + }]), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + supported_denoms: Some(vec!["uscrt".to_string()]), + }; + let init_result = instantiate(deps.as_mut(), env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let query_msg = QueryMsg::ExchangeRate {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::ExchangeRate { rate, denom } => { + assert_eq!(rate, Uint128::new(1000)); + assert_eq!(denom, "SECSEC"); + } + _ => panic!("unexpected"), + } + + // test depost/redeem not enabled + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 3; + + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.into_string()), + symbol: init_symbol.clone(), + decimals: init_decimals, + initial_balances: Some(vec![InitialBalance { + address: "giannis".to_string(), + amount: init_supply, + }]), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: None, + supported_denoms: None, + }; + let init_result = instantiate(deps.as_mut(), env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let query_msg = QueryMsg::ExchangeRate {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::ExchangeRate { rate, denom } => { + assert_eq!(rate, Uint128::new(0)); + assert_eq!(denom, String::new()); + } + _ => panic!("unexpected"), + } + } + + #[test] + fn test_query_allowance() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "giannis".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: "lebron".to_string(), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let info = mock_info("giannis", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let vk1 = "key1".to_string(); + let vk2 = "key2".to_string(); + + let query_msg = QueryMsg::Allowance { + owner: "giannis".to_string(), + spender: "lebron".to_string(), + key: vk1.clone(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + assert!( + query_result.is_ok(), + "Query failed: {}", + query_result.err().unwrap() + ); + let error = extract_error_msg(query_result); + assert!(error.contains("Wrong viewing key")); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: vk1.clone(), + padding: None, + }; + let info = mock_info("lebron", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let unwrapped_result: ExecuteAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&ExecuteAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: vk2.clone(), + padding: None, + }; + let info = mock_info("giannis", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let unwrapped_result: ExecuteAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&ExecuteAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + let query_msg = QueryMsg::Allowance { + owner: "giannis".to_string(), + spender: "lebron".to_string(), + key: vk1.clone(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let allowance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Allowance { allowance, .. } => allowance, + _ => panic!("Unexpected"), + }; + assert_eq!(allowance, Uint128::new(2000)); + + let query_msg = QueryMsg::Allowance { + owner: "giannis".to_string(), + spender: "lebron".to_string(), + key: vk2.clone(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let allowance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Allowance { allowance, .. } => allowance, + _ => panic!("Unexpected"), + }; + assert_eq!(allowance, Uint128::new(2000)); + + let query_msg = QueryMsg::Allowance { + owner: "lebron".to_string(), + spender: "giannis".to_string(), + key: vk2.clone(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let allowance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Allowance { allowance, .. } => allowance, + _ => panic!("Unexpected"), + }; + assert_eq!(allowance, Uint128::new(0)); + } + + #[test] + fn test_query_all_allowances() { + let num_owners = 3; + let num_spenders = 20; + let vk = "key".to_string(); + + let initial_balances: Vec = (0..num_owners) + .map(|i| InitialBalance { + address: format!("owner{}", i), + amount: Uint128::new(5000), + }) + .collect(); + let (init_result, mut deps) = init_helper(initial_balances); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + for i in 0..num_owners { + let handle_msg = ExecuteMsg::SetViewingKey { + key: vk.clone(), + padding: None, + }; + let info = mock_info(format!("owner{}", i).as_str(), &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let unwrapped_result: ExecuteAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&ExecuteAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + } + + for i in 0..num_owners { + for j in 0..num_spenders { + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: format!("spender{}", j), + amount: Uint128::new(50), + padding: None, + expiration: None, + }; + let info = mock_info(format!("owner{}", i).as_str(), &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: vk.clone(), + padding: None, + }; + let info = mock_info(format!("spender{}", j).as_str(), &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let unwrapped_result: ExecuteAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&ExecuteAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + } + } + + let query_msg = QueryMsg::AllowancesGiven { + owner: "owner0".to_string(), + key: vk.clone(), + page: None, + page_size: 5, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::AllowancesGiven { + owner, + allowances, + count, + } => { + assert_eq!(owner, "owner0".to_string()); + assert_eq!(allowances.len(), 5); + assert_eq!(allowances[0].spender, "spender0"); + assert_eq!(allowances[0].allowance, Uint128::from(50_u128)); + assert_eq!(allowances[0].expiration, None); + assert_eq!(count, num_spenders); + } + _ => panic!("Unexpected"), + }; + + let query_msg = QueryMsg::AllowancesGiven { + owner: "owner1".to_string(), + key: vk.clone(), + page: Some(1), + page_size: 5, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::AllowancesGiven { + owner, + allowances, + count, + } => { + assert_eq!(owner, "owner1".to_string()); + assert_eq!(allowances.len(), 5); + assert_eq!(allowances[0].spender, "spender5"); + assert_eq!(allowances[0].allowance, Uint128::from(50_u128)); + assert_eq!(allowances[0].expiration, None); + assert_eq!(count, num_spenders); + } + _ => panic!("Unexpected"), + }; + + let query_msg = QueryMsg::AllowancesGiven { + owner: "owner1".to_string(), + key: vk.clone(), + page: Some(0), + page_size: 23, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::AllowancesGiven { + owner, + allowances, + count, + } => { + assert_eq!(owner, "owner1".to_string()); + assert_eq!(allowances.len(), 20); + assert_eq!(count, num_spenders); + } + _ => panic!("Unexpected"), + }; + + let query_msg = QueryMsg::AllowancesGiven { + owner: "owner1".to_string(), + key: vk.clone(), + page: Some(2), + page_size: 8, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::AllowancesGiven { + owner, + allowances, + count, + } => { + assert_eq!(owner, "owner1".to_string()); + assert_eq!(allowances.len(), 4); + assert_eq!(count, num_spenders); + } + _ => panic!("Unexpected"), + }; + + let query_msg = QueryMsg::AllowancesGiven { + owner: "owner2".to_string(), + key: vk.clone(), + page: Some(5), + page_size: 5, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::AllowancesGiven { + owner, + allowances, + count, + } => { + assert_eq!(owner, "owner2".to_string()); + assert_eq!(allowances.len(), 0); + assert_eq!(count, num_spenders); + } + _ => panic!("Unexpected"), + }; + + let query_msg = QueryMsg::AllowancesReceived { + spender: "spender0".to_string(), + key: vk.clone(), + page: None, + page_size: 10, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::AllowancesReceived { + spender, + allowances, + count, + } => { + assert_eq!(spender, "spender0".to_string()); + assert_eq!(allowances.len(), 3); + assert_eq!(allowances[0].owner, "owner0"); + assert_eq!(allowances[0].allowance, Uint128::from(50_u128)); + assert_eq!(allowances[0].expiration, None); + assert_eq!(count, num_owners); + } + _ => panic!("Unexpected"), + }; + + let query_msg = QueryMsg::AllowancesReceived { + spender: "spender1".to_string(), + key: vk.clone(), + page: Some(1), + page_size: 1, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::AllowancesReceived { + spender, + allowances, + count, + } => { + assert_eq!(spender, "spender1".to_string()); + assert_eq!(allowances.len(), 1); + assert_eq!(allowances[0].owner, "owner1"); + assert_eq!(allowances[0].allowance, Uint128::from(50_u128)); + assert_eq!(allowances[0].expiration, None); + assert_eq!(count, num_owners); + } + _ => panic!("Unexpected"), + }; + } + + #[test] + fn test_query_balance() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let unwrapped_result: ExecuteAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&ExecuteAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + let query_msg = QueryMsg::Balance { + address: "bob".to_string(), + key: "wrong_key".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let error = extract_error_msg(query_result); + assert!(error.contains("Wrong viewing key")); + + let query_msg = QueryMsg::Balance { + address: "bob".to_string(), + key: "key".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected"), + }; + assert_eq!(balance, Uint128::new(5000)); + } + + #[test] + fn test_query_transfer_history() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(1000), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let handle_msg = ExecuteMsg::Transfer { + recipient: "banana".to_string(), + amount: Uint128::new(500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let handle_msg = ExecuteMsg::Transfer { + recipient: "mango".to_string(), + amount: Uint128::new(2500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let query_msg = QueryMsg::TransferHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: None, + page_size: 0, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + // let a: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + // println!("{:?}", a); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert!(transfers.is_empty()); + + let query_msg = QueryMsg::TransferHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 3); + + let query_msg = QueryMsg::TransferHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: None, + page_size: 2, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 2); + + let query_msg = QueryMsg::TransferHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: Some(1), + page_size: 2, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 1); + } + + #[test] + fn test_query_transfer_history_with_decoys() { + let (init_result, mut deps) = init_helper(vec![ + InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }, + InitialBalance { + address: "jhon".to_string(), + amount: Uint128::new(7000), + }, + ]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "alice_key".to_string(), + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "lior_key".to_string(), + padding: None, + }; + let info = mock_info("lior", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "banana_key".to_string(), + padding: None, + }; + let info = mock_info("banana", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let lior_addr = Addr::unchecked("lior".to_string()); + let jhon_addr = Addr::unchecked("jhon".to_string()); + let alice_addr = Addr::unchecked("alice".to_string()); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(1000), + memo: None, + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let handle_msg = ExecuteMsg::Transfer { + recipient: "banana".to_string(), + amount: Uint128::new(500), + memo: None, + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let query_msg = QueryMsg::TransferHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 2); + + let query_msg = QueryMsg::TransferHistory { + address: "alice".to_string(), + key: "alice_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 2); + + let query_msg = QueryMsg::TransferHistory { + address: "alice".to_string(), + key: "alice_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 1); + + let query_msg = QueryMsg::TransferHistory { + address: "banana".to_string(), + key: "banana_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 1); + + let query_msg = QueryMsg::TransferHistory { + address: "lior".to_string(), + key: "lior_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 0); + + let query_msg = QueryMsg::Balance { + address: "bob".to_string(), + key: "key".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected"), + }; + assert_eq!(balance, Uint128::new(3500)); + + let query_msg = QueryMsg::Balance { + address: "alice".to_string(), + key: "alice_key".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected"), + }; + assert_eq!(balance, Uint128::new(1000)); + + let query_msg = QueryMsg::Balance { + address: "banana".to_string(), + key: "banana_key".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected"), + }; + assert_eq!(balance, Uint128::new(500)); + + let query_msg = QueryMsg::Balance { + address: "lior".to_string(), + key: "lior_key".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected"), + }; + assert_eq!(balance, Uint128::new(0)); + } + + #[test] + fn test_query_transaction_history() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(10000), + }], + true, + true, + true, + true, + 1000, + vec!["uscrt".to_string()], + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Burn { + amount: Uint128::new(1), + memo: Some("my burn message".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "Pause handle failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Redeem { + amount: Uint128::new(1000), + denom: Option::from("uscrt".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: Some("my mint message".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Deposit { + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info( + "bob", + &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(1000), + }], + ); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(1000), + memo: Some("my transfer message #1".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "banana".to_string(), + amount: Uint128::new(500), + memo: Some("my transfer message #2".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "mango".to_string(), + amount: Uint128::new(2500), + memo: Some("my transfer message #3".to_string()), + decoys: None, + entropy: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let query_msg = QueryMsg::TransferHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 3); + + let query_msg = QueryMsg::TransactionHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + + use crate::transaction_history::{ExtendedTx, TxAction}; + let expected_transfers = [ + ExtendedTx { + id: 8, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("mango".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(2500), + }, + memo: Some("my transfer message #3".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 7, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("banana".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(500), + }, + memo: Some("my transfer message #2".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 6, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(1000), + }, + memo: Some("my transfer message #1".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 5, + action: TxAction::Deposit {}, + coins: Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(1000), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 4, + action: TxAction::Mint { + minter: Addr::unchecked("admin".to_string()), + recipient: Addr::unchecked("bob".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(100), + }, + memo: Some("my mint message".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 3, + action: TxAction::Redeem {}, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(1000), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 2, + action: TxAction::Burn { + burner: Addr::unchecked("bob".to_string()), + owner: Addr::unchecked("bob".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(1), + }, + memo: Some("my burn message".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 1, + action: TxAction::Mint { + minter: Addr::unchecked("admin".to_string()), + recipient: Addr::unchecked("bob".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(10000), + }, + + memo: Some("Initial Balance".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ]; + + assert_eq!(transfers, expected_transfers); + } + + #[test] + fn test_query_transaction_history_with_decoys() { + let (init_result, mut deps) = init_helper_with_config( + vec![ + InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }, + InitialBalance { + address: "jhon".to_string(), + amount: Uint128::new(7000), + }, + ], + true, + true, + true, + true, + 1000, + vec!["uscrt".to_string()], + ); + + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "alice_key".to_string(), + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "lior_key".to_string(), + padding: None, + }; + let info = mock_info("lior", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "jhon_key".to_string(), + padding: None, + }; + let info = mock_info("jhon", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let lior_addr = Addr::unchecked("lior".to_string()); + let jhon_addr = Addr::unchecked("jhon".to_string()); + let alice_addr = Addr::unchecked("alice".to_string()); + + let handle_msg = ExecuteMsg::Burn { + amount: Uint128::new(1), + memo: Some("my burn message".to_string()), + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "Pause handle failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Redeem { + amount: Uint128::new(1000), + denom: Option::from("uscrt".to_string()), + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Mint { + recipient: "bob".to_string(), + amount: Uint128::new(100), + memo: Some("my mint message".to_string()), + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Deposit { + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info( + "bob", + &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(1000), + }], + ); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(1000), + memo: Some("my transfer message #1".to_string()), + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "banana".to_string(), + amount: Uint128::new(500), + memo: Some("my transfer message #2".to_string()), + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: "mango".to_string(), + amount: Uint128::new(2500), + memo: Some("my transfer message #3".to_string()), + decoys: Some(vec![ + lior_addr.clone(), + jhon_addr.clone(), + alice_addr.clone(), + ]), + entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), + padding: None, + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let query_msg = QueryMsg::TransactionHistory { + address: "lior".to_string(), + key: "lior_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transactions = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + + assert!(transactions.is_empty()); + + let query_msg = QueryMsg::TransactionHistory { + address: "alice".to_string(), + key: "alice_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: false, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transactions = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + + assert_eq!(transactions.len(), 7); // Transfer from bob + + let query_msg = QueryMsg::TransactionHistory { + address: "alice".to_string(), + key: "alice_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transactions = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + + assert_eq!(transactions.len(), 1); // Transfer from bob + + let query_msg = QueryMsg::TransactionHistory { + address: "jhon".to_string(), + key: "jhon_key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transactions = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + + assert_eq!(transactions.len(), 1); // Mint on init + + let query_msg = QueryMsg::TransactionHistory { + address: "bob".to_string(), + key: "key".to_string(), + page: None, + page_size: 10, + should_filter_decoys: true, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transactions = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + + use crate::transaction_history::{ExtendedTx, TxAction}; + let expected_transactions = [ + ExtendedTx { + id: 9, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("mango".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(2500), + }, + memo: Some("my transfer message #3".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 8, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("banana".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(500), + }, + memo: Some("my transfer message #2".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 7, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(1000), + }, + memo: Some("my transfer message #1".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 6, + action: TxAction::Deposit {}, + coins: Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(1000), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 5, + action: TxAction::Mint { + minter: Addr::unchecked("admin".to_string()), + recipient: Addr::unchecked("bob".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(100), + }, + memo: Some("my mint message".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 4, + action: TxAction::Redeem {}, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(1000), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 3, + action: TxAction::Burn { + burner: Addr::unchecked("bob".to_string()), + owner: Addr::unchecked("bob".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(1), + }, + memo: Some("my burn message".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ExtendedTx { + id: 1, + action: TxAction::Mint { + minter: Addr::unchecked("admin".to_string()), + recipient: Addr::unchecked("bob".to_string()), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::new(5000), + }, + + memo: Some("Initial Balance".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + ]; + + assert_eq!(transactions, expected_transactions); + } +} diff --git a/contracts/external/snip20-base/src/lib.rs b/contracts/external/snip20-base/src/lib.rs new file mode 100644 index 0000000..9bafd89 --- /dev/null +++ b/contracts/external/snip20-base/src/lib.rs @@ -0,0 +1,6 @@ +mod batch; +pub mod contract; +pub mod msg; +pub mod receiver; +pub mod state; +mod transaction_history; diff --git a/contracts/external/snip20-base/src/msg.rs b/contracts/external/snip20-base/src/msg.rs new file mode 100644 index 0000000..812452c --- /dev/null +++ b/contracts/external/snip20-base/src/msg.rs @@ -0,0 +1,689 @@ +#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` + +use cosmwasm_schema::cw_serde; +use schemars::JsonSchema; +use secret_toolkit::utils::InitCallback; +use serde::{Deserialize, Serialize}; + +use crate::batch; +use crate::batch::HasDecoy; +use crate::transaction_history::{ExtendedTx, Tx}; +use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128}; +use secret_toolkit::permit::Permit; + +#[cw_serde] +pub struct InitialBalance { + pub address: String, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +pub struct InstantiateMsg { + pub name: String, + pub admin: Option, + pub symbol: String, + pub decimals: u8, + pub initial_balances: Option>, + pub prng_seed: Binary, + pub config: Option, + pub supported_denoms: Option>, +} + +impl InstantiateMsg { + pub fn config(&self) -> InitConfig { + self.config.clone().unwrap_or_default() + } +} + +impl InitCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +/// This type represents optional configuration values which can be overridden. +/// All values are optional and have defaults which are more private by default, +/// but can be overridden if necessary +#[derive(Serialize, Deserialize, JsonSchema, Clone, Default, Debug)] +#[serde(rename_all = "snake_case")] +pub struct InitConfig { + /// Indicates whether the total supply is public or should be kept secret. + /// default: False + pub public_total_supply: Option, + /// Indicates whether deposit functionality should be enabled + /// default: False + pub enable_deposit: Option, + /// Indicates whether redeem functionality should be enabled + /// default: False + pub enable_redeem: Option, + /// Indicates whether mint functionality should be enabled + /// default: False + pub enable_mint: Option, + /// Indicates whether burn functionality should be enabled + /// default: False + pub enable_burn: Option, + /// Indicated whether an admin can modify supported denoms + /// default: False + pub can_modify_denoms: Option, +} + +impl InitConfig { + pub fn public_total_supply(&self) -> bool { + self.public_total_supply.unwrap_or(false) + } + + pub fn deposit_enabled(&self) -> bool { + self.enable_deposit.unwrap_or(false) + } + + pub fn redeem_enabled(&self) -> bool { + self.enable_redeem.unwrap_or(false) + } + + pub fn mint_enabled(&self) -> bool { + self.enable_mint.unwrap_or(false) + } + + pub fn burn_enabled(&self) -> bool { + self.enable_burn.unwrap_or(false) + } + + pub fn can_modify_denoms(&self) -> bool { + self.can_modify_denoms.unwrap_or(false) + } +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + // Native coin interactions + Redeem { + amount: Uint128, + denom: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + Deposit { + decoys: Option>, + entropy: Option, + padding: Option, + }, + + // Base ERC-20 stuff + Transfer { + recipient: String, + amount: Uint128, + memo: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + Send { + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + BatchTransfer { + actions: Vec, + entropy: Option, + padding: Option, + }, + BatchSend { + actions: Vec, + entropy: Option, + padding: Option, + }, + Burn { + amount: Uint128, + memo: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + RegisterReceive { + code_hash: String, + padding: Option, + }, + CreateViewingKey { + entropy: String, + padding: Option, + }, + SetViewingKey { + key: String, + padding: Option, + }, + + // Allowance + IncreaseAllowance { + spender: String, + amount: Uint128, + expiration: Option, + padding: Option, + }, + DecreaseAllowance { + spender: String, + amount: Uint128, + expiration: Option, + padding: Option, + }, + TransferFrom { + owner: String, + recipient: String, + amount: Uint128, + memo: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + SendFrom { + owner: String, + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + BatchTransferFrom { + actions: Vec, + entropy: Option, + padding: Option, + }, + BatchSendFrom { + actions: Vec, + entropy: Option, + padding: Option, + }, + BurnFrom { + owner: String, + amount: Uint128, + memo: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + BatchBurnFrom { + actions: Vec, + entropy: Option, + padding: Option, + }, + + // Mint + Mint { + recipient: String, + amount: Uint128, + memo: Option, + decoys: Option>, + entropy: Option, + padding: Option, + }, + BatchMint { + actions: Vec, + entropy: Option, + padding: Option, + }, + AddMinters { + minters: Vec, + padding: Option, + }, + RemoveMinters { + minters: Vec, + padding: Option, + }, + SetMinters { + minters: Vec, + padding: Option, + }, + + // Admin + ChangeAdmin { + address: String, + padding: Option, + }, + SetContractStatus { + level: ContractStatusLevel, + padding: Option, + }, + /// Add deposit/redeem support for these coin denoms + AddSupportedDenoms { denoms: Vec }, + /// Remove deposit/redeem support for these coin denoms + RemoveSupportedDenoms { denoms: Vec }, + + // Permit + RevokePermit { + permit_name: String, + padding: Option, + }, +} + +pub trait Decoyable { + fn get_minimal_decoys_size(&self) -> usize; + fn get_entropy(self) -> Option; +} + +impl Decoyable for ExecuteMsg { + fn get_minimal_decoys_size(&self) -> usize { + match self { + ExecuteMsg::Deposit { decoys, .. } + | ExecuteMsg::Redeem { decoys, .. } + | ExecuteMsg::Transfer { decoys, .. } + | ExecuteMsg::Send { decoys, .. } + | ExecuteMsg::Burn { decoys, .. } + | ExecuteMsg::Mint { decoys, .. } + | ExecuteMsg::TransferFrom { decoys, .. } + | ExecuteMsg::SendFrom { decoys, .. } + | ExecuteMsg::BurnFrom { decoys, .. } => { + if let Some(user_decoys) = decoys { + return user_decoys.len(); + } + + 0 + } + ExecuteMsg::BatchSendFrom { actions, .. } => get_min_decoys_count(actions), + ExecuteMsg::BatchTransferFrom { actions, .. } => get_min_decoys_count(actions), + ExecuteMsg::BatchTransfer { actions, .. } => get_min_decoys_count(actions), + ExecuteMsg::BatchSend { actions, .. } => get_min_decoys_count(actions), + ExecuteMsg::BatchBurnFrom { actions, .. } => get_min_decoys_count(actions), + ExecuteMsg::BatchMint { actions, .. } => get_min_decoys_count(actions), + _ => 0, + } + } + + fn get_entropy(self) -> Option { + match self { + ExecuteMsg::Deposit { entropy, .. } + | ExecuteMsg::Redeem { entropy, .. } + | ExecuteMsg::Transfer { entropy, .. } + | ExecuteMsg::Send { entropy, .. } + | ExecuteMsg::Burn { entropy, .. } + | ExecuteMsg::Mint { entropy, .. } + | ExecuteMsg::TransferFrom { entropy, .. } + | ExecuteMsg::SendFrom { entropy, .. } + | ExecuteMsg::BurnFrom { entropy, .. } + | ExecuteMsg::BatchTransferFrom { entropy, .. } + | ExecuteMsg::BatchSendFrom { entropy, .. } + | ExecuteMsg::BatchTransfer { entropy, .. } + | ExecuteMsg::BatchSend { entropy, .. } + | ExecuteMsg::BatchBurnFrom { entropy, .. } + | ExecuteMsg::BatchMint { entropy, .. } => entropy, + _ => None, + } + } +} + +fn get_min_decoys_count(actions: &[T]) -> usize { + let mut min_decoys_count = usize::MAX; + for action in actions { + if let Some(user_decoys) = &action.decoys() { + if user_decoys.len() < min_decoys_count { + min_decoys_count = user_decoys.len(); + } + } + } + + if min_decoys_count == usize::MAX { + 0 + } else { + min_decoys_count + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteAnswer { + // Native + Deposit { + status: ResponseStatus, + }, + Redeem { + status: ResponseStatus, + }, + + // Base + Transfer { + status: ResponseStatus, + }, + Send { + status: ResponseStatus, + }, + BatchTransfer { + status: ResponseStatus, + }, + BatchSend { + status: ResponseStatus, + }, + Burn { + status: ResponseStatus, + }, + RegisterReceive { + status: ResponseStatus, + }, + CreateViewingKey { + key: String, + }, + SetViewingKey { + status: ResponseStatus, + }, + + // Allowance + IncreaseAllowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + }, + DecreaseAllowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + }, + TransferFrom { + status: ResponseStatus, + }, + SendFrom { + status: ResponseStatus, + }, + BatchTransferFrom { + status: ResponseStatus, + }, + BatchSendFrom { + status: ResponseStatus, + }, + BurnFrom { + status: ResponseStatus, + }, + BatchBurnFrom { + status: ResponseStatus, + }, + + // Mint + Mint { + status: ResponseStatus, + }, + BatchMint { + status: ResponseStatus, + }, + AddMinters { + status: ResponseStatus, + }, + RemoveMinters { + status: ResponseStatus, + }, + SetMinters { + status: ResponseStatus, + }, + + // Other + ChangeAdmin { + status: ResponseStatus, + }, + SetContractStatus { + status: ResponseStatus, + }, + AddSupportedDenoms { + status: ResponseStatus, + }, + RemoveSupportedDenoms { + status: ResponseStatus, + }, + + // Permit + RevokePermit { + status: ResponseStatus, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[cfg_attr(test, derive(Eq, PartialEq))] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + TokenInfo {}, + TokenConfig {}, + ContractStatus {}, + ExchangeRate {}, + Allowance { + owner: String, + spender: String, + key: String, + }, + AllowancesGiven { + owner: String, + key: String, + page: Option, + page_size: u32, + }, + AllowancesReceived { + spender: String, + key: String, + page: Option, + page_size: u32, + }, + Balance { + address: String, + key: String, + }, + TransferHistory { + address: String, + key: String, + page: Option, + page_size: u32, + should_filter_decoys: bool, + }, + TransactionHistory { + address: String, + key: String, + page: Option, + page_size: u32, + should_filter_decoys: bool, + }, + Minters {}, + WithPermit { + permit: Permit, + query: QueryWithPermit, + }, +} + +impl QueryMsg { + pub fn get_validation_params(&self, api: &dyn Api) -> StdResult<(Vec, String)> { + match self { + Self::Balance { address, key } => { + let address = api.addr_validate(address.as_str())?; + Ok((vec![address], key.clone())) + } + Self::TransferHistory { address, key, .. } => { + let address = api.addr_validate(address.as_str())?; + Ok((vec![address], key.clone())) + } + Self::TransactionHistory { address, key, .. } => { + let address = api.addr_validate(address.as_str())?; + Ok((vec![address], key.clone())) + } + Self::Allowance { + owner, + spender, + key, + .. + } => { + let owner = api.addr_validate(owner.as_str())?; + let spender = api.addr_validate(spender.as_str())?; + + Ok((vec![owner, spender], key.clone())) + } + Self::AllowancesGiven { owner, key, .. } => { + let owner = api.addr_validate(owner.as_str())?; + Ok((vec![owner], key.clone())) + } + Self::AllowancesReceived { spender, key, .. } => { + let spender = api.addr_validate(spender.as_str())?; + Ok((vec![spender], key.clone())) + } + _ => panic!("This query type does not require authentication"), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[cfg_attr(test, derive(Eq, PartialEq))] +#[serde(rename_all = "snake_case")] +pub enum QueryWithPermit { + Allowance { + owner: String, + spender: String, + }, + AllowancesGiven { + owner: String, + page: Option, + page_size: u32, + }, + AllowancesReceived { + spender: String, + page: Option, + page_size: u32, + }, + Balance {}, + TransferHistory { + page: Option, + page_size: u32, + should_filter_decoys: bool, + }, + TransactionHistory { + page: Option, + page_size: u32, + should_filter_decoys: bool, + }, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum QueryAnswer { + TokenInfo { + name: String, + symbol: String, + decimals: u8, + total_supply: Option, + }, + TokenConfig { + public_total_supply: bool, + deposit_enabled: bool, + redeem_enabled: bool, + mint_enabled: bool, + burn_enabled: bool, + supported_denoms: Vec, + }, + ContractStatus { + status: ContractStatusLevel, + }, + ExchangeRate { + rate: Uint128, + denom: String, + }, + Allowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + expiration: Option, + }, + AllowancesGiven { + owner: Addr, + allowances: Vec, + count: u32, + }, + AllowancesReceived { + spender: Addr, + allowances: Vec, + count: u32, + }, + Balance { + amount: Uint128, + }, + TransferHistory { + txs: Vec, + total: Option, + }, + TransactionHistory { + txs: Vec, + total: Option, + }, + ViewingKeyError { + msg: String, + }, + Minters { + minters: Vec, + }, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +pub struct AllowanceGivenResult { + pub spender: Addr, + pub allowance: Uint128, + pub expiration: Option, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +pub struct AllowanceReceivedResult { + pub owner: Addr, + pub allowance: Uint128, + pub expiration: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ResponseStatus { + Success, + Failure, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ContractStatusLevel { + NormalRun, + StopAllButRedeems, + StopAll, +} + +pub fn status_level_to_u8(status_level: ContractStatusLevel) -> u8 { + match status_level { + ContractStatusLevel::NormalRun => 0, + ContractStatusLevel::StopAllButRedeems => 1, + ContractStatusLevel::StopAll => 2, + } +} + +pub fn u8_to_status_level(status_level: u8) -> StdResult { + match status_level { + 0 => Ok(ContractStatusLevel::NormalRun), + 1 => Ok(ContractStatusLevel::StopAllButRedeems), + 2 => Ok(ContractStatusLevel::StopAll), + _ => Err(StdError::generic_err("Invalid state level")), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::{from_slice, StdResult}; + + #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq)] + #[serde(rename_all = "snake_case")] + pub enum Something { + Var { padding: Option }, + } + + #[test] + fn test_deserialization_of_missing_option_fields() -> StdResult<()> { + let input = b"{ \"var\": {} }"; + let obj: Something = from_slice(input)?; + assert_eq!( + obj, + Something::Var { padding: None }, + "unexpected value: {:?}", + obj + ); + Ok(()) + } +} diff --git a/contracts/external/snip20-base/src/receiver.rs b/contracts/external/snip20-base/src/receiver.rs new file mode 100644 index 0000000..21fd3d5 --- /dev/null +++ b/contracts/external/snip20-base/src/receiver.rs @@ -0,0 +1,65 @@ +#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::contract::RESPONSE_BLOCK_SIZE; +use cosmwasm_std::{to_binary, Addr, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; +use secret_toolkit::utils::space_pad; + +/// Snip20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Snip20ReceiveMsg { + pub sender: Addr, + pub from: Addr, + pub amount: Uint128, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + pub msg: Option, +} + +impl Snip20ReceiveMsg { + pub fn new( + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + ) -> Self { + Self { + sender, + from, + amount, + memo, + msg, + } + } + + /// serializes the message, and pads it to 256 bytes + pub fn into_binary(self) -> StdResult { + let msg = ReceiverHandleMsg::Receive(self); + let mut data = to_binary(&msg)?; + space_pad(&mut data.0, RESPONSE_BLOCK_SIZE); + Ok(data) + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_msg(self, code_hash: String, contract_addr: Addr) -> StdResult { + let msg = self.into_binary()?; + let execute = WasmMsg::Execute { + msg, + code_hash, + contract_addr: contract_addr.into_string(), + funds: vec![], + }; + Ok(execute.into()) + } +} + +// This is just a helper to properly serialize the above message +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +enum ReceiverHandleMsg { + Receive(Snip20ReceiveMsg), +} diff --git a/contracts/external/snip20-base/src/state.rs b/contracts/external/snip20-base/src/state.rs new file mode 100644 index 0000000..ece815a --- /dev/null +++ b/contracts/external/snip20-base/src/state.rs @@ -0,0 +1,317 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, StdError, StdResult, Storage}; +use secret_toolkit::serialization::Json; +use secret_toolkit::storage::{Item, Keymap, Keyset}; +use secret_toolkit_crypto::SHA256_HASH_SIZE; + +use crate::msg::ContractStatusLevel; + +pub const KEY_CONFIG: &[u8] = b"config"; +pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply"; +pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; +pub const KEY_PRNG: &[u8] = b"prng"; +pub const KEY_MINTERS: &[u8] = b"minters"; +pub const KEY_TX_COUNT: &[u8] = b"tx-count"; + +pub const PREFIX_CONFIG: &[u8] = b"config"; +pub const PREFIX_BALANCES: &[u8] = b"balances"; +pub const PREFIX_ALLOWANCES: &[u8] = b"allowances"; +pub const PREFIX_ALLOWED: &[u8] = b"allowed"; +pub const PREFIX_VIEW_KEY: &[u8] = b"viewingkey"; +pub const PREFIX_RECEIVERS: &[u8] = b"receivers"; + +// Config + +#[derive(Serialize, Debug, Deserialize, Clone, JsonSchema)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct Config { + pub name: String, + pub admin: Addr, + pub symbol: String, + pub decimals: u8, + // privacy configuration + pub total_supply_is_public: bool, + // is deposit enabled + pub deposit_is_enabled: bool, + // is redeem enabled + pub redeem_is_enabled: bool, + // is mint enabled + pub mint_is_enabled: bool, + // is burn enabled + pub burn_is_enabled: bool, + // the address of this contract, used to validate query permits + pub contract_address: Addr, + // coin denoms that are supported for deposit/redeem + pub supported_denoms: Vec, + // can admin add or remove supported denoms + pub can_modify_denoms: bool, +} + +pub static CONFIG: Item = Item::new(KEY_CONFIG); + +pub static TOTAL_SUPPLY: Item = Item::new(KEY_TOTAL_SUPPLY); + +pub static CONTRACT_STATUS: Item = Item::new(KEY_CONTRACT_STATUS); + +pub static PRNG: Item<[u8; SHA256_HASH_SIZE]> = Item::new(KEY_PRNG); + +pub static MINTERS: Item> = Item::new(KEY_MINTERS); + +pub static TX_COUNT: Item = Item::new(KEY_TX_COUNT); + +pub struct PrngStore {} +impl PrngStore { + pub fn load(store: &dyn Storage) -> StdResult<[u8; SHA256_HASH_SIZE]> { + PRNG.load(store).map_err(|_err| StdError::generic_err("")) + } + + pub fn save(store: &mut dyn Storage, prng_seed: [u8; SHA256_HASH_SIZE]) -> StdResult<()> { + PRNG.save(store, &prng_seed) + } +} + +pub struct MintersStore {} +impl MintersStore { + pub fn load(store: &dyn Storage) -> StdResult> { + MINTERS + .load(store) + .map_err(|_err| StdError::generic_err("")) + } + + pub fn save(store: &mut dyn Storage, minters_to_set: Vec) -> StdResult<()> { + MINTERS.save(store, &minters_to_set) + } + + pub fn add_minters(store: &mut dyn Storage, minters_to_add: Vec) -> StdResult<()> { + let mut loaded_minters = MINTERS + .load(store) + .map_err(|_err| StdError::not_found("Key not found in storage"))?; + + loaded_minters.extend(minters_to_add); + + MINTERS.save(store, &loaded_minters) + } + + pub fn remove_minters(store: &mut dyn Storage, minters_to_remove: Vec) -> StdResult<()> { + let mut loaded_minters = MINTERS + .load(store) + .map_err(|_err| StdError::generic_err(""))?; + + for minter in minters_to_remove { + loaded_minters.retain(|x| x != &minter); + } + + MINTERS.save(store, &loaded_minters) + } +} + +// To avoid balance guessing attacks based on balance overflow we need to perform safe addition and don't expose overflows to the caller. +// Assuming that max of u128 is probably an unreachable balance, we want the addition to be bounded the max of u128 +// Currently the logic here is very straight forward yet the existence of the function is mendatory for future changes if needed. +pub fn safe_add(balance: &mut u128, amount: u128) -> u128 { + // Note that new_amount can be equal to base after this operation. + // Currently we do nothing maybe on other implementations we will have something to add here + let prev_balance: u128 = *balance; + *balance = balance.saturating_add(amount); + + // Won't underflow as the minimal value possible is 0 + *balance - prev_balance +} + +pub static BALANCES: Item = Item::new(PREFIX_BALANCES); +pub struct BalancesStore {} +impl BalancesStore { + fn save(store: &mut dyn Storage, account: &Addr, amount: u128) -> StdResult<()> { + let balances = BALANCES.add_suffix(account.as_str().as_bytes()); + balances.save(store, &amount) + } + + pub fn load(store: &dyn Storage, account: &Addr) -> u128 { + let balances = BALANCES.add_suffix(account.as_str().as_bytes()); + balances.load(store).unwrap_or_default() + } + + pub fn update_balance( + store: &mut dyn Storage, + account: &Addr, + amount_to_be_updated: u128, + should_add: bool, + operation_name: &str, + decoys: &Option>, + account_random_pos: &Option, + ) -> StdResult<()> { + match decoys { + None => { + let mut balance = Self::load(store, account); + balance = match should_add { + true => { + safe_add(&mut balance, amount_to_be_updated); + balance + } + false => { + if let Some(balance) = balance.checked_sub(amount_to_be_updated) { + balance + } else { + return Err(StdError::generic_err(format!( + "insufficient funds to {operation_name}: balance={balance}, required={amount_to_be_updated}", + ))); + } + } + }; + + Self::save(store, account, balance) + } + Some(decoys_vec) => { + // It should always be set when decoys_vec is set + let account_pos = account_random_pos.unwrap(); + + let mut accounts_to_be_written: Vec<&Addr> = vec![]; + + let (first_part, second_part) = decoys_vec.split_at(account_pos); + accounts_to_be_written.extend(first_part); + accounts_to_be_written.push(account); + accounts_to_be_written.extend(second_part); + + // In a case where the account is also a decoy somehow + let mut was_account_updated = false; + + for acc in accounts_to_be_written.iter() { + // Always load account balance to obfuscate the real account + // Please note that decoys are not always present in the DB. In this case it is ok beacuse load will return 0. + let mut acc_balance = Self::load(store, acc); + let mut new_balance = acc_balance; + + if *acc == account && !was_account_updated { + was_account_updated = true; + new_balance = match should_add { + true => { + safe_add(&mut acc_balance, amount_to_be_updated); + acc_balance + } + false => { + if let Some(balance) = acc_balance.checked_sub(amount_to_be_updated) + { + balance + } else { + return Err(StdError::generic_err(format!( + "insufficient funds to {operation_name}: balance={acc_balance}, required={amount_to_be_updated}", + ))); + } + } + }; + } + Self::save(store, acc, new_balance)?; + } + + Ok(()) + } + } + } +} + +// Allowances + +#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Default, JsonSchema)] +pub struct Allowance { + pub amount: u128, + pub expiration: Option, +} + +impl Allowance { + pub fn is_expired_at(&self, block: &cosmwasm_std::BlockInfo) -> bool { + match self.expiration { + Some(time) => block.time.seconds() >= time, + None => false, // allowance has no expiration + } + } +} + +pub static ALLOWANCES: Keymap = Keymap::new(PREFIX_ALLOWANCES); +pub static ALLOWED: Keyset = Keyset::new(PREFIX_ALLOWED); +pub struct AllowancesStore {} +impl AllowancesStore { + pub fn load(store: &dyn Storage, owner: &Addr, spender: &Addr) -> Allowance { + ALLOWANCES + .add_suffix(owner.as_bytes()) + .get(store, &spender.clone()) + .unwrap_or_default() + } + + pub fn save( + store: &mut dyn Storage, + owner: &Addr, + spender: &Addr, + allowance: &Allowance, + ) -> StdResult<()> { + ALLOWED + .add_suffix(spender.as_bytes()) + .insert(store, owner)?; + ALLOWANCES + .add_suffix(owner.as_bytes()) + .insert(store, spender, allowance) + } + + pub fn all_allowances( + store: &dyn Storage, + owner: &Addr, + page: u32, + page_size: u32, + ) -> StdResult> { + ALLOWANCES + .add_suffix(owner.as_bytes()) + .paging(store, page, page_size) + } + + pub fn num_allowances(store: &dyn Storage, owner: &Addr) -> u32 { + ALLOWANCES + .add_suffix(owner.as_bytes()) + .get_len(store) + .unwrap_or(0) + } + + pub fn all_allowed( + store: &dyn Storage, + spender: &Addr, + page: u32, + page_size: u32, + ) -> StdResult> { + let owners = ALLOWED + .add_suffix(spender.as_bytes()) + .paging(store, page, page_size)?; + let owners_allowances = owners + .into_iter() + .map(|owner| (owner.clone(), AllowancesStore::load(store, &owner, spender))) + .collect(); + Ok(owners_allowances) + } + + pub fn num_allowed(store: &dyn Storage, spender: &Addr) -> u32 { + ALLOWED + .add_suffix(spender.as_bytes()) + .get_len(store) + .unwrap_or(0) + } + + pub fn is_allowed(store: &dyn Storage, owner: &Addr, spender: &Addr) -> bool { + ALLOWED + .add_suffix(spender.as_bytes()) + .contains(store, owner) + } +} + +// Receiver Interface +pub static RECEIVER_HASH: Item = Item::new(PREFIX_RECEIVERS); +pub struct ReceiverHashStore {} +impl ReceiverHashStore { + pub fn may_load(store: &dyn Storage, account: &Addr) -> StdResult> { + let receiver_hash = RECEIVER_HASH.add_suffix(account.as_str().as_bytes()); + receiver_hash.may_load(store) + } + + pub fn save(store: &mut dyn Storage, account: &Addr, code_hash: String) -> StdResult<()> { + let receiver_hash = RECEIVER_HASH.add_suffix(account.as_str().as_bytes()); + receiver_hash.save(store, &code_hash) + } +} diff --git a/contracts/external/snip20-base/src/transaction_history.rs b/contracts/external/snip20-base/src/transaction_history.rs new file mode 100644 index 0000000..0b3c327 --- /dev/null +++ b/contracts/external/snip20-base/src/transaction_history.rs @@ -0,0 +1,631 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, Coin, StdError, StdResult, Storage, Uint128}; + +use secret_toolkit::storage::AppendStore; + +use crate::state::TX_COUNT; + +const PREFIX_TXS: &[u8] = b"transactions"; +const PREFIX_TRANSFERS: &[u8] = b"transfers"; + +// Note that id is a globally incrementing counter. +// Since it's 64 bits long, even at 50 tx/s it would take +// over 11 billion years for it to rollback. I'm pretty sure +// we'll have bigger issues by then. +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +pub struct Tx { + pub id: u64, + pub from: Addr, + pub sender: Addr, + pub receiver: Addr, + pub coins: Coin, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + // The block time and block height are optional so that the JSON schema + // reflects that some SNIP-20 contracts may not include this info. + pub block_time: Option, + pub block_height: Option, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum TxAction { + Transfer { + from: Addr, + sender: Addr, + recipient: Addr, + }, + Mint { + minter: Addr, + recipient: Addr, + }, + Burn { + burner: Addr, + owner: Addr, + }, + Deposit {}, + Redeem {}, + Decoy { + address: Addr, + }, +} + +// Note that id is a globally incrementing counter. +// Since it's 64 bits long, even at 50 tx/s it would take +// over 11 billion years for it to rollback. I'm pretty sure +// we'll have bigger issues by then. +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct ExtendedTx { + pub id: u64, + pub action: TxAction, + pub coins: Coin, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + pub block_time: u64, + pub block_height: u64, +} + +// Stored types: + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] +pub struct StoredCoin { + pub denom: String, + pub amount: u128, +} + +impl From for StoredCoin { + fn from(value: Coin) -> Self { + Self { + denom: value.denom, + amount: value.amount.u128(), + } + } +} + +impl From for Coin { + fn from(value: StoredCoin) -> Self { + Self { + denom: value.denom, + amount: Uint128::new(value.amount), + } + } +} + +/// This type is the stored version of the legacy transfers +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct StoredLegacyTransfer { + id: u64, + from: Addr, + sender: Addr, + receiver: Addr, + coins: StoredCoin, + memo: Option, + block_time: u64, + block_height: u64, +} +static TRANSFERS: AppendStore = AppendStore::new(PREFIX_TRANSFERS); + +impl StoredLegacyTransfer { + pub fn into_humanized(self) -> StdResult { + let tx = Tx { + id: self.id, + from: self.from, + sender: self.sender, + receiver: self.receiver, + coins: self.coins.into(), + memo: self.memo, + block_time: Some(self.block_time), + block_height: Some(self.block_height), + }; + Ok(tx) + } + + fn append_transfer( + store: &mut dyn Storage, + tx: &StoredLegacyTransfer, + for_address: &Addr, + ) -> StdResult<()> { + let current_addr_store = TRANSFERS.add_suffix(for_address.as_bytes()); + current_addr_store.push(store, tx) + } + + pub fn get_transfers( + storage: &dyn Storage, + for_address: Addr, + page: u32, + page_size: u32, + should_filter_decoys: bool, + ) -> StdResult<(Vec, u64)> { + let current_addr_store = TRANSFERS.add_suffix(for_address.as_bytes()); + let len = current_addr_store.get_len(storage)? as u64; + // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` + // txs from the start. + let transfer_iter = current_addr_store + .iter(storage)? + .rev() + .skip((page * page_size) as _) + .take(page_size as _); + + // The `and_then` here flattens the `StdResult>` to an `StdResult` + let transfers: StdResult> = if should_filter_decoys { + transfer_iter + .filter(|transfer| match transfer { + Err(_) => true, + Ok(t) => t.block_height != 0, + }) + .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .collect() + } else { + transfer_iter + .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .collect() + }; + + transfers.map(|txs| (txs, len)) + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +enum TxCode { + Transfer = 0, + Mint = 1, + Burn = 2, + Deposit = 3, + Redeem = 4, + Decoy = 255, +} + +impl TxCode { + fn to_u8(self) -> u8 { + self as u8 + } + + fn from_u8(n: u8) -> StdResult { + use TxCode::*; + match n { + 0 => Ok(Transfer), + 1 => Ok(Mint), + 2 => Ok(Burn), + 3 => Ok(Deposit), + 4 => Ok(Redeem), + 255 => Ok(Decoy), + other => Err(StdError::generic_err(format!( + "Unexpected Tx code in transaction history: {other} Storage is corrupted.", + ))), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +struct StoredTxAction { + tx_type: u8, + address1: Option, + address2: Option, + address3: Option, +} + +impl StoredTxAction { + fn transfer(from: Addr, sender: Addr, recipient: Addr) -> Self { + Self { + tx_type: TxCode::Transfer.to_u8(), + address1: Some(from), + address2: Some(sender), + address3: Some(recipient), + } + } + fn mint(minter: Addr, recipient: Addr) -> Self { + Self { + tx_type: TxCode::Mint.to_u8(), + address1: Some(minter), + address2: Some(recipient), + address3: None, + } + } + fn burn(owner: Addr, burner: Addr) -> Self { + Self { + tx_type: TxCode::Burn.to_u8(), + address1: Some(burner), + address2: Some(owner), + address3: None, + } + } + fn deposit() -> Self { + Self { + tx_type: TxCode::Deposit.to_u8(), + address1: None, + address2: None, + address3: None, + } + } + fn redeem() -> Self { + Self { + tx_type: TxCode::Redeem.to_u8(), + address1: None, + address2: None, + address3: None, + } + } + fn decoy(recipient: &Addr) -> Self { + Self { + tx_type: TxCode::Decoy.to_u8(), + address1: Some(recipient.clone()), + address2: None, + address3: None, + } + } + + fn into_tx_action(self) -> StdResult { + let transfer_addr_err = || { + StdError::generic_err( + "Missing address in stored Transfer transaction. Storage is corrupt", + ) + }; + let mint_addr_err = || { + StdError::generic_err("Missing address in stored Mint transaction. Storage is corrupt") + }; + let burn_addr_err = || { + StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt") + }; + let decoy_addr_err = || { + StdError::generic_err("Missing address in stored decoy transaction. Storage is corrupt") + }; + + // In all of these, we ignore fields that we don't expect to find populated + let action = match TxCode::from_u8(self.tx_type)? { + TxCode::Transfer => { + let from = self.address1.ok_or_else(transfer_addr_err)?; + let sender = self.address2.ok_or_else(transfer_addr_err)?; + let recipient = self.address3.ok_or_else(transfer_addr_err)?; + TxAction::Transfer { + from, + sender, + recipient, + } + } + TxCode::Mint => { + let minter = self.address1.ok_or_else(mint_addr_err)?; + let recipient = self.address2.ok_or_else(mint_addr_err)?; + TxAction::Mint { minter, recipient } + } + TxCode::Burn => { + let burner = self.address1.ok_or_else(burn_addr_err)?; + let owner = self.address2.ok_or_else(burn_addr_err)?; + TxAction::Burn { burner, owner } + } + TxCode::Deposit => TxAction::Deposit {}, + TxCode::Redeem => TxAction::Redeem {}, + TxCode::Decoy => { + let address = self.address1.ok_or_else(decoy_addr_err)?; + TxAction::Decoy { address } + } + }; + + Ok(action) + } +} + +static TRANSACTIONS: AppendStore = AppendStore::new(PREFIX_TXS); + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct StoredExtendedTx { + id: u64, + action: StoredTxAction, + coins: StoredCoin, + memo: Option, + block_time: u64, + block_height: u64, +} + +impl StoredExtendedTx { + fn new( + id: u64, + action: StoredTxAction, + coins: Coin, + memo: Option, + block: &cosmwasm_std::BlockInfo, + ) -> Self { + Self { + id, + action, + coins: coins.into(), + memo, + block_time: block.time.seconds(), + block_height: block.height, + } + } + + fn into_humanized(self) -> StdResult { + Ok(ExtendedTx { + id: self.id, + action: self.action.into_tx_action()?, + coins: self.coins.into(), + memo: self.memo, + block_time: self.block_time, + block_height: self.block_height, + }) + } + + fn from_stored_legacy_transfer(transfer: StoredLegacyTransfer) -> Self { + let action = StoredTxAction::transfer(transfer.from, transfer.sender, transfer.receiver); + Self { + id: transfer.id, + action, + coins: transfer.coins, + memo: transfer.memo, + block_time: transfer.block_time, + block_height: transfer.block_height, + } + } + + fn append_tx( + store: &mut dyn Storage, + tx: &StoredExtendedTx, + for_address: &Addr, + ) -> StdResult<()> { + let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes()); + current_addr_store.push(store, tx) + } + + pub fn get_txs( + storage: &dyn Storage, + for_address: Addr, + page: u32, + page_size: u32, + should_filter_decoys: bool, + ) -> StdResult<(Vec, u64)> { + let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes()); + let len = current_addr_store.get_len(storage)? as u64; + + // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` + // txs from the start. + let tx_iter = current_addr_store + .iter(storage)? + .rev() + .skip((page * page_size) as _) + .take(page_size as _); + + // The `and_then` here flattens the `StdResult>` to an `StdResult` + let txs: StdResult> = if should_filter_decoys { + tx_iter + .filter(|tx| match tx { + Err(_) => true, + Ok(t) => t.action.tx_type != TxCode::Decoy.to_u8(), + }) + .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .collect() + } else { + tx_iter + .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .collect() + }; + + txs.map(|txs| (txs, len)) + } +} + +// Storage functions: + +fn increment_tx_count(store: &mut dyn Storage) -> StdResult { + let id = TX_COUNT.load(store).unwrap_or_default() + 1; + TX_COUNT.save(store, &id)?; + Ok(id) +} + +fn store_tx_with_decoys( + store: &mut dyn Storage, + tx: &StoredExtendedTx, + for_address: &Addr, + block: &cosmwasm_std::BlockInfo, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + let mut index_changer: Option = None; + match decoys { + None => StoredExtendedTx::append_tx(store, tx, for_address)?, + Some(user_decoys) => { + // It should always be set when decoys_vec is set + let account_pos = account_random_pos.unwrap(); + + for i in 0..user_decoys.len() + 1 { + if i == account_pos { + StoredExtendedTx::append_tx(store, tx, for_address)?; + index_changer = Some(1); + continue; + } + + let index = i - index_changer.unwrap_or_default(); + let decoy_action = StoredTxAction::decoy(&user_decoys[index]); + let decoy_tx = StoredExtendedTx::new( + tx.id, + decoy_action, + tx.coins.clone().into(), + tx.memo.clone(), + block, + ); + StoredExtendedTx::append_tx(store, &decoy_tx, &user_decoys[index])?; + } + } + } + + Ok(()) +} + +fn store_transfer_tx_with_decoys( + store: &mut dyn Storage, + transfer: StoredLegacyTransfer, + receiver: &Addr, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + let mut index_changer: Option = None; + match decoys { + None => StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?, + Some(user_decoys) => { + // It should always be set when decoys_vec is set + let account_pos = account_random_pos.unwrap(); + + for i in 0..user_decoys.len() + 1 { + if i == account_pos { + StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?; + index_changer = Some(1); + continue; + } + + let index = i - index_changer.unwrap_or_default(); + let decoy_transfer = StoredLegacyTransfer { + id: transfer.id, + from: transfer.from.clone(), + sender: transfer.sender.clone(), + receiver: user_decoys[index].clone(), + coins: transfer.coins.clone(), + memo: transfer.memo.clone(), + block_time: transfer.block_time, + block_height: 0, // To identify the decoy + }; + StoredLegacyTransfer::append_transfer(store, &decoy_transfer, &user_decoys[index])?; + } + } + } + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] // We just need them +pub fn store_transfer( + store: &mut dyn Storage, + owner: &Addr, + sender: &Addr, + receiver: &Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &cosmwasm_std::BlockInfo, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { denom, amount }; + let transfer = StoredLegacyTransfer { + id, + from: owner.clone(), + sender: sender.clone(), + receiver: receiver.clone(), + coins: coins.into(), + memo, + block_time: block.time.seconds(), + block_height: block.height, + }; + let tx = StoredExtendedTx::from_stored_legacy_transfer(transfer.clone()); + + // Write to the owners history if it's different from the other two addresses + if owner != sender && owner != receiver { + // cosmwasm_std::debug_print("saving transaction history for owner"); + StoredExtendedTx::append_tx(store, &tx, owner)?; + StoredLegacyTransfer::append_transfer(store, &transfer, owner)?; + } + // Write to the sender's history if it's different from the receiver + if sender != receiver { + // cosmwasm_std::debug_print("saving transaction history for sender"); + StoredExtendedTx::append_tx(store, &tx, sender)?; + StoredLegacyTransfer::append_transfer(store, &transfer, sender)?; + } + + // Always write to the recipient's history + // cosmwasm_std::debug_print("saving transaction history for receiver"); + store_tx_with_decoys(store, &tx, receiver, block, decoys, account_random_pos)?; + store_transfer_tx_with_decoys(store, transfer, receiver, decoys, account_random_pos)?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] // We just need them +pub fn store_mint( + store: &mut dyn Storage, + minter: Addr, + recipient: Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &cosmwasm_std::BlockInfo, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { denom, amount }; + let action = StoredTxAction::mint(minter.clone(), recipient.clone()); + let tx = StoredExtendedTx::new(id, action, coins, memo, block); + + if minter != recipient { + store_tx_with_decoys(store, &tx, &recipient, block, decoys, account_random_pos)?; + } + + StoredExtendedTx::append_tx(store, &tx, &minter)?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub fn store_burn( + store: &mut dyn Storage, + owner: Addr, + burner: Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &cosmwasm_std::BlockInfo, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { denom, amount }; + let action = StoredTxAction::burn(owner.clone(), burner.clone()); + let tx = StoredExtendedTx::new(id, action, coins, memo, block); + + if burner != owner { + store_tx_with_decoys(store, &tx, &owner, block, decoys, account_random_pos)?; + } + + StoredExtendedTx::append_tx(store, &tx, &burner)?; + Ok(()) +} + +pub fn store_deposit( + store: &mut dyn Storage, + recipient: &Addr, + amount: Uint128, + denom: String, + block: &cosmwasm_std::BlockInfo, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { denom, amount }; + let action = StoredTxAction::deposit(); + let tx = StoredExtendedTx::new(id, action, coins, None, block); + + store_tx_with_decoys(store, &tx, recipient, block, decoys, account_random_pos) +} + +pub fn store_redeem( + store: &mut dyn Storage, + redeemer: &Addr, + amount: Uint128, + denom: String, + block: &cosmwasm_std::BlockInfo, + decoys: &Option>, + account_random_pos: &Option, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { denom, amount }; + let action = StoredTxAction::redeem(); + let tx = StoredExtendedTx::new(id, action, coins, None, block); + + store_tx_with_decoys(store, &tx, redeemer, block, decoys, account_random_pos) +} diff --git a/contracts/external/snip20-base/tests/example-receiver/.gitignore b/contracts/external/snip20-base/tests/example-receiver/.gitignore new file mode 100644 index 0000000..e64a2c1 --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/.gitignore @@ -0,0 +1,17 @@ +# Build results +/target +contract.wasm +contract.wasm.gz + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/external/snip20-base/tests/example-receiver/Cargo.toml b/contracts/external/snip20-base/tests/example-receiver/Cargo.toml new file mode 100644 index 0000000..8d10e29 --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "receiver-contract" +version = "0.1.0" +authors = ["TomL94 "] +edition = "2018" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-std = {workspace = true} +cosmwasm-storage = {workspace = true} +schemars = {workspace = true} +serde = {workspace = true} +snafu = {workspace = true} diff --git a/contracts/external/snip20-base/tests/example-receiver/Makefile b/contracts/external/snip20-base/tests/example-receiver/Makefile new file mode 100644 index 0000000..542f013 --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/Makefile @@ -0,0 +1,52 @@ +.PHONY: all +all: clippy test + +.PHONY: check +check: + cargo check + +.PHONY: clippy +clippy: + cargo clippy + +.PHONY: test +test: unit-test + +.PHONY: unit-test +unit-test: + cargo test + +.PHONY: compile _compile +compile: _compile contract.wasm.gz +_compile: + cargo build --target wasm32-unknown-unknown + cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm + +.PHONY: compile-optimized _compile-optimized +compile-optimized: _compile-optimized contract.wasm.gz +_compile-optimized: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + @# The following line is not necessary, may work only on linux (extra size optimization) + wasm-opt -Os ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm + +.PHONY: compile-optimized-reproducible +compile-optimized-reproducible: + docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + enigmampc/secret-contract-optimizer + +contract.wasm.gz: contract.wasm + cat ./contract.wasm | gzip -9 > ./contract.wasm.gz + +contract.wasm: + cp ./target/wasm32-unknown-unknown/release/receiver_contract.wasm ./contract.wasm + +.PHONY: schema +schema: + cargo run --example schema + +.PHONY: clean +clean: + cargo clean + rm -f ./contract.wasm ./contract.wasm.gz diff --git a/contracts/external/snip20-base/tests/example-receiver/README.md b/contracts/external/snip20-base/tests/example-receiver/README.md new file mode 100644 index 0000000..f70488a --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/README.md @@ -0,0 +1,4 @@ +# Exmaple Receiver Contract + +This contract shows how to correctly implement the receiver contract, in order to interact +with a SNIP-20 contract such as `secret-secret`. diff --git a/contracts/external/snip20-base/tests/example-receiver/rustfmt.toml b/contracts/external/snip20-base/tests/example-receiver/rustfmt.toml new file mode 100644 index 0000000..11a85e6 --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/rustfmt.toml @@ -0,0 +1,15 @@ +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" + diff --git a/contracts/external/snip20-base/tests/example-receiver/src/contract.rs b/contracts/external/snip20-base/tests/example-receiver/src/contract.rs new file mode 100644 index 0000000..b5f072b --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/src/contract.rs @@ -0,0 +1,176 @@ +use cosmwasm_std::{ + entry_point, from_binary, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, + Env, MessageInfo, Response, StdError, StdResult, Uint128, WasmMsg, +}; + +use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg, Snip20Msg}; +use crate::state::{config, config_read, State}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let state = State { + count: msg.count, + owner: deps.api.addr_canonicalize(info.sender.as_str())?, + known_snip_20: vec![], + }; + + config(deps.storage).save(&state)?; + + Ok(Response::default()) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Increment {} => try_increment(deps, env), + ExecuteMsg::Reset { count } => try_reset(deps, info, count), + ExecuteMsg::Register { reg_addr, reg_hash } => try_register(deps, env, reg_addr, reg_hash), + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + memo: _, + } => try_receive(deps, env, info, sender, from, amount, msg), + ExecuteMsg::Redeem { + addr, + hash, + to, + amount, + denom, + } => try_redeem(deps, addr, hash, to, amount, denom), + ExecuteMsg::Fail {} => try_fail(), + } +} + +pub fn try_increment(deps: DepsMut, _env: Env) -> StdResult { + let mut count = 0; + config(deps.storage).update(|mut state| -> StdResult<_> { + state.count += 1; + count = state.count; + Ok(state) + })?; + + Ok(Response::new().add_attribute("count", count.to_string())) +} + +pub fn try_reset(deps: DepsMut, info: MessageInfo, count: i32) -> StdResult { + let sender_address_raw = deps.api.addr_canonicalize(info.sender.as_str())?; + config(deps.storage).update(|mut state| { + if sender_address_raw != state.owner { + return Err(StdError::generic_err("Only the owner can reset count")); + } + state.count = count; + Ok(state) + })?; + Ok(Response::default()) +} + +pub fn try_register( + deps: DepsMut, + env: Env, + reg_addr: Addr, + reg_hash: String, +) -> StdResult { + let mut conf = config(deps.storage); + let mut state = conf.load()?; + if !state.known_snip_20.contains(®_addr) { + state.known_snip_20.push(reg_addr.clone()); + } + conf.save(&state)?; + + let msg = to_binary(&Snip20Msg::register_receive(env.contract.code_hash))?; + let message = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: reg_addr.into_string(), + code_hash: reg_hash, + msg, + funds: vec![], + }); + + Ok(Response::new().add_message(message)) +} + +pub fn try_receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + _from: Addr, + _amount: Uint128, + msg: Binary, +) -> StdResult { + let msg: ExecuteMsg = from_binary(&msg)?; + + if matches!(msg, ExecuteMsg::Receive { .. }) { + return Err(StdError::generic_err( + "Recursive call to receive() is not allowed", + )); + } + + // let state = config_read(&deps.storage).load()?; + // if !state.known_snip_20.contains(&env.message.sender) { + // return Err(StdError::generic_err(format!( + // "{} is not a known SNIP-20 coin that this contract registered to", + // env.message.sender + // ))); + // } + + /* use sender & amount */ + execute(deps, env, info, msg) +} + +fn try_redeem( + _deps: DepsMut, + addr: Addr, + hash: String, + to: Addr, + amount: Uint128, + denom: Option, +) -> StdResult { + // let state = config_read(&deps.storage).load()?; + // if !state.known_snip_20.contains(&addr) { + // return Err(StdError::generic_err(format!( + // "{} is not a known SNIP-20 coin that this contract registered to", + // addr + // ))); + // } + let unwrapped_denom = denom.unwrap_or("uscrt".to_string()); + + let msg = to_binary(&Snip20Msg::redeem(amount, unwrapped_denom.clone()))?; + let secret_redeem = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: addr.into_string(), + code_hash: hash, + msg, + funds: vec![], + }); + let redeem = CosmosMsg::Bank(BankMsg::Send { + // unsafe, don't use in production obviously + amount: vec![Coin::new(amount.u128(), unwrapped_denom)], + to_address: to.into_string(), + }); + + Ok(Response::new() + .add_message(secret_redeem) + .add_message(redeem)) +} + +fn try_fail() -> StdResult { + Err(StdError::generic_err("intentional failure")) +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetCount {} => to_binary(&query_count(deps)?), + } +} + +fn query_count(deps: Deps) -> StdResult { + let state = config_read(deps.storage).load()?; + Ok(CountResponse { count: state.count }) +} diff --git a/contracts/external/snip20-base/tests/example-receiver/src/lib.rs b/contracts/external/snip20-base/tests/example-receiver/src/lib.rs new file mode 100644 index 0000000..4934c19 --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/src/lib.rs @@ -0,0 +1,3 @@ +pub mod contract; +pub mod msg; +pub mod state; diff --git a/contracts/external/snip20-base/tests/example-receiver/src/msg.rs b/contracts/external/snip20-base/tests/example-receiver/src/msg.rs new file mode 100644 index 0000000..7ab0b00 --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/src/msg.rs @@ -0,0 +1,81 @@ +use cosmwasm_std::{Addr, Binary, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct InstantiateMsg { + pub count: i32, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Increment {}, + Reset { + count: i32, + }, + Register { + reg_addr: Addr, + reg_hash: String, + }, + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Binary, + }, + Redeem { + addr: Addr, + hash: String, + to: Addr, + amount: Uint128, + denom: Option, + }, + Fail {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + GetCount {}, +} + +// We define a custom struct for each query response +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CountResponse { + pub count: i32, +} + +// Messages sent to SNIP-20 contracts +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Snip20Msg { + RegisterReceive { + code_hash: String, + padding: Option, + }, + Redeem { + amount: Uint128, + denom: String, + padding: Option, + }, +} + +impl Snip20Msg { + pub fn register_receive(code_hash: String) -> Self { + Snip20Msg::RegisterReceive { + code_hash, + padding: None, // TODO add padding calculation + } + } + + pub fn redeem(amount: Uint128, denom: String) -> Self { + Snip20Msg::Redeem { + amount, + denom, + padding: None, // TODO add padding calculation + } + } +} diff --git a/contracts/external/snip20-base/tests/example-receiver/src/state.rs b/contracts/external/snip20-base/tests/example-receiver/src/state.rs new file mode 100644 index 0000000..09e8b47 --- /dev/null +++ b/contracts/external/snip20-base/tests/example-receiver/src/state.rs @@ -0,0 +1,22 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, CanonicalAddr, Storage}; +use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; + +pub static CONFIG_KEY: &[u8] = b"config"; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct State { + pub count: i32, + pub owner: CanonicalAddr, + pub known_snip_20: Vec, +} + +pub fn config(storage: &mut dyn Storage) -> Singleton { + singleton(storage, CONFIG_KEY) +} + +pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, CONFIG_KEY) +} diff --git a/contracts/external/snip20-base/tests/integration.rs b/contracts/external/snip20-base/tests/integration.rs new file mode 100644 index 0000000..35fb6b8 --- /dev/null +++ b/contracts/external/snip20-base/tests/integration.rs @@ -0,0 +1,3 @@ +#[test] +#[ignore] +fn empty_test() {} diff --git a/contracts/external/snip20-base/tests/integration.sh b/contracts/external/snip20-base/tests/integration.sh new file mode 100755 index 0000000..dba743e --- /dev/null +++ b/contracts/external/snip20-base/tests/integration.sh @@ -0,0 +1,1754 @@ +#!/bin/bash + +set -eu +set -o pipefail # If anything in a pipeline fails, the pipe's exit status is a failure +#set -x # Print all commands for debugging + +declare -a KEY=(a b c d) + +declare -A FROM=( + [a]='-y --from a' + [b]='-y --from b' + [c]='-y --from c' + [d]='-y --from d' +) + +# This means we don't need to configure the cli since it uses the preconfigured cli in the docker. +# We define this as a function rather than as an alias because it has more flexible expansion behavior. +# In particular, it's not possible to dynamically expand aliases, but `tx_of` dynamically executes whatever +# we specify in its arguments. +function secretcli() { + docker exec secretdev /usr/bin/secretd "$@" +} + +# Just like `echo`, but prints to stderr +function log() { + echo "$@" >&2 +} + +# suppress all output to stdout for the command described in the arguments +function quiet() { + "$@" >/dev/null +} + +# suppress all output to stdout and stderr for the command described in the arguments +function silent() { + "$@" >/dev/null 2>&1 +} + +# Pad the string in the first argument to 256 bytes, using spaces +function pad_space() { + printf '%-256s' "$1" +} + +function assert_eq() { + set -e + local left="$1" + local right="$2" + local message + + if [[ "$(echo $left | xargs)" != "$(echo $right | xargs)" ]]; then + if [ -z ${3+x} ]; then + local lineno="${BASH_LINENO[0]}" + message="assertion failed on line $lineno - both sides differ. left: ${left@Q}, right: ${right@Q}" + else + message="$3" + fi + log "$message" + return 1 + fi + + return 0 +} + +function assert_ne() { + set -e + local left="$1" + local right="$2" + local message + + if [[ "$left" == "$right" ]]; then + if [ -z ${3+x} ]; then + local lineno="${BASH_LINENO[0]}" + message="assertion failed on line $lineno - both sides are equal. left: ${left@Q}, right: ${right@Q}" + else + message="$3" + fi + + log "$message" + return 1 + fi + + return 0 +} + +# what the fuck is this? +function assert_contains() { + set -e + local str="$1" + local substr="$2" + local message + + if [[ "$str" == "$substr" ]]; then + if [ -z ${3+x} ]; then + local lineno="${BASH_LINENO[0]}" + message="assertion failed on line $lineno - str doesn't contain substr. str: ${str@Q}, substr: ${substr@Q}" + else + message="$3" + fi + log "$message" + return 1 + fi + + return 0 +} + +declare -A ADDRESS=( + [a]="$(secretcli keys show --address a)" + [b]="$(secretcli keys show --address b)" + [c]="$(secretcli keys show --address c)" + [d]="$(secretcli keys show --address d)" +) + +declare -A VK=([a]='' [b]='' [c]='' [d]='') + +# Generate a label for a contract with a given code id +# This just adds "contract_" before the code id. +function label_by_id() { + local id="$1" + echo "contract_$id" +} + +# Keep polling the blockchain until the tx completes. +# The first argument is the tx hash. +# The second argument is a message that will be logged after every failed attempt. +# The tx information will be returned. +function wait_for_tx() { + local tx_hash="$1" + local message="$2" + + local result + + log "waiting on tx: $tx_hash" + # secretcli will only print to stdout when it succeeds + until result="$(secretcli query tx "$tx_hash" 2>/dev/null)"; do + log "$message" + sleep 1 + done + + # log out-of-gas events + if quiet jq -e '.raw_log | startswith("execute contract failed: Out of gas: ") or startswith("out of gas:")' <<<"$result"; then + log "$(jq -r '.raw_log' <<<"$result")" + fi + + echo "$result" +} + +# This is a wrapper around `wait_for_tx` that also decrypts the response, +# and returns a nonzero status code if the tx failed +function wait_for_compute_tx() { + local tx_hash="$1" + local message="$2" + local return_value=0 + local result + local decrypted + + result="$(wait_for_tx "$tx_hash" "$message")" + # log "$result" + if quiet jq -e '.logs == null' <<<"$result"; then + return_value=1 + fi + decrypted="$(secretcli query compute tx "$tx_hash")" || return + log "$decrypted" + echo "$decrypted" + + return "$return_value" +} + +# If the tx failed, return a nonzero status code. +# The decrypted error or message will be echoed +function check_tx() { + local tx_hash="$1" + local result + local return_value=0 + + result="$(secretcli query tx "$tx_hash")" + if quiet jq -e '.logs == null' <<<"$result"; then + return_value=1 + fi + decrypted="$(secretcli query compute tx "$tx_hash")" || return + log "$decrypted" + echo "$decrypted" + + return "$return_value" +} + +# Extract the tx_hash from the output of the command +function tx_of() { + "$@" | jq -r '.txhash' +} + +# Extract the output_data_as_string from the output of the command +function data_of() { + local result="$("$@" | jq -ec '.answers[0].output_data_as_string' | sed 's/\\//g' | sed 's/ //g' | sed 's/^"\(.*\)"$/\1/')" + echo "$result" +} + +function extract_exec_error() { + set -e + local search_pattern + local error_msg + + error_msg="$(jq -r '.output_error' <<<"$1")" + search_pattern=${error_msg#*"$2"} + echo $search_pattern +} + +# Send a compute transaction and return the tx hash. +# All arguments to this function are passed directly to `secretcli tx compute execute`. +function compute_execute() { + tx_of secretcli tx compute execute "$@" +} + +# Send a query to the contract. +# All arguments to this function are passed directly to `secretcli query compute query`. +function compute_query() { + secretcli query compute query "$@" +} + +function upload_code() { + set -e + local directory="$1" + local tx_hash + local code_id + + tx_hash="$(tx_of secretcli tx compute store "code/$directory/contract.wasm.gz" ${FROM[a]} --gas 30000000)" + code_id="$( + wait_for_tx "$tx_hash" 'waiting for contract upload' | + jq -r '.logs[0].events[0].attributes[] | select(.key == "code_id") | .value' + )" + + log "uploaded contract #$code_id" + + echo "$code_id" +} + +function instantiate() { + set -e + local code_id="$1" + local init_msg="$2" + + log 'sending init message:' + log "${init_msg@Q}" + + local tx_hash + tx_hash="$(tx_of secretcli tx compute instantiate "$code_id" "$init_msg" --label "$(label_by_id "$code_id")" ${FROM[a]} --gas 10000000)" + wait_for_tx "$tx_hash" 'waiting for init to complete' +} + +# This function uploads and instantiates a contract, and returns the new contract's address +function create_contract() { + set -e + local dir="$1" + local init_msg="$2" + + local code_id + code_id="$(upload_code "$dir")" + + local init_result + init_result="$(instantiate "$code_id" "$init_msg")" + + if quiet jq -e '.logs == null' <<<"$init_result"; then + log "$(secretcli query compute tx "$(jq -r '.txhash' <<<"$init_result")")" + return 1 + fi + + jq -r '.logs[0].events[0].attributes[] | select(.key == "contract_address") | .value' <<<"$init_result" +} + +function deposit() { + set -e + local contract_addr="$1" + local key="$2" + local amount="$3" + + local deposit_message='{"deposit":{"padding":":::::::::::::::::"}}' + local tx_hash + local deposit_response + tx_hash="$(compute_execute "$contract_addr" "$deposit_message" --amount "${amount}uscrt" ${FROM[$key]} --gas 250000)" + deposit_response="$(wait_for_compute_tx "$tx_hash" "waiting for deposit to \"$key\" to process" | jq -ec '.answers[0].output_data_as_string' | sed 's/\\//g' | sed 's/ //g' | sed 's/^"\(.*\)"$/\1/')" + assert_eq "$deposit_response" "$(pad_space '{"deposit":{"status":"success"}}' | sed 's/ //g')" + log "deposited ${amount}uscrt to \"$key\" successfully" + echo "$tx_hash" +} + +function mint() { + set -e + local contract_addr="$1" + local key="$2" + local recipient="$3" + local amount="$4" + + local mint_message='{"mint":{"recipient":"'"$recipient"'","amount":"'"$amount"'","padding":":::::::::::::::::"}}' + local tx_hash + local deposit_response + tx_hash="$(compute_execute "$contract_addr" "$mint_message" ${FROM[$key]} --gas 251000)" + echo "$tx_hash" + deposit_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for mint to \"$recipient\" to process")" + assert_eq "$deposit_response" "$(pad_space '{"mint":{"status":"success"}}' | sed 's/ //g')" + log "minted ${amount}uscrt for \"$recipient\" successfully" +} + +function burn() { + set -e + local contract_addr="$1" + local key="$2" + local amount="$3" + + local burn_message='{"burn":{"amount":"'"$amount"'"}}' + local tx_hash + local burn_response + tx_hash="$(compute_execute "$contract_addr" "$burn_message" ${FROM[$key]} --gas 250000)" + echo "$tx_hash" + burn_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for burn for \"$key\" to process")" + log "$burn_response" + assert_eq "$burn_response" "$(pad_space '{"burn":{"status":"success"}}' | sed 's/ //g')" + log "burned ${amount}uscrt for \"$key\" successfully" +} + +function get_balance() { + set -e + local contract_addr="$1" + local key="$2" + + log "querying balance for \"$key\"" + local balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' + local balance_response + balance_response="$(compute_query "$contract_addr" "$balance_query")" + log "balance response was: $balance_response" + jq -r '.balance.amount' <<<"$balance_response" +} + +# Redeem some SCRT from an account +# As you can see, verifying this is happening correctly requires a lot of code +# so I separated it to its own function, because it's used several times. +function redeem() { + set -e + local contract_addr="$1" + local key="$2" + local amount="$3" + + local redeem_message + local tx_hash + local redeem_tx + local transfer_attributes + local redeem_response + + log "redeeming \"$key\"" + redeem_message='{"redeem":{"amount":"'"$amount"'","denom":"uscrt"}}' + tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" + redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from \"$key\" to process")" + transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" + assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "${ADDRESS[$key]}" + assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt" + log "redeem response for \"$key\" returned ${amount}uscrt" + + redeem_response="$(data_of check_tx "$tx_hash")" + assert_eq "$redeem_response" "$(pad_space '{"redeem":{"status":"success"}}' | sed 's/ //g')" + log "redeemed ${amount} from \"$key\" successfully" + echo "$tx_hash" +} + +function get_token_info() { + set -e + local contract_addr="$1" + + local token_info_query='{"token_info":{}}' + compute_query "$contract_addr" "$token_info_query" +} + +function increase_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + local amount="$4" + + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_message='{"increase_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' + local allowance_response + + tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" + allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the increase of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" + assert_eq "$(jq -r '.increase_allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.increase_allowance.owner' <<<"$allowance_response")" "$owner_address" + jq -r '.increase_allowance.allowance' <<<"$allowance_response" + log "Increased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" +} + +function decrease_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + local amount="$4" + + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_message='{"decrease_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' + local allowance_response + + tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" + allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the decrease of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" + assert_eq "$(jq -r '.decrease_allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.decrease_allowance.owner' <<<"$allowance_response")" "$owner_address" + jq -r '.decrease_allowance.allowance' <<<"$allowance_response" + log "Decreased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" +} + +function get_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + + log "querying allowance given to \"$spender_key\" by \"$owner_key\"" + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_query='{"allowance":{"spender":"'"$spender_address"'","owner":"'"$owner_address"'","key":"'"${VK[$owner_key]}"'"}}' + local allowance_response + allowance_response="$(compute_query "$contract_addr" "$allowance_query")" + log "allowance response was: $allowance_response" + assert_eq "$(jq -r '.allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.allowance.owner' <<<"$allowance_response")" "$owner_address" + jq -r '.allowance.allowance' <<<"$allowance_response" +} + +# This function is the same as above, but it also checks the expiration +function check_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + local expiration="$4" + + log "querying allowance given to \"$spender_key\" by \"$owner_key\"" + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_query='{"allowance":{"spender":"'"$spender_address"'","owner":"'"$owner_address"'","key":"'"${VK[$owner_key]}"'"}}' + local allowance_response + allowance_response="$(compute_query "$contract_addr" "$allowance_query")" + log "allowance response was: $allowance_response" + assert_eq "$(jq -r '.allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.allowance.owner' <<<"$allowance_response")" "$owner_address" + assert_eq "$(jq -r '.allowance.expiration' <<<"$allowance_response")" "$expiration" + jq -r '.allowance.allowance' <<<"$allowance_response" +} + +function log_test_header() { + log " ########### Starting ${FUNCNAME[1]} ###############################################################################################################################################" +} + +function extract_viewing_key_from_result() { + set -e + local tx_hash="$1" + local key="$2" + local viewing_key + + viewing_key_response="$(wait_for_compute_tx "$tx_hash" "waiting for viewing key for \"$key\" to be created")" + viewing_key="$(jq -ec '.answers[0].output_data_as_string' <<<"$viewing_key_response" | cut -d'\' -f 6 | cut -c2-)" + + log "viewing key for \"$key\" set to ${viewing_key}" + echo "$viewing_key" +} + +function test_viewing_key() { + set -e + local contract_addr="$1" + + log_test_header + + # common variables + local result + local tx_hash + + # query balance. Should fail. + local wrong_key + wrong_key="$(xxd -ps <<<'wrong-key')" + local balance_query + local expected_error='{"viewing_key_error":{"msg":"Wrong viewing key for this address or viewing key not set"}}' + for key in "${KEY[@]}"; do + log "querying balance for \"$key\" with wrong viewing key" + balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"$wrong_key"'"}}' + result="$(compute_query "$contract_addr" "$balance_query")" + assert_eq "$result" "$expected_error" + done + + # Create viewing keys + local create_viewing_key_message='{"create_viewing_key":{"entropy":"MyPassword123"}}' + local viewing_key_response + for key in "${KEY[@]}"; do + log "creating viewing key for \"$key\"" + tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[$key]} --gas 1400000)" + VK[$key]="$(extract_viewing_key_from_result "$tx_hash" "$key")" + + + if [[ "${VK[$key]}" =~ ^api_key_ ]]; then + log "viewing key \"$key\" seems valid" + else + log 'viewing key is invalid' + return 1 + fi + done + + # Check that all viewing keys are different despite using the same entropy + assert_ne "${VK[a]}" "${VK[b]}" + assert_ne "${VK[b]}" "${VK[c]}" + assert_ne "${VK[c]}" "${VK[d]}" + + # query balance. Should succeed. + local balance_query + for key in "${KEY[@]}"; do + balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' + log "querying balance for \"$key\" with correct viewing key" + result="$(compute_query "$contract_addr" "$balance_query")" + if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then + log "Balance query returned unexpected response: ${result@Q}" + return 1 + fi + done + + # Change viewing keys + local vk2_a + + log 'creating new viewing key for "a"' + tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[a]} --gas 1400000)" + vk2_a="$(extract_viewing_key_from_result "$tx_hash" "$key")" + assert_ne "${VK[a]}" "$vk2_a" + + # query balance with old keys. Should fail. + log 'querying balance for "a" with old viewing key' + local balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + assert_eq "$result" "$expected_error" + + # query balance with new keys. Should succeed. + log 'querying balance for "a" with new viewing key' + balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then + log "Balance query returned unexpected response: ${result@Q}" + return 1 + fi + + # Set the vk for "a" to the original vk + log 'setting the viewing key for "a" back to the first one' + local set_viewing_key_message='{"set_viewing_key":{"key":"'"${VK[a]}"'"}}' + tx_hash="$(compute_execute "$contract_addr" "$set_viewing_key_message" ${FROM[a]} --gas 1400000)" + viewing_key_response="$(wait_for_compute_tx "$tx_hash" "waiting for viewing key for "a" to be set")" + viewing_key_response="$(jq -ec '.answers[0].output_data_as_string' <<<"$viewing_key_response" | cut -d'\' -f 6 | cut -c2-)" + assert_eq "$viewing_key_response" "success" + + # try to use the new key - should fail + log 'querying balance for "a" with new viewing key' + balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + assert_eq "$result" "$expected_error" + + # try to use the old key - should succeed + log 'querying balance for "a" with old viewing key' + balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then + log "Balance query returned unexpected response: ${result@Q}" + return 1 + fi +} + +function test_permit() { + set -e + local contract_addr="$1" + + log_test_header + + # common variables + local result + local tx_hash + + # fail due to token not in permit + secretcli keys delete banana -yf || true + secretcli keys add banana + local wrong_contract=$(secretcli keys show -a banana) + + local permit + permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error="Error: query result: Generic error: Permit doesn't apply to token \"$contract_addr\", allowed tokens: [\"$wrong_contract\"]" + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" with wrong permit for that contract" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail due to revoked permit + local permit + permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"to_be_revoked","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" with a revoked permit" + tx_hash="$(compute_execute "$contract_addr" '{"revoke_permit":{"permit_name":"to_be_revoked"}}' ${FROM[$key]} --gas 250000)" + wait_for_compute_tx "$tx_hash" "waiting for revoke_permit from \"$key\" to process" + + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"to_be_revoked","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: Permit \"to_be_revoked\" was revoked by account \"${ADDRESS[$key]}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail due to params not matching params that were signed on + local permit + permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" with params not matching permit" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"not_blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: Failed to verify signatures for the given permit" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail balance query due to no balance permission + local permit_conf + permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" without the right permission" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query balance, got permissions [History]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail history query due to no history permission + local permit_conf + permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying history for \"$key\" without the right permission" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + + permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + + permit_query='{"with_permit":{"query":{"transaction_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail allowance query due to no allowance permission + local permit_conf + permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying allowance for \"$key\" without the right permission" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query allowance, got permissions [History]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail allowance query due to no permit signer not owner or spender + local permit + wrong_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error + log "permit querying allowance without signer being the owner or spender" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$wrong_permit"') --from a") + permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"$wrong_contract"'","spender":"'"$wrong_contract"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: Cannot query allowance. Requires permit for either owner \"$wrong_contract\" or spender \"$wrong_contract\", got permit for \"${ADDRESS[a]}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + + # succeed balance query + local permit + local good_permit + good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_output + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\"" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_output="{\"balance\":{\"amount\":\"0\"}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + done + + # succeed history queries + local permit + local good_permit + good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_output + for key in "${KEY[@]}"; do + log "permit querying history for \"$key\"" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + + permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_output="{\"transfer_history\":{\"txs\":[],\"total\":0}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + + permit_query='{"with_permit":{"query":{"transaction_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_output="{\"transaction_history\":{\"txs\":[],\"total\":0}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + done + + # succeed allowance query + local permit + local good_permit + good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_output + for key in "${KEY[@]}"; do + log "permit querying history for \"$key\"" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + + permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' + expected_output="{\"allowance\":{\"spender\":\"${ADDRESS[$key]}\",\"owner\":\"${ADDRESS[$key]}\",\"allowance\":\"0\",\"expiration\":null}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + done +} + +function test_deposit() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + local -A deposits=([a]=1000000 [b]=2000000 [c]=3000000 [d]=4000000) + local tx_hash + local native_tx + local timestamp + local block_height + for key in "${KEY[@]}"; do + tx_hash="$(deposit "$contract_addr" "$key" "${deposits[$key]}")" + native_tx="$(secretcli q tx "$tx_hash")" + + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + quiet check_latest_tx_history_deposit "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" "${deposits[$key]}" "$timestamp" "$block_height" + done + + # Query the balances of the accounts and make sure they have the right balances. + for key in "${KEY[@]}"; do + assert_eq "$(get_balance "$contract_addr" "$key")" "${deposits[$key]}" + done + + # Try to overdraft + local redeem_message + local overdraft + local redeem_response + for key in "${KEY[@]}"; do + overdraft="$((deposits[$key] + 1))" + redeem_message='{"redeem":{"amount":"'"$overdraft"'","denom":"uscrt"}}' + tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! redeem_response="$(wait_for_compute_tx "$tx_hash" "waiting for overdraft from \"$key\" to process")" + log "trying to overdraft from \"$key\" was rejected" + assert_eq \ + "$(extract_exec_error "$redeem_response" "error: ")" \ + "insufficient funds to redeem: balance=${deposits[$key]}, required=$overdraft" + done + + # Withdraw Everything + local tx_hash + local native_tx + local timestamp + local block_height + for key in "${KEY[@]}"; do + tx_hash="$(redeem "$contract_addr" "$key" "${deposits[$key]}")" + native_tx="$(secretcli q tx "$tx_hash")" + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + quiet check_latest_tx_history_redeem "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" "${deposits[$key]}" "$timestamp" "$block_height" + done + + # Check the balances again. They should all be empty + for key in "${KEY[@]}"; do + assert_eq "$(get_balance "$contract_addr" "$key")" 0 + done +} + +function unix_time_of_tx() { + set -e + local tx="$1" + + date -d "$(jq -r '.timestamp' <<<"$tx")" '+%s' +} + +function get_transfer_history() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local page_size="$4" + local page="$5" + + local transfer_history_query + local transfer_history_response + transfer_history_query='{"transfer_history":{"address":"'"$account"'","key":"'"$viewing_key"'","page_size":'"$page_size"',"page":'"$page"',"should_filter_decoys":false}}' + transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" + log "$transfer_history_response" + # There's no good way of tracking the exact expected value, + # so we just check that the `total` field is a number + quiet jq -e '.transfer_history.total | numbers' <<<"$transfer_history_response" + jq -r '.transfer_history.txs' <<<"$transfer_history_response" +} + +# This function checks that the latest tx history for the account matches +# the expected parameters. +# The id of the tx is printed out. +function check_latest_transfer_history() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local sender="$4" + local from="$5" + local receiver="$6" + local amount="$7" + local block_time="$8" + local block_height="$9" + + local txs + local tx + + txs="$(get_transfer_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.sender' <<<"$tx")" "$sender" + assert_eq "$(jq -r '.from' <<<"$tx")" "$from" + assert_eq "$(jq -r '.receiver' <<<"$tx")" "$receiver" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function get_transaction_history() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local page_size="$4" + local page="$5" + + local transaction_history_query + local transaction_history_response + transaction_history_query='{"transaction_history":{"address":"'"$account"'","key":"'"$viewing_key"'","page_size":'"$page_size"',"page":'"$page"', "should_filter_decoys":false}}' + transaction_history_response="$(compute_query "$contract_addr" "$transaction_history_query")" + log "$transaction_history_response" + # There's no good way of tracking the exact expected value, + # so we just check that the `total` field is a number + quiet jq -e '.transaction_history.total | numbers' <<<"$transaction_history_response" + jq -r '.transaction_history.txs' <<<"$transaction_history_response" +} + +function check_latest_tx_history_transfer() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local sender="$4" + local from="$5" + local recipient="$6" + local amount="$7" + local block_time="$8" + local block_height="$9" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.action.transfer.sender' <<<"$tx")" "$sender" + assert_eq "$(jq -r '.action.transfer.from' <<<"$tx")" "$from" + assert_eq "$(jq -r '.action.transfer.recipient' <<<"$tx")" "$recipient" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_mint() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local minter="$4" + local recipient="$5" + local amount="$6" + local block_time="$7" + local block_height="$8" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.action.mint.minter' <<<"$tx")" "$minter" + assert_eq "$(jq -r '.action.mint.recipient' <<<"$tx")" "$recipient" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_burn() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local burner="$4" + local owner="$5" + local amount="$6" + local block_time="$7" + local block_height="$8" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.action.burn.burner' <<<"$tx")" "$burner" + assert_eq "$(jq -r '.action.burn.owner' <<<"$tx")" "$owner" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_deposit() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local amount="$4" + local block_time="$5" + local block_height="$6" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + quiet jq -e '.action.deposit | objects' <<<"$tx" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'uscrt' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_redeem() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local amount="$4" + local block_time="$5" + local block_height="$6" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + quiet jq -e '.action.redeem | objects' <<<"$tx" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function test_transfer() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + # Check "a" and "b" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Try to transfer more than "a" has + log 'transferring funds from "a" to "b", but more than "a" has' + local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + log "trying to overdraft from \"a\" to transfer to \"b\" was rejected" + assert_eq "$(extract_exec_error "$transfer_response" "error: ")" "insufficient funds to transfer: balance=1000000, required=1000001" + + # Check both a and b, that their last transaction is not for 1000001 uscrt + local txs + for key in a b; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Transfer from "a" to "b" + log 'transferring funds from "a" to "b"' + local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"400000"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 200000)" + transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + assert_eq "$transfer_response" "$(pad_space '{"transfer":{"status":"success"}}' | sed 's/ //g')" + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check for both "a" and "b" that they recorded the transfer + local -A tx_ids + local tx_id + for key in a b; do + log "querying the transfer history of \"$key\"" + tx_ids[$key]="$( + check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "${ADDRESS[b]}" 400000 "$timestamp" "$block_height" + )" + tx_id="$( + check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "${ADDRESS[b]}" 400000 "$timestamp" "$block_height" + )" + assert_eq "$tx_id" "${tx_ids[$key]}" + done + + assert_eq "${tx_ids[a]}" "${tx_ids[b]}" + log 'The transfer was recorded correctly in the transaction history' + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Check that "b" has the funds that "a" deposited + assert_eq "$(get_balance "$contract_addr" 'b')" 400000 + + # Redeem both accounts + redeem "$contract_addr" a 600000 + redeem "$contract_addr" b 400000 + # Send the funds back + quiet secretcli tx bank send b "${ADDRESS[a]}" 400000uscrt -y -b block +} + +RECEIVER_ADDRESS='' + +function create_receiver_contract() { + set -e + local init_msg + + if [[ "$RECEIVER_ADDRESS" != '' ]]; then + log 'Receiver contract already exists' + echo "$RECEIVER_ADDRESS" + return 0 + fi + + init_msg='{"count":0}' + RECEIVER_ADDRESS="$(create_contract 'tests/example-receiver' "$init_msg")" + + log "uploaded receiver contract to $RECEIVER_ADDRESS" + echo "$RECEIVER_ADDRESS" +} + +# This function exists so that we can reset the state as much as possible between different tests +function redeem_receiver() { + set -e + local receiver_addr="$1" + local snip20_addr="$2" + local to_addr="$3" + local amount="$4" + + local tx_hash + local redeem_tx + local transfer_attributes + + log 'fetching snip20 hash' + local snip20_hash + snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" + + local redeem_message='{"redeem":{"addr":"'"$snip20_addr"'","hash":"'"${snip20_hash:2}"'","to":"'"$to_addr"'","amount":"'"$amount"'","denom":"uscrt"}}' + tx_hash="$(compute_execute "$receiver_addr" "$redeem_message" ${FROM[a]} --gas 300000)" + redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from receiver at \"$receiver_addr\" to process")" + # log "$redeem_tx" + transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" + assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "$receiver_addr"$'\n'"$to_addr" + assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt"$'\n'"${amount}uscrt" + log "redeem response for \"$receiver_addr\" returned ${amount}uscrt" +} + +function register_receiver() { + set -e + local receiver_addr="$1" + local snip20_addr="$2" + + local tx_hash + + log 'fetching snip20 hash' + local snip20_hash + snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" + + log 'registering with snip20' + local register_message='{"register":{"reg_addr":"'"$snip20_addr"'","reg_hash":"'"${snip20_hash:2}"'"}}' + tx_hash="$(compute_execute "$receiver_addr" "$register_message" ${FROM[a]} --gas 300000)" + + # we throw away the output since we know it's empty + local register_tx + register_tx="$(wait_for_compute_tx "$tx_hash" 'Waiting for receiver registration')" + + assert_eq \ + "$(jq -r '.output_logs[] | select(.type == "wasm") | .attributes[] | select(.key == "register_status ") | .value' <<<"$register_tx")" \ + 'success' + log 'receiver registered successfully' +} + +function test_send() { + set -e + local contract_addr="$1" + local skip_register_receiver="$2" + + log_test_header + + local receiver_addr + receiver_addr="$(create_receiver_contract)" + local receiver_hash + receiver_hash="$(secretcli q compute contract-hash $receiver_addr | sed 's/^0x//')" + + if [ "$skip_register_receiver" != "skip-register" ]; then + register_receiver "$receiver_addr" "$contract_addr" + fi + + local tx_hash + + # Check "a" and "b" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Try to send more than "a" has + log 'sending funds from "a" to "b", but more than "a" has' + local send_message + send_message='{"send":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' + local send_response + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "b" to process')" + log "trying to overdraft from \"a\" to send to \"b\" was rejected" + + assert_eq "$(extract_exec_error "$send_response" "error: ")" "insufficient funds to transfer: balance=1000000, required=1000001" + + # Check both a and b, that their last transaction is not for 1000001 uscrt + local txs + for key in a b; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Query receiver state before Send + local receiver_state + local receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local original_count + original_count="$(jq -r '.count' <<<"$receiver_state")" + + # Send from "a" to the receiver with message to the Receiver + log 'sending funds from "a" to the Receiver, with message to the Receiver' + local receiver_msg='{"increment":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send":{"recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + local send_response + + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" + send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq \ + "$(jq -r '.output_logs[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ + "$((original_count + 1))" + log 'received send response' + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check that the receiver got the message + log 'checking whether state was updated in the receiver' + receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local new_count + new_count="$(jq -r '.count' <<<"$receiver_state")" + assert_eq "$((original_count + 1))" "$new_count" + log 'receiver contract received the message' + + # Check that "a" recorded the transfer + log 'querying the transfer history of "a"' + local tx_id1 + local tx_id2 + tx_id1="$(check_latest_transfer_history "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height")" + tx_id2="$(check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height")" + assert_eq "$tx_id1" "$tx_id2" + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Test that send callback failure also denies the transfer + log 'sending funds from "a" to the Receiver, with a "Fail" message to the Receiver' + receiver_msg='{"fail":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send":{"recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq "$(extract_exec_error "$send_response" "error: ")" 'intentional failure' # This comes from the receiver contract + + # Check that "a" does not have fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before + + log 'a failure in the callback caused the transfer to roll back, as expected' + + # redeem both accounts + redeem "$contract_addr" 'a' 600000 + redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 +} + +function set_minters() { + # set -e + local minters + + if (( $# == 0 )); then + minters='[]' + else + minters='["' + + for minter in "${@:1:$(($# - 1))}"; do + minters="${minters}${minter}"'","' + done + + minters="${minters}${*: -1}"'"]' + fi + + log "$minters" + + local set_minters_message='{"set_minters":{"minters":'"$minters"'}}' + local tx_hash + local response + tx_hash="$(compute_execute "$contract_addr" "$set_minters_message" ${FROM[a]} --gas 150000)" + response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for minter set to update")" + assert_eq "$response" "$(pad_space '{"set_minters":{"status":"success"}}' | sed 's/ //g')" + log "set the minters to these addresses: $*" +} + +# This test also tests TokenInfo +function test_burn() { + set -e + local contract_addr="$1" + + log_test_header + + local token_info_response + local burn_response + local tx_hash + + set_minters "${ADDRESS[b]}" + + # minting from the wrong account should fail + set +e; tx_hash="$(mint "$contract_addr" 'a' "${ADDRESS[a]}" 1000000)" + local _res="$?"; set -e; + if (( _res != 0 )); then + assert_eq "$(extract_exec_error "$(secretcli query compute tx "$tx_hash")" "error: ")" 'Minting is allowed to minter accounts only' + log 'minting from the wrong account failed as expected' + else + log 'minting was allowed from a non-minter address!' + return 1 + fi + + # Mint to a using b + local native_tx + local timestamp + local block_height + tx_hash="$(mint "$contract_addr" 'b' "${ADDRESS[a]}" 1000000)" + native_tx="$(secretcli q tx "$tx_hash")" + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + check_latest_tx_history_mint "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" 1000000 "$timestamp" "$block_height" + + # Check total supply + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 1000000 + + # Try to over-burn + local burn_message='{"burn":{"amount":"10000000"}}' # 110% + local burn_response + tx_hash="$(compute_execute "$contract_addr" "$burn_message" ${FROM[a]} --gas 150000)" + ! burn_response="$(wait_for_compute_tx "$tx_hash" 'waiting for burn for "a" to process')" + assert_eq "$(extract_exec_error "$burn_response" "error: ")" 'insufficient funds to burn: balance=1000000, required=10000000' + + # Check "a" balance - should not have changes + assert_eq "$(get_balance "$contract_addr" 'a')" 1000000 + + # Check total supply + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 1000000 + + # Try to burn + quiet burn "$contract_addr" 'a' 100000 # 10% + + # Check "a" balance + assert_eq "$(get_balance "$contract_addr" 'a')" 900000 + + # Check total supply + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 900000 + + # Burn the rest of the balance + tx_hash="$(burn "$contract_addr" 'a' 900000)" + log "the tx_hash is $tx_hash" + native_tx="$(secretcli q tx "$tx_hash")" + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + check_latest_tx_history_burn "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" 900000 "$timestamp" "$block_height" + + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 0 +} + +function test_transfer_from() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + # Check "a", "b", and "c" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_balance "$contract_addr" 'c')" 0 + + # Check that the allowance given to "b" by "a" is zero + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Make "a" give allowance to "b" + assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 + + # Try to transfer from "a", using "b" more than "a" has allowed + log 'transferring funds from "a" to "c" using "b", but more than "a" has allowed' + local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" + log "trying to overdraft from \"a\" to transfer to \"c\" using \"b\" was rejected" + assert_eq "$(extract_exec_error "$transfer_response" "error: ")" "insufficient allowance: allowance=1000000, required=1000001" + + # Check both "a", "b", and "c", that their last transaction is not for 1000001 uscrt + local txs + for key in a b c; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Transfer from "a" to "c" using "b" + log 'transferring funds from "a" to "c" using "b"' + local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"400000"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 250000)" + transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" + assert_eq "$transfer_response" "$(pad_space '{"transfer_from":{"status":"success"}}' | sed 's/ //g')" + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check for both "a", "b", and "c" that they recorded the transfer + local -A tx_ids + local tx_id + for key in a b c; do + log "querying the transfer history of \"$key\"" + tx_ids[$key]="$( + check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "${ADDRESS[c]}" 400000 "$timestamp" "$block_height" + )" + tx_id="$( + check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "${ADDRESS[c]}" 400000 "$timestamp" "$block_height" + )" + assert_eq "$tx_id" "${tx_ids[$key]}" + done + + assert_eq "${tx_ids[a]}" "${tx_ids[b]}" + assert_eq "${tx_ids[b]}" "${tx_ids[c]}" + log 'The transfer was recorded correctly in the transaction history' + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Check that "b" has the same funds still, but less allowance + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 + + # Check that "c" has the funds that "b" deposited from "a" + assert_eq "$(get_balance "$contract_addr" 'c')" 400000 + + # Redeem both accounts + redeem "$contract_addr" a 600000 + redeem "$contract_addr" c 400000 + # Reset allowance + assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 + # Send the funds back + quiet secretcli tx bank send c "${ADDRESS[a]}" 400000uscrt -y -b block +} + +function test_send_from() { + set -e + local contract_addr="$1" + local skip_register_receiver="$2" + + log_test_header + + local receiver_addr + receiver_addr="$(create_receiver_contract)" + local receiver_hash + receiver_hash="$(secretcli q compute contract-hash $receiver_addr | sed 's/^0x//')" + + if [ "$skip_register_receiver" != "skip-register" ]; then + register_receiver "$receiver_addr" "$contract_addr" + fi + + local tx_hash + + # Check "a" and "b" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_balance "$contract_addr" 'c')" 0 + + # Check that the allowance given to "b" by "a" is zero + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Make "a" give allowance to "b" + assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 + + # TTry to send from "a", using "b" more than "a" has allowed + log 'sending funds from "a" to "c" using "b", but more than "a" has allowed' + local send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' + local send_response + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "c" by "b" to process')" + log "trying to overdraft from \"a\" to send to \"c\" using \"b\" was rejected" + assert_eq "$(extract_exec_error "$send_response" "error: ")" "insufficient allowance: allowance=1000000, required=1000001" + + # Check both a and b, that their last transaction is not for 1000001 uscrt + local txs + for key in a b c; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Query receiver state before Send + local receiver_state + local receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local original_count + original_count="$(jq -r '.count' <<<"$receiver_state")" + + # Send from "a", using "b", to the receiver with message to the Receiver + log 'sending funds from "a", using "b", to the Receiver, with message to the Receiver' + local receiver_msg='{"increment":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + local send_message + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + local send_response + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 302000)" + send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq \ + "$(jq -r '.output_logs[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ + "$((original_count + 1))" + log 'received send response' + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check that the receiver got the message + log 'checking whether state was updated in the receiver' + receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local new_count + new_count="$(jq -r '.count' <<<"$receiver_state")" + assert_eq "$((original_count + 1))" "$new_count" + log 'receiver contract received the message' + + # Check that "a" recorded the transfer + local -A tx_ids + local tx_id + for key in a b; do + log "querying the transfer history of \"$key\"" + tx_ids[$key]="$( + check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height" + )" + tx_id="$( + check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height" + )" + assert_eq "$tx_id" "${tx_ids[$key]}" + done + + assert_eq "${tx_ids[a]}" "${tx_ids[b]}" + log 'The transfer was recorded correctly in the transaction history' + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Check that "b" has the same funds still, but less allowance + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 + + # Test that send callback failure also denies the transfer + log 'sending funds from "a", using "b", to the Receiver, with a "Fail" message to the Receiver' + receiver_msg='{"fail":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 300000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq "$(extract_exec_error "$send_response" "error: ")" 'intentional failure' # This comes from the receiver contract + + # Check that "a" does not have fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before + + log 'a failure in the callback caused the transfer to roll back, as expected' + + # redeem both accounts + redeem "$contract_addr" 'a' 600000 + redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 + # Reset allowance + assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 +} + +function measure_transfer_gas() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 10 + + local b_balance=$(get_balance "$contract_addr" 'b') + + local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"5"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 1500000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + ! transfer_response_not_compute="$(wait_for_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + + assert_ne "$(get_balance "$contract_addr" 'b')" $b_balance + + echo $transfer_response_not_compute + # echo "$(extract_gas "$transfer_response")" +} + +function measure_transfer_with_decoys_gas() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 10 + + local b_balance=$(get_balance "$contract_addr" 'b') + + local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"5", "entropy":"TXlQYXN3b3JkMTIz", "decoys":["secret1kmgdagt5efcz2kku0ak9ezfgntg29g2vr88q0e","secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg","secret1qqq8g6cjht0qfemed6nmrgjp8gajhnw3panxcf","secret1qqg9z5473z4f842c9nddc2942mj82lw8h7n5uf","secret1qqjmkf275wwaeevkkmqdk5xw2j7k5u5c8j7e9y"]}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 1500000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + ! transfer_response_not_compute="$(wait_for_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + + echo $transfer_response_not_compute + assert_ne "$(get_balance "$contract_addr" 'b')" $b_balance + + # echo "$(extract_gas "$transfer_response")" +} + +function main() { + log ' <####> Starting integration tests <####>' + log "secretcli version in the docker image is: $(secretcli version)" + + secretcli tx bank send a $(secretcli keys show -a c) 100000000000uscrt -y -b block > /dev/null + secretcli tx bank send a $(secretcli keys show -a d) 100000000000uscrt -y -b block > /dev/null + + local prng_seed + prng_seed="$(base64 <<<'enigma-rocks')" + local init_msg + init_msg='{"name":"secret-secret","admin":"'"${ADDRESS[a]}"'","symbol":"SSCRT","decimals":6,"initial_balances":[],"prng_seed":"'"$prng_seed"'","config":{"public_total_supply":true,"enable_deposit":true,"enable_redeem":true,"enable_mint":true,"enable_burn":true},"supported_denoms":["uscrt"]}' + contract_addr="$(create_contract '.' "$init_msg")" + + # To make testing faster, check the logs and try to reuse the deployed contract and VKs from previous runs. + # Remember to comment out the contract deployment and `test_viewing_key` if you do. +# local contract_addr='secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg' +# VK[a]='api_key_U6FcuhP2km6UHtYeFSyaZbggcgMJQAiTMlNWV3X4iXQ=' +# VK[b]='api_key_YoQlmqnOkkEoh81XzFkiZ3z7+ZAJh9kyFXvtaMBhiFU=' +# VK[c]='api_key_/cdkitEbzaHZA41OB6cGcz1XGnQk6LYTAfSBWTOU5aQ=' +# VK[d]='api_key_WQYkuGOco/mSHgtKWG0f7b2UcrSG3s1fIqm1X/wIGDo=' + + log "contract address: $contract_addr" + + wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[b]}" 100000000uscrt -y)" "waiting for send to b" + wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[c]}" 100000000uscrt -y)" "waiting for send to c" + wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[d]}" 100000000uscrt -y)" "waiting for send to d" + + # This first test also sets the `VK[*]` global variables that are used in the other tests + test_viewing_key "$contract_addr" + test_permit "$contract_addr" + test_deposit "$contract_addr" + test_transfer "$contract_addr" + test_send "$contract_addr" register + test_send "$contract_addr" skip-register + test_burn "$contract_addr" + test_transfer_from "$contract_addr" + test_send_from "$contract_addr" register + test_send_from "$contract_addr" skip-register + + measure_transfer_gas "$contract_addr" + measure_transfer_with_decoys_gas "$contract_addr" + + log 'Tests completed successfully' + + # If everything else worked, return successful status + return 0 +} + +main "$@" diff --git a/contracts/external/snip20-reference-impl/.github/workflows/test.yml b/contracts/external/snip20-reference-impl/.github/workflows/test.yml index ef186b9..7799efb 100644 --- a/contracts/external/snip20-reference-impl/.github/workflows/test.yml +++ b/contracts/external/snip20-reference-impl/.github/workflows/test.yml @@ -31,7 +31,7 @@ jobs: options: --name secretdev image: ghcr.io/scrtlabs/localsecret:v1.6.0-alpha.4 volumes: - - "/home/runner/work/snip20-reference-impl/snip20-reference-impl/:/root/code" + - "/home/runner/work/snip20-base/snip20-base/:/root/code" ports: # Opens tcp port - 5000:5000 diff --git a/contracts/external/snip20-reference-impl/README.md b/contracts/external/snip20-reference-impl/README.md index 294b59d..1e530ec 100644 --- a/contracts/external/snip20-reference-impl/README.md +++ b/contracts/external/snip20-reference-impl/README.md @@ -3,7 +3,7 @@ This is an implementation of a [SNIP-20](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md), [SNIP-21](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-21.md), [SNIP-22](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-22.md), [SNIP-23](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-23.md), [SNIP-24](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md), [SNIP-25](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-25.md) and [SNIP-26](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-26.md) compliant token contract. > **Note:** -> The master branch contains new features not covered by officially-released SNIPs and may be subject to change. When releasing a token on mainnet, we recommend you start with a [tagged release](https://github.com/scrtlabs/snip20-reference-impl/tags) to ensure compatibility with SNIP standards. +> The master branch contains new features not covered by officially-released SNIPs and may be subject to change. When releasing a token on mainnet, we recommend you start with a [tagged release](https://github.com/scrtlabs/snip20-base/tags) to ensure compatibility with SNIP standards. At the time of token creation you may configure: * Public Total Supply: If you enable this, the token's total supply will be displayed whenever a TokenInfo query is performed. DEFAULT: false diff --git a/contracts/external/snip20-reference-impl/src/msg.rs b/contracts/external/snip20-reference-impl/src/msg.rs index 072b50d..812452c 100644 --- a/contracts/external/snip20-reference-impl/src/msg.rs +++ b/contracts/external/snip20-reference-impl/src/msg.rs @@ -334,7 +334,7 @@ fn get_min_decoys_count(actions: &[T]) -> usize { } } -#[derive(Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteAnswer { // Native @@ -631,8 +631,7 @@ pub struct AllowanceReceivedResult { pub expiration: Option, } -#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] -#[cfg_attr(test, derive(Eq, PartialEq))] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ResponseStatus { Success, diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/.cargo/config b/contracts/pre-propose/dao-pre-propose-approval-single/.cargo/config.toml similarity index 100% rename from contracts/pre-propose/dao-pre-propose-approval-single/.cargo/config rename to contracts/pre-propose/dao-pre-propose-approval-single/.cargo/config.toml diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml b/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml index 08d1b2b..6c304b0 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml @@ -42,3 +42,7 @@ dao-voting = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-snip20-staked = { workspace = true } dao-proposal-single = { workspace = true } +snip20-base = { workspace = true } +cw4 ={ workspace = true} +snip721-reference-impl = { workspace = true } +query_auth = { workspace = true } \ No newline at end of file diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs b/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs index 95b03cf..6f1a2e5 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs +++ b/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs @@ -174,14 +174,12 @@ pub fn execute_approve( &proposal_id, &(proposal.deposit.clone(), proposal.proposer.clone()), )?; - let propose_messsage = WasmMsg::Execute { contract_addr: proposal_module.addr.into_string(), code_hash: proposal_module.code_hash, msg: to_binary(&ProposeMessageInternal::Propose(proposal.msg.clone()))?, funds: vec![], }; - COMPLETED_PROPOSALS.insert( deps.storage, &id, @@ -215,6 +213,7 @@ pub fn execute_reject( ) -> Result { // Check sender is the approver let approver = APPROVER.load(deps.storage)?; + println!("In execute Reject"); if approver != info.sender { return Err(PreProposeError::Unauthorized {}); } @@ -271,6 +270,7 @@ pub fn execute_update_approver( ) -> Result { // Check sender is the approver let approver = APPROVER.load(deps.storage)?; + println!("In execute update approver"); if approver != info.sender { return Err(PreProposeError::Unauthorized {}); } @@ -293,6 +293,11 @@ pub fn execute_add_approver_hook( let dao = pre_propose_base.dao.load(deps.storage)?; let approver = APPROVER.load(deps.storage)?; + println!("In execute add approver hook"); + println!("Info.sender ::::::: {:?}", info.sender); + println!("Approver ::::::: {:?}", approver); + println!("DAO ::::::: {:?}", dao); + // Check sender is the approver or the parent DAO if approver != info.sender && dao.addr != info.sender { return Err(PreProposeError::Unauthorized {}); @@ -317,6 +322,8 @@ pub fn execute_remove_approver_hook( let dao = pre_propose_base.dao.load(deps.storage)?; let approver = APPROVER.load(deps.storage)?; + println!("In execute remove approver hook"); + // Check sender is the approver or the parent DAO if approver != info.sender && dao.addr != info.sender { return Err(PreProposeError::Unauthorized {}); @@ -362,49 +369,70 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } QueryExt::PendingProposals { start_after, limit } => { let mut res: Vec = Vec::new(); - let mut start = start_after; + let max_limit = 50; // Defined a sensible upper limit + let limit = limit.unwrap_or(max_limit).min(max_limit); + + // Fetch all pending proposals let binding = &PENDING_PROPOSALS; let iter = binding.iter(deps.storage)?; + + let mut found_start = start_after.is_none(); + for item in iter { let (id, proposal) = item?; - if let Some(start_after) = &start { - if &id == start_after { - // If we found the start point, reset it to start iterating - start = None; + + // Skip until we find the `start_after` item + if let Some(ref start_id) = start_after { + if &id == start_id { + found_start = true; + continue; } } - if start.is_none() { + + if found_start { res.push(proposal); - if res.len() >= limit.unwrap_or_default() as usize { - break; // Break out of loop if limit reached + if res.len() >= limit.try_into().unwrap() { + break; // Stop once the limit is reached } } } + to_binary(&res) } + QueryExt::ReversePendingProposals { start_before, limit, } => { let mut res: Vec = Vec::new(); - let mut start = start_before; + let max_limit = 50; // Defined a sensible upper limit + let limit = limit.unwrap_or(max_limit).min(max_limit); + + // Fetch all pending proposals and iterate in reverse let binding = &PENDING_PROPOSALS; - let iter = binding.iter(deps.storage)?.rev(); // Iterate in reverse + let iter = binding.iter(deps.storage)?.rev(); + + let mut skip_until_found = start_before.is_some(); + for item in iter { let (id, proposal) = item?; - if let Some(start_before) = &start { - if &id == start_before { - // If we found the start point, reset it to start iterating - start = None; + + // Skip until we find the `start_before` item + if let Some(ref start_id) = start_before { + if &id == start_id { + skip_until_found = false; + continue; // Skip the `start_before` item itself } } - if start.is_none() { + + if !skip_until_found { res.push(proposal); - if res.len() >= limit.unwrap_or_default() as usize { - break; // Break out of loop if limit reached + if res.len() >= limit.try_into().unwrap() { + break; // Stop once the limit is reached } } } + to_binary(&res) } @@ -413,51 +441,73 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } QueryExt::CompletedProposals { start_after, limit } => { let mut res: Vec = Vec::new(); - let mut start = start_after; + let max_limit = 50; // Defined a sensible upper limit + let limit = limit.unwrap_or(max_limit).min(max_limit); + + // Fetch all completed proposals let binding = &COMPLETED_PROPOSALS; let iter = binding.iter(deps.storage)?; + + let mut found_start = start_after.is_none(); + for item in iter { let (id, proposal) = item?; - if let Some(start_after) = &start { - if &id == start_after { - // If we found the start point, reset it to start iterating - start = None; + + // Skip until we find the `start_after` item + if let Some(ref start_id) = start_after { + if &id == start_id { + found_start = true; + continue; // Skip the `start_after` item itself } } - if start.is_none() { + + if found_start { res.push(proposal); - if res.len() >= limit.unwrap_or_default() as usize { - break; // Break out of loop if limit reached + if res.len() >= limit.try_into().unwrap() { + break; // Stop once the limit is reached } } } + to_binary(&res) } + QueryExt::ReverseCompletedProposals { start_before, limit, } => { let mut res: Vec = Vec::new(); - let mut start = start_before; + let max_limit = 50; // Defined a sensible upper limit + let limit = limit.unwrap_or(max_limit).min(max_limit); + + // Fetch all completed proposals and iterate in reverse let binding = &COMPLETED_PROPOSALS; - let iter = binding.iter(deps.storage)?.rev(); // Iterate in reverse + let iter = binding.iter(deps.storage)?.rev(); + + let mut skip_until_found = start_before.is_some(); + for item in iter { let (id, proposal) = item?; - if let Some(start_before) = &start { - if &id == start_before { - // If we found the start point, reset it to start iterating - start = None; + + // Skip until we find the `start_before` item + if let Some(ref start_id) = start_before { + if &id == start_id { + skip_until_found = false; + continue; // Skip the `start_before` item itself } } - if start.is_none() { + + if !skip_until_found { res.push(proposal); - if res.len() >= limit.unwrap_or_default() as usize { - break; // Break out of loop if limit reached + if res.len() >= limit.try_into().unwrap() { + break; // Stop once the limit is reached } } } + to_binary(&res) } + QueryExt::CompletedProposalIdForCreatedProposalId { id } => { to_binary(&CREATED_PROPOSAL_TO_COMPLETED_PROPOSAL.get(deps.storage, &id)) } diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/src/lib.rs b/contracts/pre-propose/dao-pre-propose-approval-single/src/lib.rs index 9b62a89..47ba417 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/src/lib.rs +++ b/contracts/pre-propose/dao-pre-propose-approval-single/src/lib.rs @@ -4,8 +4,8 @@ pub mod contract; pub mod msg; pub mod state; -// #[cfg(test)] -// mod tests; +#[cfg(test)] +mod tests; // Exporting these means that contracts interacting with this one don't // need an explicit dependency on the base contract to read queries. diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/src/state.rs b/contracts/pre-propose/dao-pre-propose-approval-single/src/state.rs index 36bda99..553a73b 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/src/state.rs +++ b/contracts/pre-propose/dao-pre-propose-approval-single/src/state.rs @@ -37,9 +37,9 @@ pub struct Proposal { } pub const APPROVER: Item = Item::new("approver"); -pub static PENDING_PROPOSALS: Keymap = Keymap::new(b"pending_proposals"); -pub static COMPLETED_PROPOSALS: Keymap = Keymap::new(b"completed_proposals"); -pub static CREATED_PROPOSAL_TO_COMPLETED_PROPOSAL: Keymap = +pub const PENDING_PROPOSALS: Keymap = Keymap::new(b"pending_proposals"); +pub const COMPLETED_PROPOSALS: Keymap = Keymap::new(b"completed_proposals"); +pub const CREATED_PROPOSAL_TO_COMPLETED_PROPOSAL: Keymap = Keymap::new(b"created_to_completed_proposal"); /// Used internally to track the current approval_id. diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs b/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs index 07afdf1..3f0d0f8 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs @@ -1,14 +1,11 @@ -use cosmwasm_std::{coins, from_json, to_json_binary, Addr, Coin, Empty, Uint128}; -use cw2::ContractVersion; -use cw20::Cw20Coin; +use cosmwasm_std::{ + coins, from_binary, to_binary, Addr, Binary, Coin, ContractInfo, Empty, Uint128, +}; use cw_denom::UncheckedDenom; -use cw_multi_test::{App, BankSudo, Contract, ContractWrapper, Executor}; -use cw_utils::Duration; -use dao_interface::state::ProposalModule; use dao_interface::state::{Admin, ModuleInstantiateInfo}; +use dao_interface::state::{AnyContractInfo, ProposalModule}; use dao_pre_propose_base::{error::PreProposeError, msg::DepositInfoResponse, state::Config}; use dao_proposal_single::query::ProposalResponse; -use dao_testing::helpers::instantiate_with_cw4_groups_governance; use dao_voting::{ deposit::{CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, pre_propose::{PreProposeInfo, ProposalCreationPolicy}, @@ -16,10 +13,20 @@ use dao_voting::{ threshold::{PercentageThreshold, Threshold}, voting::Vote, }; +use dao_voting_cw4::msg::GroupContract; +use secret_multi_test::{ + App, BankSudo, Contract, ContractInstantiationInfo, ContractWrapper, Executor, +}; +use secret_utils::Duration; +use shade_protocol::basic_staking::Auth; +use shade_protocol::utils::asset::RawContract; +use snip20_base::msg::{InitConfig, InitialBalance}; use crate::state::{Proposal, ProposalStatus}; use crate::{contract::*, msg::*}; +const CREATOR_ADDR: &str = "creator"; + fn cw_dao_proposal_single_contract() -> Box> { let contract = ContractWrapper::new( dao_proposal_single::contract::execute, @@ -31,39 +38,166 @@ fn cw_dao_proposal_single_contract() -> Box> { Box::new(contract) } +pub fn dao_dao_contract() -> Box> { + let contract = ContractWrapper::new( + dao_dao_core::contract::execute, + dao_dao_core::contract::instantiate, + dao_dao_core::contract::query, + ) + .with_reply(dao_dao_core::contract::reply) + .with_migrate(dao_dao_core::contract::migrate); + Box::new(contract) +} + fn cw_pre_propose_base_proposal_single() -> Box> { let contract = ContractWrapper::new(execute, instantiate, query); Box::new(contract) } -fn cw20_base_contract() -> Box> { +fn snip20_base_contract() -> Box> { + let contract = ContractWrapper::new( + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, + ); + Box::new(contract) +} + +pub fn cw4_group_contract() -> Box> { + let contract = ContractWrapper::new( + cw4_group::contract::execute, + cw4_group::contract::instantiate, + cw4_group::contract::query, + ); + Box::new(contract) +} + +pub fn dao_voting_cw4_contract() -> Box> { + let contract = ContractWrapper::new( + dao_voting_cw4::contract::execute, + dao_voting_cw4::contract::instantiate, + dao_voting_cw4::contract::query, + ) + .with_reply(dao_voting_cw4::contract::reply); + Box::new(contract) +} + +pub fn query_auth_contract() -> Box> { let contract = ContractWrapper::new( - cw20_base::contract::execute, - cw20_base::contract::instantiate, - cw20_base::contract::query, + query_auth::contract::execute, + query_auth::contract::instantiate, + query_auth::contract::query, ); Box::new(contract) } +pub fn instantiate_with_cw4_groups_governance( + app: &mut App, + core_info: ContractInstantiationInfo, + core_code_id: u64, + core_code_hash: String, + proposal_module_instantiate: Binary, + initial_weights: Option>, +) -> ContractInfo { + let cw4_info = app.store_code(cw4_group_contract()); + let votemod_info = app.store_code(dao_voting_cw4_contract()); + let query_auth = app.store_code(query_auth_contract()); + let initial_weights = initial_weights.unwrap_or_default(); + + // Remove duplicates so that we can test duplicate voting. + let initial_weights: Vec = { + let mut already_seen = vec![]; + initial_weights + .into_iter() + .filter(|InitialBalance { address, .. }| { + if already_seen.contains(address) { + false + } else { + already_seen.push(address.clone()); + true + } + }) + .map(|InitialBalance { address, amount }| cw4::Member { + addr: address, + weight: amount.u128() as u64, + }) + .collect() + }; + + let governance_instantiate = dao_interface::msg::InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs".to_string(), + image_url: None, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: votemod_info.code_id, + code_hash: votemod_info.code_hash, + msg: to_binary(&dao_voting_cw4::msg::InstantiateMsg { + group_contract: GroupContract::New { + cw4_group_code_id: cw4_info.code_id, + cw4_group_code_hash: cw4_info.code_hash, + initial_members: initial_weights, + query_auth: None, + }, + }) + .unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "DAO DAO voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: core_code_id, + code_hash: core_code_hash, + msg: proposal_module_instantiate, + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "DAO DAO governance module".to_string(), + }], + initial_items: None, + query_auth_code_id: query_auth.code_id, + query_auth_code_hash: query_auth.code_hash, + prng_seed: "seed".into(), + }; + + let info = app + .instantiate_contract( + core_info.clone(), + Addr::unchecked(CREATOR_ADDR), + &governance_instantiate, + &[], + "DAO DAO", + None, + ) + .unwrap(); + + // Update the block so that weights appear. + app.update_block(|block| block.height += 1); + + info +} + fn get_default_proposal_module_instantiate( app: &mut App, deposit_info: Option, open_proposal_submission: bool, + query_auth: Option, ) -> dao_proposal_single::msg::InstantiateMsg { - let pre_propose_id = app.store_code(cw_pre_propose_base_proposal_single()); + let pre_propose_instantiate_info = app.store_code(cw_pre_propose_base_proposal_single()); dao_proposal_single::msg::InstantiateMsg { threshold: Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, }, - max_voting_period: cw_utils::Duration::Time(86400), + max_voting_period: secret_utils::Duration::Time(86400), min_voting_period: None, only_members_execute: false, allow_revoting: false, pre_propose_info: PreProposeInfo::ModuleMayPropose { info: ModuleInstantiateInfo { - code_id: pre_propose_id, - msg: to_json_binary(&InstantiateMsg { + code_id: pre_propose_instantiate_info.code_id, + code_hash: pre_propose_instantiate_info.code_hash, + msg: to_binary(&InstantiateMsg { deposit_info, open_proposal_submission, extension: InstantiateExt { @@ -78,37 +212,47 @@ fn get_default_proposal_module_instantiate( }, close_proposal_on_execution_failure: false, veto: None, + query_auth, } } -fn instantiate_cw20_base_default(app: &mut App) -> Addr { - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_instantiate = cw20_base::msg::InstantiateMsg { - name: "cw20 token".to_string(), - symbol: "cwtwenty".to_string(), +fn instantiate_snip20_base_default(app: &mut App) -> ContractInfo { + let snip20_info = app.store_code(snip20_base_contract()); + let snip20_instantiate = snip20_base::msg::InstantiateMsg { + name: "snip20 token".to_string(), + symbol: "sniptwenty".to_string(), decimals: 6, - initial_balances: vec![Cw20Coin { + initial_balances: Some(vec![InitialBalance { address: "ekez".to_string(), amount: Uint128::new(10), - }], - mint: None, - marketing: None, + }]), + admin: None, + prng_seed: to_binary("data").unwrap(), + config: Some(InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(true), + enable_burn: Some(true), + can_modify_denoms: Some(true), + }), + supported_denoms: None, }; app.instantiate_contract( - cw20_id, + snip20_info, Addr::unchecked("ekez"), - &cw20_instantiate, + &snip20_instantiate, &[], - "cw20-base", + "snip20-base", None, ) .unwrap() } struct DefaultTestSetup { - core_addr: Addr, - proposal_single: Addr, - pre_propose: Addr, + core_contract_info: ContractInfo, + proposal_single_contract_info: ContractInfo, + pre_propose_contract_info: ContractInfo, } fn setup_default_test( @@ -116,21 +260,28 @@ fn setup_default_test( deposit_info: Option, open_proposal_submission: bool, ) -> DefaultTestSetup { - let dao_proposal_single_id = app.store_code(cw_dao_proposal_single_contract()); + let dao_proposal_single_instantiate_info = app.store_code(cw_dao_proposal_single_contract()); + let core_contract_instantiate_info = app.store_code(dao_dao_contract()); - let proposal_module_instantiate = - get_default_proposal_module_instantiate(app, deposit_info, open_proposal_submission); + let proposal_module_instantiate = get_default_proposal_module_instantiate( + app, + deposit_info, + open_proposal_submission, + None, + ); - let core_addr = instantiate_with_cw4_groups_governance( + let core_contract_info = instantiate_with_cw4_groups_governance( app, - dao_proposal_single_id, - to_json_binary(&proposal_module_instantiate).unwrap(), + core_contract_instantiate_info, + dao_proposal_single_instantiate_info.code_id, + dao_proposal_single_instantiate_info.code_hash, + to_binary(&proposal_module_instantiate).unwrap(), Some(vec![ - cw20::Cw20Coin { + InitialBalance { address: "ekez".to_string(), amount: Uint128::new(9), }, - cw20::Cw20Coin { + InitialBalance { address: "keze".to_string(), amount: Uint128::new(8), }, @@ -139,7 +290,8 @@ fn setup_default_test( let proposal_modules: Vec = app .wrap() .query_wasm_smart( - core_addr.clone(), + core_contract_info.code_hash.clone(), + core_contract_info.address.clone(), &dao_interface::msg::QueryMsg::ProposalModules { start_after: None, limit: None, @@ -148,44 +300,76 @@ fn setup_default_test( .unwrap(); assert_eq!(proposal_modules.len(), 1); - let proposal_single = proposal_modules.into_iter().next().unwrap().address; + let proposal_single_address = proposal_modules.clone().into_iter().next().unwrap().address; + let proposal_single_code_hash = proposal_modules.into_iter().next().unwrap().code_hash; let proposal_creation_policy = app .wrap() .query_wasm_smart( - proposal_single.clone(), + proposal_single_code_hash.clone(), + proposal_single_address.clone(), &dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {}, ) .unwrap(); - let pre_propose = match proposal_creation_policy { - ProposalCreationPolicy::Module { addr } => addr, + let (pre_propose_addr, pre_proposse_code_hash) = match proposal_creation_policy { + ProposalCreationPolicy::Module { addr, code_hash } => (addr, code_hash), _ => panic!("expected a module for the proposal creation policy"), }; // Make sure things were set up correctly. assert_eq!( - proposal_single, - get_proposal_module(app, pre_propose.clone()) + AnyContractInfo { + addr: proposal_single_address.clone(), + code_hash: proposal_single_code_hash.clone(), + }, + get_proposal_module( + app, + pre_propose_addr.clone(), + pre_proposse_code_hash.clone() + ) + ); + assert_eq!( + AnyContractInfo { + addr: core_contract_info.address.clone(), + code_hash: core_contract_info.code_hash.clone(), + }, + get_dao( + app, + pre_propose_addr.clone(), + pre_proposse_code_hash.clone() + ) ); - assert_eq!(core_addr, get_dao(app, pre_propose.clone())); DefaultTestSetup { - core_addr, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info: ContractInfo { + address: proposal_single_address, + code_hash: proposal_single_code_hash, + }, + pre_propose_contract_info: ContractInfo { + address: pre_propose_addr, + code_hash: pre_proposse_code_hash, + }, } } -fn make_pre_proposal(app: &mut App, pre_propose: Addr, proposer: &str, funds: &[Coin]) -> u64 { +fn make_pre_proposal( + app: &mut App, + pre_propose_contract_info: ContractInfo, + proposer: &str, + funds: &[Coin], + auth: Auth, +) -> u64 { app.execute_contract( Addr::unchecked(proposer), - pre_propose.clone(), + &pre_propose_contract_info.clone(), &ExecuteMsg::Propose { msg: ProposeMessage::Propose { title: "title".to_string(), description: "description".to_string(), msgs: vec![], }, + auth, }, funds, ) @@ -195,7 +379,8 @@ fn make_pre_proposal(app: &mut App, pre_propose: Addr, proposer: &str, funds: &[ let mut pending: Vec = app .wrap() .query_wasm_smart( - pre_propose, + pre_propose_contract_info.code_hash, + pre_propose_contract_info.address, &QueryMsg::QueryExtension { msg: QueryExt::PendingProposals { start_after: None, @@ -211,37 +396,54 @@ fn make_pre_proposal(app: &mut App, pre_propose: Addr, proposer: &str, funds: &[ fn mint_natives(app: &mut App, receiver: &str, coins: Vec) { // Mint some ekez tokens for ekez so we can pay the deposit. - app.sudo(cw_multi_test::SudoMsg::Bank(BankSudo::Mint { + app.sudo(secret_multi_test::SudoMsg::Bank(BankSudo::Mint { to_address: receiver.to_string(), amount: coins, })) .unwrap(); } -fn increase_allowance(app: &mut App, sender: &str, receiver: &Addr, cw20: Addr, amount: Uint128) { +fn increase_allowance( + app: &mut App, + sender: &str, + receiver: &Addr, + snip20_contract_info: ContractInfo, + amount: Uint128, +) { app.execute_contract( Addr::unchecked(sender), - cw20, - &cw20::Cw20ExecuteMsg::IncreaseAllowance { + &snip20_contract_info, + &snip20_base::msg::ExecuteMsg::IncreaseAllowance { spender: receiver.to_string(), amount, - expires: None, + expiration: None, + padding: None, }, &[], ) .unwrap(); } -fn get_balance_cw20, U: Into>( +fn get_balance_snip20, C: Into, U: Into, K: Into>( app: &App, contract_addr: T, + code_hash: C, address: U, + key: K, ) -> Uint128 { - let msg = cw20::Cw20QueryMsg::Balance { + let msg = snip20_base::msg::QueryMsg::Balance { address: address.into(), + key: key.into(), }; - let result: cw20::BalanceResponse = app.wrap().query_wasm_smart(contract_addr, &msg).unwrap(); - result.balance + let result: snip20_base::msg::QueryAnswer = app + .wrap() + .query_wasm_smart(code_hash, contract_addr, &msg) + .unwrap(); + let mut balance = Uint128::zero(); + if let snip20_base::msg::QueryAnswer::Balance { amount } = result { + balance = amount; + } + balance } fn get_balance_native(app: &App, who: &str, denom: &str) -> Uint128 { @@ -249,14 +451,22 @@ fn get_balance_native(app: &App, who: &str, denom: &str) -> Uint128 { res.amount } -fn vote(app: &mut App, module: Addr, sender: &str, id: u64, position: Vote) -> Status { +fn vote( + app: &mut App, + module_contract_info: ContractInfo, + sender: &str, + id: u64, + position: Vote, + auth: Auth, +) -> Status { app.execute_contract( Addr::unchecked(sender), - module.clone(), + &module_contract_info.clone(), &dao_proposal_single::msg::ExecuteMsg::Vote { proposal_id: id, vote: position, rationale: None, + auth, }, &[], ) @@ -265,7 +475,8 @@ fn vote(app: &mut App, module: Addr, sender: &str, id: u64, position: Vote) -> S let proposal: ProposalResponse = app .wrap() .query_wasm_smart( - module, + module_contract_info.code_hash, + module_contract_info.address, &dao_proposal_single::msg::QueryMsg::Proposal { proposal_id: id }, ) .unwrap(); @@ -273,40 +484,59 @@ fn vote(app: &mut App, module: Addr, sender: &str, id: u64, position: Vote) -> S proposal.proposal.status } -fn get_config(app: &App, module: Addr) -> Config { +fn get_config(app: &App, module_addr: Addr, module_code_hash: String) -> Config { + app.wrap() + .query_wasm_smart(module_code_hash, module_addr, &QueryMsg::Config {}) + .unwrap() +} + +fn get_dao(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { app.wrap() - .query_wasm_smart(module, &QueryMsg::Config {}) + .query_wasm_smart(module_code_hash, module_addr, &QueryMsg::Dao {}) .unwrap() } -fn get_dao(app: &App, module: Addr) -> Addr { +fn get_proposal_module(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { app.wrap() - .query_wasm_smart(module, &QueryMsg::Dao {}) + .query_wasm_smart(module_code_hash, module_addr, &QueryMsg::ProposalModule {}) .unwrap() } -fn get_proposal_module(app: &App, module: Addr) -> Addr { +fn query_query_auth(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { app.wrap() - .query_wasm_smart(module, &QueryMsg::ProposalModule {}) + .query_wasm_smart( + module_code_hash, + module_addr, + &dao_interface::msg::QueryMsg::QueryAuthInfo {}, + ) .unwrap() } -fn get_deposit_info(app: &App, module: Addr, id: u64) -> DepositInfoResponse { +fn get_deposit_info( + app: &App, + module_addr: Addr, + module_code_hash: String, + id: u64, +) -> DepositInfoResponse { app.wrap() - .query_wasm_smart(module, &QueryMsg::DepositInfo { proposal_id: id }) + .query_wasm_smart( + module_code_hash, + module_addr, + &QueryMsg::DepositInfo { proposal_id: id }, + ) .unwrap() } fn update_config( app: &mut App, - module: Addr, + module_contract_info: ContractInfo, sender: &str, deposit_info: Option, open_proposal_submission: bool, ) -> Config { app.execute_contract( Addr::unchecked(sender), - module.clone(), + &module_contract_info.clone(), &ExecuteMsg::UpdateConfig { deposit_info, open_proposal_submission, @@ -315,19 +545,23 @@ fn update_config( ) .unwrap(); - get_config(app, module) + get_config( + app, + module_contract_info.address, + module_contract_info.code_hash, + ) } fn update_config_should_fail( app: &mut App, - module: Addr, + module_contract_info: ContractInfo, sender: &str, deposit_info: Option, open_proposal_submission: bool, ) -> PreProposeError { app.execute_contract( Addr::unchecked(sender), - module, + &module_contract_info, &ExecuteMsg::UpdateConfig { deposit_info, open_proposal_submission, @@ -339,58 +573,128 @@ fn update_config_should_fail( .unwrap() } -fn withdraw(app: &mut App, module: Addr, sender: &str, denom: Option) { +fn create_viewing_key(app: &mut App, contract_info: ContractInfo, sender: &str) -> String { + let msg = shade_protocol::contract_interfaces::query_auth::ExecuteMsg::CreateViewingKey { + entropy: "entropy".to_string(), + padding: None, + }; + let res = app + .execute_contract(Addr::unchecked(sender), &contract_info, &msg, &[]) + .unwrap(); + let mut viewing_key = String::new(); + let data: shade_protocol::contract_interfaces::query_auth::ExecuteAnswer = + from_binary(&res.data.unwrap()).unwrap(); + if let shade_protocol::contract_interfaces::query_auth::ExecuteAnswer::CreateViewingKey { + key, + } = data + { + viewing_key = key; + }; + viewing_key +} + +fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: &str) -> String { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { + entropy: "entropy".to_string(), + padding: None, + }; + let res = app + .execute_contract(Addr::unchecked(addr), &contract_info, &msg, &[]) + .unwrap(); + let mut viewing_key = String::new(); + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { + viewing_key = key; + }; + viewing_key +} + +fn _withdraw( + app: &mut App, + module_contract_info: ContractInfo, + sender: &str, + denom: Option, + key: String, +) { app.execute_contract( Addr::unchecked(sender), - module, - &ExecuteMsg::Withdraw { denom }, + &module_contract_info, + &ExecuteMsg::Withdraw { + denom, + key: Some(key), + }, &[], ) .unwrap(); } -fn withdraw_should_fail( +fn _withdraw_should_fail( app: &mut App, - module: Addr, + module_contract_info: ContractInfo, sender: &str, denom: Option, ) -> PreProposeError { app.execute_contract( Addr::unchecked(sender), - module, - &ExecuteMsg::Withdraw { denom }, + &module_contract_info, + &ExecuteMsg::Withdraw { denom, key: None }, &[], ) .unwrap_err() .downcast() .unwrap() } - -fn close_proposal(app: &mut App, module: Addr, sender: &str, proposal_id: u64) { +fn close_proposal( + app: &mut App, + module_contract_info: ContractInfo, + sender: &str, + proposal_id: u64, +) { app.execute_contract( Addr::unchecked(sender), - module, + &module_contract_info, &dao_proposal_single::msg::ExecuteMsg::Close { proposal_id }, &[], ) .unwrap(); } -fn execute_proposal(app: &mut App, module: Addr, sender: &str, proposal_id: u64) { +fn close_proposal_wrapper( + app: &mut App, + contract_info: ContractInfo, + sender: &str, + id: u64, + _auth: Auth, +) { + close_proposal(app, contract_info, sender, id); +} + +fn execute_proposal( + app: &mut App, + module_contract_info: ContractInfo, + sender: &str, + proposal_id: u64, + auth: Auth, +) { app.execute_contract( Addr::unchecked(sender), - module, - &dao_proposal_single::msg::ExecuteMsg::Execute { proposal_id }, + &module_contract_info, + &dao_proposal_single::msg::ExecuteMsg::Execute { auth, proposal_id }, &[], ) .unwrap(); } -fn approve_proposal(app: &mut App, module: Addr, sender: &str, proposal_id: u64) -> u64 { +fn approve_proposal( + app: &mut App, + module_contract_info: ContractInfo, + sender: &str, + proposal_id: u64, +) -> u64 { let res = app .execute_contract( Addr::unchecked(sender), - module, + &module_contract_info, &ExecuteMsg::Extension { msg: ExecuteExt::Approve { id: proposal_id }, }, @@ -404,10 +708,15 @@ fn approve_proposal(app: &mut App, module: Addr, sender: &str, proposal_id: u64) attrs[attrs.len() - 2].value.parse().unwrap() } -fn reject_proposal(app: &mut App, module: Addr, sender: &str, proposal_id: u64) { +fn reject_proposal( + app: &mut App, + module_contract_info: ContractInfo, + sender: &str, + proposal_id: u64, +) { app.execute_contract( Addr::unchecked(sender), - module, + &module_contract_info, &ExecuteMsg::Extension { msg: ExecuteExt::Reject { id: proposal_id }, }, @@ -440,9 +749,9 @@ fn test_native_permutation( let mut app = App::default(); let DefaultTestSetup { - core_addr, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { @@ -455,9 +764,31 @@ fn test_native_permutation( false, ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + mint_natives(&mut app, "ekez", coins(10, "ujuno")); - let pre_propose_id = - make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + let pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); // Make sure it went away. let balance = get_balance_native(&app, "ekez", "ujuno"); @@ -467,28 +798,59 @@ fn test_native_permutation( match approval_status { ApprovalStatus::Approved => { // Approver approves, new proposal id is returned - let id = approve_proposal(&mut app, pre_propose, "approver", pre_propose_id); + let id = approve_proposal( + &mut app, + pre_propose_contract_info.clone(), + "approver", + pre_propose_id, + ); + + println!("Here :::::::::::::"); // Voting happens on newly created proposal #[allow(clippy::type_complexity)] let (position, expected_status, trigger_refund): ( _, _, - fn(&mut App, Addr, &str, u64) -> (), + fn(&mut App, ContractInfo, &str, u64, Auth) -> (), ) = match end_status { EndStatus::Passed => (Vote::Yes, Status::Passed, execute_proposal), - EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal), + EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal_wrapper), }; - let new_status = vote(&mut app, proposal_single.clone(), "ekez", id, position); + let new_status = vote( + &mut app, + proposal_single_contract_info.clone(), + "ekez", + id, + position, + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); assert_eq!(new_status, expected_status); // Close or execute the proposal to trigger a refund. - trigger_refund(&mut app, proposal_single, "ekez", id); + trigger_refund( + &mut app, + proposal_single_contract_info, + "ekez", + id, + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); } ApprovalStatus::Rejected => { // Proposal is rejected by approver // No proposal is created so there is no voting - reject_proposal(&mut app, pre_propose, "approver", pre_propose_id); + reject_proposal( + &mut app, + pre_propose_contract_info, + "approver", + pre_propose_id, + ); } }; @@ -498,12 +860,12 @@ fn test_native_permutation( }; let proposer_balance = get_balance_native(&app, "ekez", "ujuno"); - let dao_balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); + let dao_balance = get_balance_native(&app, core_contract_info.address.as_str(), "ujuno"); assert_eq!(proposer_expected, proposer_balance.u128()); assert_eq!(dao_expected, dao_balance.u128()) } -fn test_cw20_permutation( +fn test_snip20_permutation( end_status: EndStatus, refund_policy: DepositRefundPolicy, receiver: RefundReceiver, @@ -511,17 +873,20 @@ fn test_cw20_permutation( ) { let mut app = App::default(); - let cw20_address = instantiate_cw20_base_default(&mut app); + let snip20_contract_info = instantiate_snip20_base_default(&mut app); let DefaultTestSetup { - core_addr, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { denom: DepositToken::Token { - denom: UncheckedDenom::Cw20(cw20_address.to_string()), + denom: UncheckedDenom::Snip20( + snip20_contract_info.address.to_string(), + snip20_contract_info.code_hash.clone(), + ), }, amount: Uint128::new(10), refund_policy, @@ -529,45 +894,121 @@ fn test_cw20_permutation( false, ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + + let viewing_key_snip20 = create_viewing_key_snip20( + &mut app, + ContractInfo { + address: snip20_contract_info.address.clone(), + code_hash: snip20_contract_info.code_hash.clone(), + }, + "ekez", + ); + + let viewing_key_snip20_core = create_viewing_key_snip20( + &mut app, + ContractInfo { + address: snip20_contract_info.address.clone(), + code_hash: snip20_contract_info.code_hash.clone(), + }, + core_contract_info.address.as_str(), + ); + increase_allowance( &mut app, "ekez", - &pre_propose, - cw20_address.clone(), + &pre_propose_contract_info.address.clone(), + snip20_contract_info.clone(), Uint128::new(10), ); - let pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); + let pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &[], + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); // Make sure it went await. - let balance = get_balance_cw20(&app, cw20_address.clone(), "ekez"); + let balance = get_balance_snip20( + &app, + snip20_contract_info.address.clone(), + snip20_contract_info.code_hash.clone(), + "ekez", + viewing_key_snip20.clone(), + ); assert_eq!(balance, Uint128::zero()); // Approver approves or rejects proposal match approval_status { ApprovalStatus::Approved => { // Approver approves, new proposal id is returned - let id = approve_proposal(&mut app, pre_propose.clone(), "approver", pre_propose_id); + let id = approve_proposal( + &mut app, + pre_propose_contract_info.clone(), + "approver", + pre_propose_id, + ); // Voting happens on newly created proposal #[allow(clippy::type_complexity)] let (position, expected_status, trigger_refund): ( _, _, - fn(&mut App, Addr, &str, u64) -> (), + fn(&mut App, ContractInfo, &str, u64, Auth) -> (), ) = match end_status { EndStatus::Passed => (Vote::Yes, Status::Passed, execute_proposal), - EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal), + EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal_wrapper), }; - let new_status = vote(&mut app, proposal_single.clone(), "ekez", id, position); + let new_status = vote( + &mut app, + proposal_single_contract_info.clone(), + "ekez", + id, + position, + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); assert_eq!(new_status, expected_status); // Close or execute the proposal to trigger a refund. - trigger_refund(&mut app, proposal_single, "ekez", id); + trigger_refund( + &mut app, + proposal_single_contract_info, + "ekez", + id, + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); } ApprovalStatus::Rejected => { // Proposal is rejected by approver // No proposal is created so there is no voting - reject_proposal(&mut app, pre_propose.clone(), "approver", pre_propose_id); + reject_proposal( + &mut app, + pre_propose_contract_info.clone(), + "approver", + pre_propose_id, + ); } }; @@ -576,8 +1017,20 @@ fn test_cw20_permutation( RefundReceiver::Dao => (10, 0), }; - let proposer_balance = get_balance_cw20(&app, &cw20_address, "ekez"); - let dao_balance = get_balance_cw20(&app, &cw20_address, core_addr); + let proposer_balance = get_balance_snip20( + &app, + &snip20_contract_info.address.clone(), + snip20_contract_info.code_hash.clone(), + "ekez", + viewing_key_snip20, + ); + let dao_balance = get_balance_snip20( + &app, + &snip20_contract_info.address, + snip20_contract_info.code_hash, + core_contract_info.address.as_str(), + viewing_key_snip20_core, + ); assert_eq!(proposer_expected, proposer_balance.u128()); assert_eq!(dao_expected, dao_balance.u128()) } @@ -603,8 +1056,8 @@ fn test_native_rejected_always_refund() { } #[test] -fn test_cw20_failed_always_refund() { - test_cw20_permutation( +fn test_snip20_failed_always_refund() { + test_snip20_permutation( EndStatus::Failed, DepositRefundPolicy::Always, RefundReceiver::Proposer, @@ -613,8 +1066,8 @@ fn test_cw20_failed_always_refund() { } #[test] -fn test_cw20_rejected_always_refund() { - test_cw20_permutation( +fn test_snip20_rejected_always_refund() { + test_snip20_permutation( EndStatus::Failed, DepositRefundPolicy::Always, RefundReceiver::Proposer, @@ -633,8 +1086,8 @@ fn test_native_passed_always_refund() { } #[test] -fn test_cw20_passed_always_refund() { - test_cw20_permutation( +fn test_snip20_passed_always_refund() { + test_snip20_permutation( EndStatus::Passed, DepositRefundPolicy::Always, RefundReceiver::Proposer, @@ -653,8 +1106,8 @@ fn test_native_passed_never_refund() { } #[test] -fn test_cw20_passed_never_refund() { - test_cw20_permutation( +fn test_snip20_passed_never_refund() { + test_snip20_permutation( EndStatus::Passed, DepositRefundPolicy::Never, RefundReceiver::Dao, @@ -683,8 +1136,8 @@ fn test_native_rejected_never_refund() { } #[test] -fn test_cw20_failed_never_refund() { - test_cw20_permutation( +fn test_snip20_failed_never_refund() { + test_snip20_permutation( EndStatus::Failed, DepositRefundPolicy::Never, RefundReceiver::Dao, @@ -693,8 +1146,8 @@ fn test_cw20_failed_never_refund() { } #[test] -fn test_cw20_rejected_never_refund() { - test_cw20_permutation( +fn test_snip20_rejected_never_refund() { + test_snip20_permutation( EndStatus::Failed, DepositRefundPolicy::Never, RefundReceiver::Dao, @@ -712,8 +1165,8 @@ fn test_native_passed_passed_refund() { ) } #[test] -fn test_cw20_passed_passed_refund() { - test_cw20_permutation( +fn test_snip20_passed_passed_refund() { + test_snip20_permutation( EndStatus::Passed, DepositRefundPolicy::OnlyPassed, RefundReceiver::Proposer, @@ -742,8 +1195,8 @@ fn test_native_rejected_passed_refund() { } #[test] -fn test_cw20_failed_passed_refund() { - test_cw20_permutation( +fn test_snip20_failed_passed_refund() { + test_snip20_permutation( EndStatus::Failed, DepositRefundPolicy::OnlyPassed, RefundReceiver::Dao, @@ -752,8 +1205,8 @@ fn test_cw20_failed_passed_refund() { } #[test] -fn test_cw20_rejected_passed_refund() { - test_cw20_permutation( +fn test_snip20_rejected_passed_refund() { + test_snip20_permutation( EndStatus::Failed, DepositRefundPolicy::OnlyPassed, RefundReceiver::Dao, @@ -767,9 +1220,9 @@ fn test_multiple_open_proposals() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { @@ -782,39 +1235,78 @@ fn test_multiple_open_proposals() { false, ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + mint_natives(&mut app, "ekez", coins(20, "ujuno")); - let first_pre_propose_id = - make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + let first_pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); let balance = get_balance_native(&app, "ekez", "ujuno"); assert_eq!(10, balance.u128()); // Approver approves prop, balance remains the same let first_id = approve_proposal( &mut app, - pre_propose.clone(), + pre_propose_contract_info.clone(), "approver", first_pre_propose_id, ); let balance = get_balance_native(&app, "ekez", "ujuno"); assert_eq!(10, balance.u128()); - let second_pre_propose_id = - make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + let second_pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); let balance = get_balance_native(&app, "ekez", "ujuno"); assert_eq!(0, balance.u128()); // Approver approves prop, balance remains the same - let second_id = approve_proposal(&mut app, pre_propose, "approver", second_pre_propose_id); + let second_id = approve_proposal( + &mut app, + pre_propose_contract_info, + "approver", + second_pre_propose_id, + ); let balance = get_balance_native(&app, "ekez", "ujuno"); assert_eq!(0, balance.u128()); // Finish up the first proposal. let new_status = vote( &mut app, - proposal_single.clone(), + proposal_single_contract_info.clone(), "ekez", first_id, Vote::Yes, + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, ); assert_eq!(Status::Passed, new_status); @@ -822,7 +1314,16 @@ fn test_multiple_open_proposals() { let balance = get_balance_native(&app, "ekez", "ujuno"); assert_eq!(0, balance.u128()); - execute_proposal(&mut app, proposal_single.clone(), "ekez", first_id); + execute_proposal( + &mut app, + proposal_single_contract_info.clone(), + "ekez", + first_id, + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); // First proposal refunded. let balance = get_balance_native(&app, "ekez", "ujuno"); @@ -831,10 +1332,14 @@ fn test_multiple_open_proposals() { // Finish up the second proposal. let new_status = vote( &mut app, - proposal_single.clone(), + proposal_single_contract_info.clone(), "ekez", second_id, Vote::No, + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, ); assert_eq!(Status::Rejected, new_status); @@ -842,7 +1347,7 @@ fn test_multiple_open_proposals() { let balance = get_balance_native(&app, "ekez", "ujuno"); assert_eq!(10, balance.u128()); - close_proposal(&mut app, proposal_single, "ekez", second_id); + close_proposal(&mut app, proposal_single_contract_info, "ekez", second_id); // All deposits have been refunded. let balance = get_balance_native(&app, "ekez", "ujuno"); @@ -854,9 +1359,9 @@ fn test_pending_proposal_queries() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single: _, - pre_propose, + core_contract_info, + proposal_single_contract_info: _, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { @@ -869,15 +1374,48 @@ fn test_pending_proposal_queries() { false, ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + mint_natives(&mut app, "ekez", coins(20, "ujuno")); - make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); - make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); + make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); // Query for individual proposal let prop1: Proposal = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::PendingProposal { id: 1 }, }, @@ -889,7 +1427,8 @@ fn test_pending_proposal_queries() { let prop1: Proposal = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::Proposal { id: 1 }, }, @@ -902,7 +1441,8 @@ fn test_pending_proposal_queries() { let pre_propose_props: Vec = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::PendingProposals { start_after: None, @@ -918,7 +1458,8 @@ fn test_pending_proposal_queries() { let reverse_pre_propose_props: Vec = app .wrap() .query_wasm_smart( - pre_propose, + pre_propose_contract_info.code_hash, + pre_propose_contract_info.address, &QueryMsg::QueryExtension { msg: QueryExt::ReversePendingProposals { start_before: None, @@ -937,9 +1478,9 @@ fn test_completed_proposal_queries() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single: _, - pre_propose, + core_contract_info, + proposal_single_contract_info: _, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { @@ -952,14 +1493,47 @@ fn test_completed_proposal_queries() { false, ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + mint_natives(&mut app, "ekez", coins(20, "ujuno")); - let approve_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); - let reject_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + let approve_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); + let reject_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "ekez".into(), + }, + ); let is_pending: bool = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::IsPending { id: approve_id }, }, @@ -967,14 +1541,24 @@ fn test_completed_proposal_queries() { .unwrap(); assert!(is_pending); - let created_approved_id = - approve_proposal(&mut app, pre_propose.clone(), "approver", approve_id); - reject_proposal(&mut app, pre_propose.clone(), "approver", reject_id); + let created_approved_id = approve_proposal( + &mut app, + pre_propose_contract_info.clone(), + "approver", + approve_id, + ); + reject_proposal( + &mut app, + pre_propose_contract_info.clone(), + "approver", + reject_id, + ); let is_pending: bool = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::IsPending { id: approve_id }, }, @@ -986,7 +1570,8 @@ fn test_completed_proposal_queries() { let prop1: Proposal = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::CompletedProposal { id: approve_id }, }, @@ -1001,7 +1586,8 @@ fn test_completed_proposal_queries() { let prop1: Proposal = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::Proposal { id: approve_id }, }, @@ -1017,7 +1603,8 @@ fn test_completed_proposal_queries() { let prop1_id: Option = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::CompletedProposalIdForCreatedProposalId { id: created_approved_id, @@ -1030,7 +1617,8 @@ fn test_completed_proposal_queries() { let prop2: Proposal = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::CompletedProposal { id: reject_id }, }, @@ -1042,7 +1630,8 @@ fn test_completed_proposal_queries() { let pre_propose_props: Vec = app .wrap() .query_wasm_smart( - pre_propose.clone(), + pre_propose_contract_info.code_hash.clone(), + pre_propose_contract_info.address.clone(), &QueryMsg::QueryExtension { msg: QueryExt::CompletedProposals { start_after: None, @@ -1059,7 +1648,8 @@ fn test_completed_proposal_queries() { let reverse_pre_propose_props: Vec = app .wrap() .query_wasm_smart( - pre_propose, + pre_propose_contract_info.code_hash, + pre_propose_contract_info.address, &QueryMsg::QueryExtension { msg: QueryExt::ReverseCompletedProposals { start_before: None, @@ -1075,13 +1665,13 @@ fn test_completed_proposal_queries() { } #[test] -fn test_set_version() { +fn test_permissions() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single: _, - pre_propose, + core_contract_info, + proposal_single_contract_info: _, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { @@ -1091,49 +1681,27 @@ fn test_set_version() { amount: Uint128::new(10), refund_policy: DepositRefundPolicy::Always, }), - false, + false, // no open proposal submission. ); - let info: ContractVersion = from_json( - app.wrap() - .query_wasm_raw(pre_propose, "contract_info".as_bytes()) - .unwrap() - .unwrap(), - ) - .unwrap(); - assert_eq!( - ContractVersion { - contract: CONTRACT_NAME.to_string(), - version: CONTRACT_VERSION.to_string() - }, - info - ) -} - -#[test] -fn test_permissions() { - let mut app = App::default(); - - let DefaultTestSetup { - core_addr, - proposal_single: _, - pre_propose, - } = setup_default_test( + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::Token { - denom: UncheckedDenom::Native("ujuno".to_string()), - }, - amount: Uint128::new(10), - refund_policy: DepositRefundPolicy::Always, - }), - false, // no open proposal submission. + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "nonmember", ); let err: PreProposeError = app .execute_contract( - core_addr, - pre_propose.clone(), + core_contract_info.address, + &pre_propose_contract_info.clone(), &ExecuteMsg::ProposalCompletedHook { proposal_id: 1, new_status: Status::Closed, @@ -1150,13 +1718,17 @@ fn test_permissions() { let err: PreProposeError = app .execute_contract( Addr::unchecked("nonmember"), - pre_propose, + &pre_propose_contract_info, &ExecuteMsg::Propose { msg: ProposeMessage::Propose { title: "I would like to join the DAO".to_string(), description: "though, I am currently not a member.".to_string(), msgs: vec![], }, + auth: Auth::ViewingKey { + key: viewing_key, + address: "nonmember".into(), + }, }, &[], ) @@ -1170,9 +1742,9 @@ fn test_permissions() { fn test_approval_and_rejection_permissions() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single: _, - pre_propose, + core_contract_info, + proposal_single_contract_info: _, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { @@ -1185,20 +1757,38 @@ fn test_approval_and_rejection_permissions() { true, // yes, open proposal submission. ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "nonmember", + ); + // Non-member proposes. mint_natives(&mut app, "nonmember", coins(10, "ujuno")); let pre_propose_id = make_pre_proposal( &mut app, - pre_propose.clone(), + pre_propose_contract_info.clone(), "nonmember", &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "nonmember".into(), + }, ); // Only approver can propose let err: PreProposeError = app .execute_contract( Addr::unchecked("nonmember"), - pre_propose.clone(), + &pre_propose_contract_info.clone(), &ExecuteMsg::Extension { msg: ExecuteExt::Approve { id: pre_propose_id }, }, @@ -1213,7 +1803,7 @@ fn test_approval_and_rejection_permissions() { let err: PreProposeError = app .execute_contract( Addr::unchecked("nonmember"), - pre_propose, + &pre_propose_contract_info, &ExecuteMsg::Extension { msg: ExecuteExt::Reject { id: pre_propose_id }, }, @@ -1229,9 +1819,9 @@ fn test_approval_and_rejection_permissions() { fn test_propose_open_proposal_submission() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info, + pre_propose_contract_info, } = setup_default_test( &mut app, Some(UncheckedDepositInfo { @@ -1244,20 +1834,62 @@ fn test_propose_open_proposal_submission() { true, // yes, open proposal submission. ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr.clone(), + code_hash: query_auth.code_hash.clone(), + }, + "nonmember", + ); + + let viewing_key_ekez = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + // Non-member proposes. mint_natives(&mut app, "nonmember", coins(10, "ujuno")); let pre_propose_id = make_pre_proposal( &mut app, - pre_propose.clone(), + pre_propose_contract_info.clone(), "nonmember", &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key.clone(), + address: "nonmember".into(), + }, ); // Approver approves - let id = approve_proposal(&mut app, pre_propose, "approver", pre_propose_id); + let id = approve_proposal( + &mut app, + pre_propose_contract_info, + "approver", + pre_propose_id, + ); // Member votes. - let new_status = vote(&mut app, proposal_single, "ekez", id, Vote::Yes); + let new_status = vote( + &mut app, + proposal_single_contract_info, + "ekez", + id, + Vote::Yes, + Auth::ViewingKey { + key: viewing_key_ekez, + address: "ekez".into(), + }, + ); assert_eq!(Status::Passed, new_status) } @@ -1265,21 +1897,68 @@ fn test_propose_open_proposal_submission() { fn test_no_deposit_required_open_submission() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info, + pre_propose_contract_info, } = setup_default_test( &mut app, None, true, // yes, open proposal submission. ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr.clone(), + code_hash: query_auth.code_hash.clone(), + }, + "nonmember", + ); + + let viewing_key_ekez = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + // Non-member proposes. - let pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "nonmember", &[]); + let pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "nonmember", + &[], + Auth::ViewingKey { + key: viewing_key, + address: "nonmember".into(), + }, + ); // Approver approves - let id = approve_proposal(&mut app, pre_propose, "approver", pre_propose_id); + let id = approve_proposal( + &mut app, + pre_propose_contract_info, + "approver", + pre_propose_id, + ); // Member votes. - let new_status = vote(&mut app, proposal_single, "ekez", id, Vote::Yes); + let new_status = vote( + &mut app, + proposal_single_contract_info, + "ekez", + id, + Vote::Yes, + Auth::ViewingKey { + key: viewing_key_ekez, + address: "ekez".into(), + }, + ); assert_eq!(Status::Passed, new_status) } @@ -1287,24 +1966,51 @@ fn test_no_deposit_required_open_submission() { fn test_no_deposit_required_members_submission() { let mut app = App::default(); let DefaultTestSetup { - core_addr: _, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info, + pre_propose_contract_info, } = setup_default_test( &mut app, None, false, // no open proposal submission. ); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + let viewing_key = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr.clone(), + code_hash: query_auth.code_hash.clone(), + }, + "nonmember", + ); + + let viewing_key_ekez = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + // Non-member proposes and this fails. let err: PreProposeError = app .execute_contract( Addr::unchecked("nonmember"), - pre_propose.clone(), + &pre_propose_contract_info.clone(), &ExecuteMsg::Propose { msg: ProposeMessage::Propose { title: "I would like to join the DAO".to_string(), description: "though, I am currently not a member.".to_string(), msgs: vec![], }, + auth: Auth::ViewingKey { + key: viewing_key.clone(), + address: "nonmember".into(), + }, }, &[], ) @@ -1313,12 +2019,36 @@ fn test_no_deposit_required_members_submission() { .unwrap(); assert_eq!(err, PreProposeError::NotMember {}); - let pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); + let pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &[], + Auth::ViewingKey { + key: viewing_key_ekez.clone(), + address: "ekez".into(), + }, + ); // Approver approves - let id = approve_proposal(&mut app, pre_propose, "approver", pre_propose_id); + let id = approve_proposal( + &mut app, + pre_propose_contract_info, + "approver", + pre_propose_id, + ); - let new_status = vote(&mut app, proposal_single, "ekez", id, Vote::Yes); + let new_status = vote( + &mut app, + proposal_single_contract_info, + "ekez", + id, + Vote::Yes, + Auth::ViewingKey { + key: viewing_key_ekez, + address: "ekez".into(), + }, + ); assert_eq!(Status::Passed, new_status) } @@ -1327,10 +2057,11 @@ fn test_no_deposit_required_members_submission() { fn test_instantiate_with_zero_native_deposit() { let mut app = App::default(); - let dao_proposal_single_id = app.store_code(cw_dao_proposal_single_contract()); + let dao_proposal_single_instantiate_info = app.store_code(cw_dao_proposal_single_contract()); + let core_instantiate_info = app.store_code(dao_dao_contract()); let proposal_module_instantiate = { - let pre_propose_id = app.store_code(cw_pre_propose_base_proposal_single()); + let pre_propose_instantiate_info = app.store_code(cw_pre_propose_base_proposal_single()); dao_proposal_single::msg::InstantiateMsg { threshold: Threshold::AbsolutePercentage { @@ -1342,8 +2073,8 @@ fn test_instantiate_with_zero_native_deposit() { allow_revoting: false, pre_propose_info: PreProposeInfo::ModuleMayPropose { info: ModuleInstantiateInfo { - code_id: pre_propose_id, - msg: to_json_binary(&InstantiateMsg { + code_id: pre_propose_instantiate_info.code_id, + msg: to_binary(&InstantiateMsg { deposit_info: Some(UncheckedDepositInfo { denom: DepositToken::Token { denom: UncheckedDenom::Native("ujuno".to_string()), @@ -1360,24 +2091,28 @@ fn test_instantiate_with_zero_native_deposit() { admin: Some(Admin::CoreModule {}), funds: vec![], label: "baby's first pre-propose module".to_string(), + code_hash: pre_propose_instantiate_info.code_hash.clone(), }, }, close_proposal_on_execution_failure: false, veto: None, + query_auth: None, } }; // Should panic. instantiate_with_cw4_groups_governance( &mut app, - dao_proposal_single_id, - to_json_binary(&proposal_module_instantiate).unwrap(), + core_instantiate_info, + dao_proposal_single_instantiate_info.code_id, + dao_proposal_single_instantiate_info.code_hash, + to_binary(&proposal_module_instantiate).unwrap(), Some(vec![ - cw20::Cw20Coin { + InitialBalance { address: "ekez".to_string(), amount: Uint128::new(9), }, - cw20::Cw20Coin { + InitialBalance { address: "keze".to_string(), amount: Uint128::new(8), }, @@ -1387,15 +2122,16 @@ fn test_instantiate_with_zero_native_deposit() { #[test] #[should_panic(expected = "invalid zero deposit. set the deposit to `None` to have no deposit")] -fn test_instantiate_with_zero_cw20_deposit() { +fn test_instantiate_with_zero_snip20_deposit() { let mut app = App::default(); - let cw20_addr = instantiate_cw20_base_default(&mut app); + let snip20_contract_info = instantiate_snip20_base_default(&mut app); - let dao_proposal_single_id = app.store_code(cw_dao_proposal_single_contract()); + let dao_proposal_single_instantiate_info = app.store_code(cw_dao_proposal_single_contract()); + let core_contract_info = app.store_code(dao_dao_contract()); let proposal_module_instantiate = { - let pre_propose_id = app.store_code(cw_pre_propose_base_proposal_single()); + let pre_propose_instantiate_info = app.store_code(cw_pre_propose_base_proposal_single()); dao_proposal_single::msg::InstantiateMsg { threshold: Threshold::AbsolutePercentage { @@ -1407,11 +2143,15 @@ fn test_instantiate_with_zero_cw20_deposit() { allow_revoting: false, pre_propose_info: PreProposeInfo::ModuleMayPropose { info: ModuleInstantiateInfo { - code_id: pre_propose_id, - msg: to_json_binary(&InstantiateMsg { + code_id: pre_propose_instantiate_info.code_id, + code_hash: pre_propose_instantiate_info.code_hash, + msg: to_binary(&InstantiateMsg { deposit_info: Some(UncheckedDepositInfo { denom: DepositToken::Token { - denom: UncheckedDenom::Cw20(cw20_addr.into_string()), + denom: UncheckedDenom::Snip20( + snip20_contract_info.address.into_string(), + snip20_contract_info.code_hash, + ), }, amount: Uint128::zero(), refund_policy: DepositRefundPolicy::OnlyPassed, @@ -1429,20 +2169,23 @@ fn test_instantiate_with_zero_cw20_deposit() { }, close_proposal_on_execution_failure: false, veto: None, + query_auth: None, } }; // Should panic. instantiate_with_cw4_groups_governance( &mut app, - dao_proposal_single_id, - to_json_binary(&proposal_module_instantiate).unwrap(), + core_contract_info, + dao_proposal_single_instantiate_info.code_id, + dao_proposal_single_instantiate_info.code_hash, + to_binary(&proposal_module_instantiate).unwrap(), Some(vec![ - cw20::Cw20Coin { + InitialBalance { address: "ekez".to_string(), amount: Uint128::new(9), }, - cw20::Cw20Coin { + InitialBalance { address: "keze".to_string(), amount: Uint128::new(8), }, @@ -1454,12 +2197,16 @@ fn test_instantiate_with_zero_cw20_deposit() { fn test_update_config() { let mut app = App::default(); let DefaultTestSetup { - core_addr, - proposal_single, - pre_propose, + core_contract_info, + proposal_single_contract_info, + pre_propose_contract_info, } = setup_default_test(&mut app, None, false); - let config = get_config(&app, pre_propose.clone()); + let config = get_config( + &app, + pre_propose_contract_info.address.clone(), + pre_propose_contract_info.code_hash.clone(), + ); assert_eq!( config, Config { @@ -1468,15 +2215,44 @@ fn test_update_config() { } ); - let pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); + let query_auth = query_query_auth( + &app, + core_contract_info.address.clone(), + core_contract_info.code_hash.clone(), + ); + + let viewing_key_ekez = create_viewing_key( + &mut app, + ContractInfo { + address: query_auth.addr, + code_hash: query_auth.code_hash, + }, + "ekez", + ); + + let pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &[], + Auth::ViewingKey { + key: viewing_key_ekez.clone(), + address: "ekez".into(), + }, + ); // Approver approves - let id = approve_proposal(&mut app, pre_propose.clone(), "approver", pre_propose_id); + let id = approve_proposal( + &mut app, + pre_propose_contract_info.clone(), + "approver", + pre_propose_id, + ); update_config( &mut app, - pre_propose.clone(), - core_addr.as_str(), + pre_propose_contract_info.clone(), + core_contract_info.address.as_str(), Some(UncheckedDepositInfo { denom: DepositToken::Token { denom: UncheckedDenom::Native("ujuno".to_string()), @@ -1487,7 +2263,11 @@ fn test_update_config() { true, ); - let config = get_config(&app, pre_propose.clone()); + let config = get_config( + &app, + pre_propose_contract_info.address.clone(), + pre_propose_contract_info.code_hash.clone(), + ); assert_eq!( config, Config { @@ -1501,7 +2281,12 @@ fn test_update_config() { ); // Old proposal should still have same deposit info. - let info = get_deposit_info(&app, pre_propose.clone(), id); + let info = get_deposit_info( + &app, + pre_propose_contract_info.address.clone(), + pre_propose_contract_info.code_hash.clone(), + id, + ); assert_eq!( info, DepositInfoResponse { @@ -1512,18 +2297,31 @@ fn test_update_config() { // New proposals should have the new deposit info. mint_natives(&mut app, "ekez", coins(10, "ujuno")); - let new_pre_propose_id = - make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + let new_pre_propose_id = make_pre_proposal( + &mut app, + pre_propose_contract_info.clone(), + "ekez", + &coins(10, "ujuno"), + Auth::ViewingKey { + key: viewing_key_ekez.clone(), + address: "ekez".into(), + }, + ); // Approver approves let new_id = approve_proposal( &mut app, - pre_propose.clone(), + pre_propose_contract_info.clone(), "approver", new_pre_propose_id, ); - let info = get_deposit_info(&app, pre_propose.clone(), new_id); + let info = get_deposit_info( + &app, + pre_propose_contract_info.address.clone(), + pre_propose_contract_info.code_hash.clone(), + new_id, + ); assert_eq!( info, DepositInfoResponse { @@ -1537,176 +2335,59 @@ fn test_update_config() { ); // Both proposals should be allowed to complete. - vote(&mut app, proposal_single.clone(), "ekez", id, Vote::Yes); - vote(&mut app, proposal_single.clone(), "ekez", new_id, Vote::Yes); - execute_proposal(&mut app, proposal_single.clone(), "ekez", id); - execute_proposal(&mut app, proposal_single.clone(), "ekez", new_id); - // Deposit should not have been refunded (never policy in use). - let balance = get_balance_native(&app, "ekez", "ujuno"); - assert_eq!(balance, Uint128::new(0)); - - // Only the core module can update the config. - let err = - update_config_should_fail(&mut app, pre_propose, proposal_single.as_str(), None, true); - assert_eq!(err, PreProposeError::NotDao {}); -} - -#[test] -fn test_withdraw() { - let mut app = App::default(); - - let DefaultTestSetup { - core_addr, - proposal_single, - pre_propose, - } = setup_default_test(&mut app, None, false); - - let err = withdraw_should_fail( - &mut app, - pre_propose.clone(), - proposal_single.as_str(), - Some(UncheckedDenom::Native("ujuno".to_string())), - ); - assert_eq!(err, PreProposeError::NotDao {}); - - let err = withdraw_should_fail( - &mut app, - pre_propose.clone(), - core_addr.as_str(), - Some(UncheckedDenom::Native("ujuno".to_string())), - ); - assert_eq!(err, PreProposeError::NothingToWithdraw {}); - - let err = withdraw_should_fail(&mut app, pre_propose.clone(), core_addr.as_str(), None); - assert_eq!(err, PreProposeError::NoWithdrawalDenom {}); - - // Turn on native deposits. - update_config( - &mut app, - pre_propose.clone(), - core_addr.as_str(), - Some(UncheckedDepositInfo { - denom: DepositToken::Token { - denom: UncheckedDenom::Native("ujuno".to_string()), - }, - amount: Uint128::new(10), - refund_policy: DepositRefundPolicy::Always, - }), - false, - ); - - // Withdraw with no specified denom - should fall back to the one - // in the config. - mint_natives(&mut app, pre_propose.as_str(), coins(10, "ujuno")); - withdraw(&mut app, pre_propose.clone(), core_addr.as_str(), None); - let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); - assert_eq!(balance, Uint128::new(10)); - - // Withdraw again, this time specifying a native denomination. - mint_natives(&mut app, pre_propose.as_str(), coins(10, "ujuno")); - withdraw( - &mut app, - pre_propose.clone(), - core_addr.as_str(), - Some(UncheckedDenom::Native("ujuno".to_string())), - ); - let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); - assert_eq!(balance, Uint128::new(20)); - - // Make a proposal with the native tokens to put some in the system. - mint_natives(&mut app, "ekez", coins(10, "ujuno")); - let native_pre_propose_id = - make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); - - // Approver approves - let native_id = approve_proposal( - &mut app, - pre_propose.clone(), - "approver", - native_pre_propose_id, - ); - - // Update the config to use a cw20 token. - let cw20_address = instantiate_cw20_base_default(&mut app); - update_config( - &mut app, - pre_propose.clone(), - core_addr.as_str(), - Some(UncheckedDepositInfo { - denom: DepositToken::Token { - denom: UncheckedDenom::Cw20(cw20_address.to_string()), - }, - amount: Uint128::new(10), - refund_policy: DepositRefundPolicy::Always, - }), - false, - ); - - increase_allowance( + vote( &mut app, + proposal_single_contract_info.clone(), "ekez", - &pre_propose, - cw20_address.clone(), - Uint128::new(10), - ); - let cw20_pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); - - // Approver approves - let cw20_id = approve_proposal( - &mut app, - pre_propose.clone(), - "approver", - cw20_pre_propose_id, + id, + Vote::Yes, + Auth::ViewingKey { + key: viewing_key_ekez.clone(), + address: "ekez".into(), + }, ); - - // There is now a pending proposal and cw20 tokens in the - // pre-propose module that should be returned on that proposal's - // completion. To make things interesting, we withdraw those - // tokens which should cause the status change hook on the - // proposal's execution to fail as we don't have sufficent balance - // to return the deposit. - withdraw(&mut app, pre_propose.clone(), core_addr.as_str(), None); - let balance = get_balance_cw20(&app, &cw20_address, core_addr.as_str()); - assert_eq!(balance, Uint128::new(10)); - - // Proposal should still be executable! We just get removed from - // the proposal module's hook receiver list. vote( &mut app, - proposal_single.clone(), + proposal_single_contract_info.clone(), "ekez", - cw20_id, + new_id, Vote::Yes, + Auth::ViewingKey { + key: viewing_key_ekez.clone(), + address: "ekez".into(), + }, ); - execute_proposal(&mut app, proposal_single.clone(), "ekez", cw20_id); - - // Make sure the proposal module has fallen back to anyone can - // propose becuase of our malfunction. - let proposal_creation_policy: ProposalCreationPolicy = app - .wrap() - .query_wasm_smart( - proposal_single.clone(), - &dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {}, - ) - .unwrap(); - - assert_eq!(proposal_creation_policy, ProposalCreationPolicy::Anyone {}); - - // Close out the native proposal and it's deposit as well. - vote( + execute_proposal( &mut app, - proposal_single.clone(), + proposal_single_contract_info.clone(), "ekez", - native_id, - Vote::No, + id, + Auth::ViewingKey { + key: viewing_key_ekez.clone(), + address: "ekez".into(), + }, ); - close_proposal(&mut app, proposal_single.clone(), "ekez", native_id); - withdraw( + execute_proposal( &mut app, - pre_propose.clone(), - core_addr.as_str(), - Some(UncheckedDenom::Native("ujuno".to_string())), + proposal_single_contract_info.clone(), + "ekez", + new_id, + Auth::ViewingKey { + key: viewing_key_ekez.clone(), + address: "ekez".into(), + }, ); - let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); - assert_eq!(balance, Uint128::new(30)); + // Deposit should not have been refunded (never policy in use). + let balance = get_balance_native(&app, "ekez", "ujuno"); + assert_eq!(balance, Uint128::new(0)); + + // Only the core module can update the config. + let err = update_config_should_fail( + &mut app, + pre_propose_contract_info, + proposal_single_contract_info.address.as_str(), + None, + true, + ); + assert_eq!(err, PreProposeError::NotDao {}); } diff --git a/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml b/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml index b75484c..5f07de4 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml @@ -40,7 +40,7 @@ dao-proposal-single = { workspace = true, features = ["library"] } dao-voting = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-snip20-staked = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } snip721-reference-impl = { workspace = true } cw4 ={ workspace = true} query_auth ={ workspace = true} diff --git a/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs b/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs index bb8b6a7..04a8da6 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs +++ b/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs @@ -33,13 +33,13 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> Result { + println!("In instantiate and sender is {}", info.sender); // This contract does not handle deposits or have open submissions // Here we hardcode the pre-propose-base instantiate message let base_instantiate_msg = BaseInstantiateMsg { deposit_info: None, open_proposal_submission: false, extension: Empty {}, - proposal_module_code_hash: msg.proposal_module_code_hash, }; // Default pre-propose-base instantiation let resp = PrePropose::default().instantiate( @@ -113,6 +113,7 @@ pub fn execute_propose( ) -> Result { // Check that this is coming from the expected approval contract let approval_contract = PRE_PROPOSE_APPROVAL_CONTRACT.load(deps.storage)?; + println!("In execute propose approver"); if info.sender != approval_contract.addr { return Err(PreProposeError::Unauthorized {}); } @@ -213,6 +214,8 @@ pub fn execute_reset_approver( ) -> Result { // Check that this is coming from the DAO. let dao = PrePropose::default().dao.load(deps.storage)?; + println!("In execute reset approver"); + if info.sender != dao.addr { return Err(PreProposeError::Unauthorized {}); } diff --git a/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs b/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs index 21f0bcc..0be2fd3 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs @@ -1,2068 +1,1458 @@ -use cosmwasm_std::{ - coins, from_binary, to_binary, Addr, Binary, Coin, ContractInfo, Empty, Uint128, -}; -use cw_denom::UncheckedDenom; -use dao_voting_cw4::msg::GroupContract; -use dps::query::{ProposalListResponse, ProposalResponse}; -use secret_cw2::ContractVersion; -use secret_multi_test::{ - App, BankSudo, Contract, ContractInstantiationInfo, ContractWrapper, Executor, -}; - -use dao_interface::state::{Admin, ModuleInstantiateInfo}; -use dao_interface::state::{AnyContractInfo, ProposalModule}; -use dao_pre_propose_approval_single::{ - msg::{ - ExecuteExt, ExecuteMsg, InstantiateExt, InstantiateMsg, ProposeMessage, QueryExt, QueryMsg, - }, - state::Proposal, -}; -use dao_pre_propose_base::{error::PreProposeError, msg::DepositInfoResponse, state::Config}; -use dao_proposal_single as dps; -use dao_voting::{ - deposit::{CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, - pre_propose::{PreProposeInfo, ProposalCreationPolicy}, - status::Status, - threshold::{PercentageThreshold, Threshold}, - voting::Vote, -}; -use shade_protocol::basic_staking::Auth; -use shade_protocol::utils::asset::RawContract; -use snip20_reference_impl::msg::{InitConfig, InitialBalance}; - -use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; -use crate::msg::InstantiateMsg as ApproverInstantiateMsg; -use crate::msg::{ - ExecuteExt as ApproverExecuteExt, ExecuteMsg as ApproverExecuteMsg, - QueryExt as ApproverQueryExt, QueryMsg as ApproverQueryMsg, -}; - -// The approver dao contract is the 6th contract instantiated -const APPROVER: &str = "contract6"; -const CREATOR_ADDR: &str = "creator"; - -pub fn cw4_group_contract() -> Box> { - let contract = ContractWrapper::new( - cw4_group::contract::execute, - cw4_group::contract::instantiate, - cw4_group::contract::query, - ); - Box::new(contract) -} - -pub fn dao_dao_contract() -> Box> { - let contract = ContractWrapper::new( - dao_dao_core::contract::execute, - dao_dao_core::contract::instantiate, - dao_dao_core::contract::query, - ) - .with_reply(dao_dao_core::contract::reply) - .with_migrate(dao_dao_core::contract::migrate); - Box::new(contract) -} - -pub fn dao_voting_cw4_contract() -> Box> { - let contract = ContractWrapper::new( - dao_voting_cw4::contract::execute, - dao_voting_cw4::contract::instantiate, - dao_voting_cw4::contract::query, - ) - .with_reply(dao_voting_cw4::contract::reply); - Box::new(contract) -} - -pub fn snip721_base_contract() -> Box> { - let contract = ContractWrapper::new( - snip721_reference_impl::contract::execute, - snip721_reference_impl::contract::instantiate, - snip721_reference_impl::contract::query, - ); - Box::new(contract) -} - -pub fn query_auth_contract() -> Box> { - let contract = ContractWrapper::new( - query_auth::contract::execute, - query_auth::contract::instantiate, - query_auth::contract::query, - ); - Box::new(contract) -} - -pub fn instantiate_with_cw4_groups_governance( - app: &mut App, - core_info: ContractInstantiationInfo, - core_code_id: u64, - core_code_hash: String, - proposal_module_instantiate: Binary, - initial_weights: Option>, -) -> ContractInfo { - let cw4_info = app.store_code(cw4_group_contract()); - let votemod_info = app.store_code(dao_voting_cw4_contract()); - let snip20_info = app.store_code(snip20_base_contract()); - let snip721_info = app.store_code(snip721_base_contract()); - let query_auth = app.store_code(query_auth_contract()); - let initial_weights = initial_weights.unwrap_or_default(); - - // Remove duplicates so that we can test duplicate voting. - let initial_weights: Vec = { - let mut already_seen = vec![]; - initial_weights - .into_iter() - .filter(|InitialBalance { address, .. }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .map(|InitialBalance { address, amount }| cw4::Member { - addr: address, - weight: amount.u128() as u64, - }) - .collect() - }; - - let governance_instantiate = dao_interface::msg::InstantiateMsg { - dao_uri: None, - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: votemod_info.code_id, - code_hash: votemod_info.code_hash, - msg: to_binary(&dao_voting_cw4::msg::InstantiateMsg { - group_contract: GroupContract::New { - cw4_group_code_id: cw4_info.code_id, - cw4_group_code_hash: cw4_info.code_hash, - initial_members: initial_weights, - query_auth: None, - }, - dao_code_hash: core_info.code_hash.clone(), - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: core_code_id, - code_hash: core_code_hash, - msg: proposal_module_instantiate, - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module".to_string(), - }], - initial_items: None, - query_auth_code_id: query_auth.code_id, - query_auth_code_hash: query_auth.code_hash, - prng_seed: "seed".into(), - snip20_code_hash: snip20_info.code_hash, - snip721_code_hash: snip721_info.code_hash, - }; - - let addr = app - .instantiate_contract( - core_info.clone(), - Addr::unchecked(CREATOR_ADDR), - &governance_instantiate, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - // Update the block so that weights appear. - app.update_block(|block| block.height += 1); - - addr -} - -fn cw_dao_proposal_single_contract() -> Box> { - let contract = ContractWrapper::new( - dps::contract::execute, - dps::contract::instantiate, - dps::contract::query, - ) - .with_migrate(dps::contract::migrate) - .with_reply(dps::contract::reply); - Box::new(contract) -} - -fn cw_pre_propose_base_proposal_single() -> Box> { - let contract = ContractWrapper::new( - dao_pre_propose_approval_single::contract::execute, - dao_pre_propose_approval_single::contract::instantiate, - dao_pre_propose_approval_single::contract::query, - ); - Box::new(contract) -} - -fn snip20_base_contract() -> Box> { - let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, - ); - Box::new(contract) -} -fn pre_propose_approver_contract() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ); - Box::new(contract) -} - -fn get_proposal_module_approval_single_instantiate( - app: &mut App, - deposit_info: Option, - open_proposal_submission: bool, - proposal_module_code_hash: String, - query_auth: ContractInfo, - dao_code_hash: String, -) -> dps::msg::InstantiateMsg { - let pre_propose_info = app.store_code(cw_pre_propose_base_proposal_single()); - - dps::msg::InstantiateMsg { - threshold: Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Majority {}, - }, - max_voting_period: secret_utils::Duration::Time(86400), - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - pre_propose_info: PreProposeInfo::ModuleMayPropose { - info: ModuleInstantiateInfo { - code_id: pre_propose_info.code_id, - code_hash: pre_propose_info.code_hash.clone(), - msg: to_binary(&InstantiateMsg { - deposit_info, - open_proposal_submission, - extension: InstantiateExt { - approver: APPROVER.to_string(), - }, - proposal_module_code_hash, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "baby's first pre-propose module, needs supervision".to_string(), - }, - }, - close_proposal_on_execution_failure: false, - veto: None, - dao_code_hash, - query_auth: Some(RawContract::new( - &query_auth.address.into_string(), - &query_auth.code_hash, - )), - } -} - -fn get_proposal_module_approver_instantiate( - app: &mut App, - _deposit_info: Option, - _open_proposal_submission: bool, - pre_propose_approval_contract_info: ContractInfo, - proposal_module_code_hash: String, - query_auth: ContractInfo, - dao_code_hash: String, -) -> dps::msg::InstantiateMsg { - let pre_propose_info = app.store_code(pre_propose_approver_contract()); - - dps::msg::InstantiateMsg { - threshold: Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Majority {}, - }, - max_voting_period: secret_utils::Duration::Time(86400), - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - pre_propose_info: PreProposeInfo::ModuleMayPropose { - info: ModuleInstantiateInfo { - code_id: pre_propose_info.code_id, - code_hash: pre_propose_info.code_hash, - msg: to_binary(&ApproverInstantiateMsg { - pre_propose_approval_contract: pre_propose_approval_contract_info - .address - .into(), - pre_propose_approval_contract_code_hash: pre_propose_approval_contract_info - .code_hash, - proposal_module_code_hash, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "approver module".to_string(), - }, - }, - close_proposal_on_execution_failure: false, - veto: None, - dao_code_hash, - query_auth: Some(RawContract::new( - &query_auth.address.into_string(), - &query_auth.code_hash, - )), - } -} - -fn instantiate_snip20_base_default(app: &mut App) -> ContractInfo { - let snip20_info = app.store_code(snip20_base_contract()); - let snip20_instantiate = snip20_reference_impl::msg::InstantiateMsg { - name: "snip20 token".to_string(), - symbol: "sniptwenty".to_string(), - decimals: 6, - initial_balances: Some(vec![InitialBalance { - address: "ekez".to_string(), - amount: Uint128::new(10), - }]), - admin: None, - prng_seed: to_binary("data").unwrap(), - config: Some(InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(true), - enable_burn: Some(true), - can_modify_denoms: Some(true), - }), - supported_denoms: None, - }; - app.instantiate_contract( - snip20_info, - Addr::unchecked("ekez"), - &snip20_instantiate, - &[], - "snip20-base", - None, - ) - .unwrap() -} - -fn instantiate_query_auth(app: &mut App) -> ContractInfo { - let query_auth_info = app.store_code(query_auth_contract()); - let msg = shade_protocol::contract_interfaces::query_auth::InstantiateMsg { - admin_auth: shade_protocol::Contract { - address: Addr::unchecked("admin_contract"), - code_hash: "code_hash".to_string(), - }, - prng_seed: to_binary("seed").unwrap(), - }; - - app.instantiate_contract( - query_auth_info, - Addr::unchecked(CREATOR_ADDR), - &msg, - &[], - "query_auth", - None, - ) - .unwrap() -} - -fn create_viewing_key(app: &mut App, contract_info: ContractInfo, sender: &str) -> String { - let msg = shade_protocol::contract_interfaces::query_auth::ExecuteMsg::CreateViewingKey { - entropy: "entropy".to_string(), - padding: None, - }; - let res = app - .execute_contract(Addr::unchecked(sender), &contract_info, &msg, &[]) - .unwrap(); - let mut viewing_key = String::new(); - let data: shade_protocol::contract_interfaces::query_auth::ExecuteAnswer = - from_binary(&res.data.unwrap()).unwrap(); - if let shade_protocol::contract_interfaces::query_auth::ExecuteAnswer::CreateViewingKey { - key, - } = data - { - viewing_key = key; - }; - viewing_key -} - -fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: &str) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { - entropy: "entropy".to_string(), - padding: None, - }; - let res = app - .execute_contract(Addr::unchecked(addr), &contract_info, &msg, &[]) - .unwrap(); - let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { - viewing_key = key; - }; - viewing_key -} - -struct DefaultTestSetup { - core_contract_info: ContractInfo, - proposal_single_contract_info: ContractInfo, - pre_propose_contract_info: ContractInfo, - approver_core_contract_info: ContractInfo, - pre_propose_approver: ContractInfo, - proposal_single_approver_contract_info: ContractInfo, -} - -fn setup_default_test( - app: &mut App, - deposit_info: Option, - open_proposal_submission: bool, -) -> DefaultTestSetup { - let dps_info = app.store_code(cw_dao_proposal_single_contract()); - let query_auth = instantiate_query_auth(app); - let core_info = app.store_code(dao_dao_contract()); - - // Instantiate SubDAO with pre-propose-approval-single - let proposal_module_instantiate = get_proposal_module_approval_single_instantiate( - app, - deposit_info.clone(), - open_proposal_submission, - dps_info.code_hash.clone(), - query_auth.clone(), - core_info.code_hash.clone(), - ); - let core_contract_info = instantiate_with_cw4_groups_governance( - app, - core_info.clone(), - dps_info.code_id, - dps_info.code_hash.clone(), - to_binary(&proposal_module_instantiate).unwrap(), - Some(vec![ - InitialBalance { - address: "ekez".to_string(), - amount: Uint128::new(9), - }, - InitialBalance { - address: "keze".to_string(), - amount: Uint128::new(8), - }, - ]), - ); - let proposal_modules: Vec = app - .wrap() - .query_wasm_smart( - core_contract_info.code_hash.clone(), - core_contract_info.address.clone(), - &dao_interface::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - // Make sure things were set up correctly. - assert_eq!(proposal_modules.len(), 1); - let proposal_single_address = proposal_modules.clone().into_iter().next().unwrap().address; - let proposal_single_code_hash = proposal_modules.into_iter().next().unwrap().code_hash; - let proposal_creation_policy = app - .wrap() - .query_wasm_smart( - proposal_single_code_hash.clone(), - proposal_single_address.clone(), - &dps::msg::QueryMsg::ProposalCreationPolicy {}, - ) - .unwrap(); - let pre_propose = match proposal_creation_policy { - ProposalCreationPolicy::Module { addr, code_hash } => (addr, code_hash), - _ => panic!("expected a module for the proposal creation policy"), - }; - assert_eq!( - AnyContractInfo { - addr: proposal_single_address.clone(), - code_hash: proposal_single_code_hash.clone() - }, - get_proposal_module(app, pre_propose.0.clone(), pre_propose.1.clone()) - ); - assert_eq!( - AnyContractInfo { - addr: core_contract_info.address.clone(), - code_hash: core_contract_info.code_hash.clone() - }, - get_dao(app, pre_propose.0.clone(), pre_propose.1.clone()) - ); - - // Instantiate SubDAO with pre-propose-approver - let proposal_module_instantiate = get_proposal_module_approver_instantiate( - app, - deposit_info, - open_proposal_submission, - ContractInfo { - address: pre_propose.0.clone(), - code_hash: pre_propose.1.clone(), - }, - dps_info.code_hash.clone(), - query_auth.clone(), - core_contract_info.code_hash.clone(), - ); - - let approver_core_contract_info = instantiate_with_cw4_groups_governance( - app, - core_info, - dps_info.code_id, - dps_info.code_hash, - to_binary(&proposal_module_instantiate).unwrap(), - Some(vec![ - InitialBalance { - address: "ekez".to_string(), - amount: Uint128::new(9), - }, - InitialBalance { - address: "keze".to_string(), - amount: Uint128::new(8), - }, - ]), - ); - let proposal_modules: Vec = app - .wrap() - .query_wasm_smart( - approver_core_contract_info.code_hash.clone(), - approver_core_contract_info.address.clone(), - &dao_interface::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - // Make sure things were set up correctly. - assert_eq!(proposal_modules.len(), 1); - let proposal_single_approver_addr = - proposal_modules.clone().into_iter().next().unwrap().address; - let proposal_single_approver_code_hash = proposal_modules - .clone() - .into_iter() - .next() - .unwrap() - .code_hash; - - let proposal_creation_policy = app - .wrap() - .query_wasm_smart( - proposal_single_approver_code_hash.clone(), - proposal_single_approver_code_hash.clone(), - &dps::msg::QueryMsg::ProposalCreationPolicy {}, - ) - .unwrap(); - let pre_propose_approver = match proposal_creation_policy { - ProposalCreationPolicy::Module { addr, code_hash } => (addr, code_hash), - _ => panic!("expected a module for the proposal creation policy"), - }; - assert_eq!( - AnyContractInfo { - addr: proposal_single_approver_addr.clone(), - code_hash: proposal_single_approver_code_hash.clone(), - }, - get_proposal_module( - app, - pre_propose_approver.0.clone(), - pre_propose_approver.1.clone() - ) - ); - assert_eq!( - AnyContractInfo { - addr: approver_core_contract_info.address.clone(), - code_hash: approver_core_contract_info.code_hash.clone(), - }, - get_dao( - app, - pre_propose_approver.0.clone(), - pre_propose_approver.1.clone() - ) - ); - - DefaultTestSetup { - core_contract_info, - proposal_single_contract_info: ContractInfo { - address: proposal_single_address, - code_hash: proposal_single_code_hash, - }, - pre_propose_contract_info: ContractInfo { - address: pre_propose.0, - code_hash: pre_propose.1, - }, - approver_core_contract_info, - proposal_single_approver_contract_info: ContractInfo { - address: proposal_single_approver_addr, - code_hash: proposal_single_approver_code_hash, - }, - pre_propose_approver: ContractInfo { - address: pre_propose_approver.0, - code_hash: pre_propose_approver.1, - }, - } -} - -fn make_pre_proposal( - app: &mut App, - pre_propose_contract_info: ContractInfo, - proposer: &str, - funds: &[Coin], - auth: Auth, -) -> u64 { - app.execute_contract( - Addr::unchecked(proposer), - &pre_propose_contract_info.clone(), - &ExecuteMsg::Propose { - auth, - msg: ProposeMessage::Propose { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - }, - }, - funds, - ) - .unwrap(); - - // Query for pending proposal and return latest id - let mut pending: Vec = app - .wrap() - .query_wasm_smart( - pre_propose_contract_info.code_hash, - pre_propose_contract_info.address, - &QueryMsg::QueryExtension { - msg: QueryExt::PendingProposals { - start_after: None, - limit: None, - }, - }, - ) - .unwrap(); - - // Return last item in list, id is first element of tuple - pending.pop().unwrap().approval_id -} - -fn mint_natives(app: &mut App, receiver: &str, coins: Vec) { - // Mint some ekez tokens for ekez so we can pay the deposit. - app.sudo(secret_multi_test::SudoMsg::Bank(BankSudo::Mint { - to_address: receiver.to_string(), - amount: coins, - })) - .unwrap(); -} - -fn increase_allowance( - app: &mut App, - sender: &str, - receiver: &Addr, - snip20_contract_info: ContractInfo, - amount: Uint128, -) { - app.execute_contract( - Addr::unchecked(sender), - &snip20_contract_info, - &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { - spender: receiver.to_string(), - amount, - expiration: None, - padding: None, - }, - &[], - ) - .unwrap(); -} - -fn get_balance_snip20, C: Into, U: Into, K: Into>( - app: &App, - contract_addr: T, - code_hash: C, - address: U, - key: K, -) -> Uint128 { - let msg = snip20_reference_impl::msg::QueryMsg::Balance { - address: address.into(), - key: key.into(), - }; - let result: snip20_reference_impl::msg::QueryAnswer = app - .wrap() - .query_wasm_smart(code_hash, contract_addr, &msg) - .unwrap(); - let mut balance = Uint128::zero(); - if let snip20_reference_impl::msg::QueryAnswer::Balance { amount } = result { - balance = amount; - } - balance -} - -fn get_balance_native(app: &App, who: &str, denom: &str) -> Uint128 { - let res = app.wrap().query_balance(who, denom).unwrap(); - res.amount -} - -fn vote( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - id: u64, - position: Vote, - auth: Auth, -) -> Status { - app.execute_contract( - Addr::unchecked(sender), - &module_contract_info.clone(), - &dps::msg::ExecuteMsg::Vote { - proposal_id: id, - vote: position, - rationale: None, - auth, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = app - .wrap() - .query_wasm_smart( - module_contract_info.code_hash, - module_contract_info.address, - &dps::msg::QueryMsg::Proposal { proposal_id: id }, - ) - .unwrap(); - - proposal.proposal.status -} - -fn get_config(app: &App, module_addr: Addr, module_code_hash: String) -> Config { - app.wrap() - .query_wasm_smart(module_addr, module_code_hash, &QueryMsg::Config {}) - .unwrap() -} - -fn get_dao(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { - app.wrap() - .query_wasm_smart(module_code_hash, module_addr, &QueryMsg::Dao {}) - .unwrap() -} - -fn query_query_auth(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { - app.wrap() - .query_wasm_smart( - module_code_hash, - module_addr, - &dao_interface::msg::QueryMsg::QueryAuthInfo {}, - ) - .unwrap() -} - -fn get_proposal_module(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { - app.wrap() - .query_wasm_smart(module_code_hash, module_addr, &QueryMsg::ProposalModule {}) - .unwrap() -} - -fn get_deposit_info( - app: &App, - module_addr: Addr, - module_code_hash: String, - id: u64, -) -> DepositInfoResponse { - app.wrap() - .query_wasm_smart( - module_code_hash, - module_addr, - &QueryMsg::DepositInfo { proposal_id: id }, - ) - .unwrap() -} - -fn get_proposals(app: &App, module_addr: Addr, module_code_hash: String) -> ProposalListResponse { - app.wrap() - .query_wasm_smart( - module_code_hash, - module_addr, - &dps::msg::QueryMsg::ListProposals { - start_after: None, - limit: None, - }, - ) - .unwrap() -} - -fn get_latest_proposal_id(app: &App, module_contract_info: ContractInfo) -> u64 { - // Check prop was created in the main DAO - let props: ProposalListResponse = app - .wrap() - .query_wasm_smart( - module_contract_info.code_hash, - module_contract_info.address, - &dps::msg::QueryMsg::ListProposals { - start_after: None, - limit: None, - }, - ) - .unwrap(); - props.proposals[props.proposals.len() - 1].id -} - -fn update_config( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - deposit_info: Option, - open_proposal_submission: bool, -) -> Config { - app.execute_contract( - Addr::unchecked(sender), - &module_contract_info.clone(), - &ExecuteMsg::UpdateConfig { - deposit_info, - open_proposal_submission, - }, - &[], - ) - .unwrap(); - - get_config( - app, - module_contract_info.address, - module_contract_info.code_hash, - ) -} - -fn update_config_should_fail( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - deposit_info: Option, - open_proposal_submission: bool, -) -> PreProposeError { - app.execute_contract( - Addr::unchecked(sender), - &module_contract_info, - &ExecuteMsg::UpdateConfig { - deposit_info, - open_proposal_submission, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap() -} - -fn withdraw( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - denom: Option, - key: String, -) { - app.execute_contract( - Addr::unchecked(sender), - &module_contract_info, - &ExecuteMsg::Withdraw { - denom, - key: Some(key), - }, - &[], - ) - .unwrap(); -} - -fn withdraw_should_fail( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - denom: Option, -) -> PreProposeError { - app.execute_contract( - Addr::unchecked(sender), - &module_contract_info, - &ExecuteMsg::Withdraw { denom, key: None }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap() -} - -fn close_proposal( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - proposal_id: u64, -) { - app.execute_contract( - Addr::unchecked(sender), - &module_contract_info, - &dps::msg::ExecuteMsg::Close { proposal_id }, - &[], - ) - .unwrap(); -} - -fn close_proposal_wrapper( - app: &mut App, - contract_info: ContractInfo, - sender: &str, - id: u64, - _auth: Auth, -) { - close_proposal(app, contract_info, sender, id); -} - -fn execute_proposal( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - proposal_id: u64, - auth: Auth, -) { - app.execute_contract( - Addr::unchecked(sender), - &module_contract_info, - &dps::msg::ExecuteMsg::Execute { auth, proposal_id }, - &[], - ) - .unwrap(); -} - -fn approve_proposal( - app: &mut App, - module_contract_info: ContractInfo, - sender: &str, - proposal_id: u64, - auth: Auth, -) { - // Approver votes on prop - vote( - app, - module_contract_info.clone(), - sender, - proposal_id, - Vote::Yes, - auth.clone(), - ); - // Approver executes prop - execute_proposal(app, module_contract_info, sender, proposal_id, auth); -} - -enum ApprovalStatus { - Approved, - Rejected, -} - -enum EndStatus { - Passed, - Failed, -} - -enum RefundReceiver { - Proposer, - Dao, -} - -fn test_native_permutation( - end_status: EndStatus, - refund_policy: DepositRefundPolicy, - receiver: RefundReceiver, - approval_status: ApprovalStatus, -) { - let mut app = App::default(); - - // Need to instantiate this so contract addresses match with cw20 test cases - let _ = instantiate_snip20_base_default(&mut app); - - let DefaultTestSetup { - core_contract_info, - proposal_single_contract_info, - pre_propose_contract_info, - approver_core_contract_info, - pre_propose_approver, - proposal_single_approver_contract_info, - } = setup_default_test( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::Token { - denom: UncheckedDenom::Native("ujuno".to_string()), - }, - amount: Uint128::new(10), - refund_policy, - }), - false, - ); - - let query_auth = query_query_auth( - &app, - core_contract_info.address.clone(), - core_contract_info.code_hash.clone(), - ); - let viewing_key = create_viewing_key( - &mut app, - ContractInfo { - address: query_auth.addr, - code_hash: query_auth.code_hash, - }, - "ekez", - ); - - mint_natives(&mut app, "ekez", coins(10, "ujuno")); - let _pre_propose_id = make_pre_proposal( - &mut app, - pre_propose_contract_info, - "ekez", - &coins(10, "ujuno"), - Auth::ViewingKey { - key: viewing_key.clone(), - address: "ekez".into(), - }, - ); - - // Check no props created on main DAO yet - let props = get_proposals( - &app, - proposal_single_contract_info.address.clone(), - proposal_single_contract_info.code_hash.clone(), - ); - assert_eq!(props.proposals.len(), 0); - - // Make sure it went away. - let balance = get_balance_native(&app, "ekez", "ujuno"); - assert_eq!(balance, Uint128::zero()); - - // Approver approves or rejects proposal - match approval_status { - ApprovalStatus::Approved => { - // Get approver proposal id - let id = get_latest_proposal_id(&app, proposal_single_approver_contract_info.clone()); - - // Approver votes on prop - vote( - &mut app, - proposal_single_approver_contract_info.clone(), - "ekez", - id, - Vote::Yes, - Auth::ViewingKey { - key: viewing_key.clone(), - address: "ekez".into(), - }, - ); - // Approver executes prop - execute_proposal( - &mut app, - proposal_single_approver_contract_info, - "ekez", - id, - Auth::ViewingKey { - key: viewing_key.clone(), - address: "ekez".into(), - }, - ); - - // Check prop was created in the main DAO - let id = get_latest_proposal_id(&app, proposal_single_contract_info.clone()); - let props = get_proposals( - &app, - proposal_single_contract_info.address.clone(), - proposal_single_contract_info.code_hash.clone(), - ); - assert_eq!(props.proposals.len(), 1); - - // Voting happens on newly created proposal - #[allow(clippy::type_complexity)] - let (position, expected_status, trigger_refund): ( - _, - _, - fn(&mut App, ContractInfo, &str, u64, Auth) -> (), - ) = match end_status { - EndStatus::Passed => (Vote::Yes, Status::Passed, execute_proposal), - EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal_wrapper), - }; - let new_status = vote( - &mut app, - proposal_single_contract_info.clone(), - "ekez", - id, - position, - Auth::ViewingKey { - key: viewing_key.clone(), - address: "ekez".into(), - }, - ); - assert_eq!(new_status, expected_status); - - // Close or execute the proposal to trigger a refund. - trigger_refund( - &mut app, - proposal_single_contract_info, - "ekez", - id, - Auth::ViewingKey { - key: viewing_key.clone(), - address: "ekez".into(), - }, - ); - } - ApprovalStatus::Rejected => { - // Approver votes on prop - // No proposal is created so there is no voting - vote( - &mut app, - proposal_single_approver_contract_info.clone(), - "ekez", - 1, - Vote::No, - Auth::ViewingKey { - key: viewing_key.clone(), - address: "ekez".into(), - }, - ); - // Approver executes prop - close_proposal(&mut app, proposal_single_approver_contract_info, "ekez", 1); - - // No prop created - let props = get_proposals( - &app, - proposal_single_contract_info.address, - proposal_single_contract_info.code_hash, - ); - assert_eq!(props.proposals.len(), 0); - } - }; - - let (dao_expected, proposer_expected) = match receiver { - RefundReceiver::Proposer => (0, 10), - RefundReceiver::Dao => (10, 0), - }; - - let proposer_balance = get_balance_native(&app, "ekez", "ujuno"); - let dao_balance = get_balance_native(&app, core_contract_info.address.as_str(), "ujuno"); - assert_eq!(proposer_expected, proposer_balance.u128()); - assert_eq!(dao_expected, dao_balance.u128()) -} - -// fn test_cw20_permutation( -// end_status: EndStatus, -// refund_policy: DepositRefundPolicy, -// receiver: RefundReceiver, -// approval_status: ApprovalStatus, -// ) { -// let mut app = App::default(); - -// let cw20_address = instantiate_cw20_base_default(&mut app); - -// let DefaultTestSetup { -// core_addr, -// proposal_single, -// pre_propose, -// approver_core_addr: _, -// proposal_single_approver, -// pre_propose_approver: _, -// } = setup_default_test( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Cw20(cw20_address.to_string()), -// }, -// amount: Uint128::new(10), -// refund_policy, -// }), -// false, +// use cosmwasm_std::{ +// coins, from_binary, to_binary, Addr, Binary, Coin, ContractInfo, Empty, Uint128, +// }; +// use cw_denom::UncheckedDenom; +// use dao_voting_cw4::msg::GroupContract; +// use dps::query::{ProposalListResponse, ProposalResponse}; +// use secret_cw2::ContractVersion; +// use secret_multi_test::{ +// App, BankSudo, Contract, ContractInstantiationInfo, ContractWrapper, Executor, +// }; + +// use dao_interface::state::{Admin, ModuleInstantiateInfo}; +// use dao_interface::state::{AnyContractInfo, ProposalModule}; +// use dao_pre_propose_approval_single::{ +// msg::{ +// ExecuteExt, ExecuteMsg, InstantiateExt, InstantiateMsg, ProposeMessage, QueryExt, QueryMsg, +// }, +// state::Proposal, +// }; +// use dao_pre_propose_base::{error::PreProposeError, msg::DepositInfoResponse, state::Config}; +// use dao_proposal_single as dps; +// use dao_voting::{ +// deposit::{CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, +// pre_propose::{PreProposeInfo, ProposalCreationPolicy}, +// status::Status, +// threshold::{PercentageThreshold, Threshold}, +// voting::Vote, +// }; +// use shade_protocol::basic_staking::Auth; +// use shade_protocol::utils::asset::RawContract; +// use snip20_reference_impl::msg::{InitConfig, InitialBalance}; + +// use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; +// use crate::msg::InstantiateMsg as ApproverInstantiateMsg; +// use crate::msg::{ +// ExecuteExt as ApproverExecuteExt, ExecuteMsg as ApproverExecuteMsg, +// QueryExt as ApproverQueryExt, QueryMsg as ApproverQueryMsg, +// }; + +// // The approver dao contract is the 6th contract instantiated +// const APPROVER: &str = "contract6"; +// const CREATOR_ADDR: &str = "creator"; + +// pub fn cw4_group_contract() -> Box> { +// let contract = ContractWrapper::new( +// cw4_group::contract::execute, +// cw4_group::contract::instantiate, +// cw4_group::contract::query, // ); - -// increase_allowance( -// &mut app, -// "ekez", -// &pre_propose, -// cw20_address.clone(), -// Uint128::new(10), -// ); -// let _pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); - -// // Check no props created on main DAO yet -// let props = get_proposals(&app, proposal_single.clone()); -// assert_eq!(props.proposals.len(), 0); - -// // Make sure it went await. -// let balance = get_balance_cw20(&app, cw20_address.clone(), "ekez"); -// assert_eq!(balance, Uint128::zero()); - -// // Approver approves or rejects proposal -// match approval_status { -// ApprovalStatus::Approved => { -// // Get approver proposal id -// let id = get_latest_proposal_id(&app, proposal_single_approver.clone()); - -// // Approver votes on prop -// vote( -// &mut app, -// proposal_single_approver.clone(), -// "ekez", -// id, -// Vote::Yes, -// ); -// // Approver executes prop -// execute_proposal(&mut app, proposal_single_approver, "ekez", id); - -// // Check prop was created in the main DAO -// let id = get_latest_proposal_id(&app, proposal_single.clone()); -// let props = get_proposals(&app, proposal_single.clone()); -// assert_eq!(props.proposals.len(), 1); - -// // Voting happens on newly created proposal -// #[allow(clippy::type_complexity)] -// let (position, expected_status, trigger_refund): ( -// _, -// _, -// fn(&mut App, Addr, &str, u64) -> (), -// ) = match end_status { -// EndStatus::Passed => (Vote::Yes, Status::Passed, execute_proposal), -// EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal), -// }; -// let new_status = vote(&mut app, proposal_single.clone(), "ekez", id, position); -// assert_eq!(new_status, expected_status); - -// // Close or execute the proposal to trigger a refund. -// trigger_refund(&mut app, proposal_single, "ekez", id); -// } -// ApprovalStatus::Rejected => { -// // Approver votes on prop -// // No proposal is created so there is no voting -// vote( -// &mut app, -// proposal_single_approver.clone(), -// "ekez", -// 1, -// Vote::No, -// ); -// // Approver executes prop -// close_proposal(&mut app, proposal_single_approver, "ekez", 1); - -// // No prop created -// let props = get_proposals(&app, proposal_single); -// assert_eq!(props.proposals.len(), 0); -// } -// }; - -// let (dao_expected, proposer_expected) = match receiver { -// RefundReceiver::Proposer => (0, 10), -// RefundReceiver::Dao => (10, 0), -// }; - -// let proposer_balance = get_balance_cw20(&app, &cw20_address, "ekez"); -// let dao_balance = get_balance_cw20(&app, &cw20_address, core_addr); -// assert_eq!(proposer_expected, proposer_balance.u128()); -// assert_eq!(dao_expected, dao_balance.u128()) +// Box::new(contract) // } -#[test] -fn test_native_failed_always_refund() { - test_native_permutation( - EndStatus::Failed, - DepositRefundPolicy::Always, - RefundReceiver::Proposer, - ApprovalStatus::Approved, - ) -} - -// #[test] -// fn test_native_rejected_always_refund() { -// test_native_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::Always, -// RefundReceiver::Proposer, -// ApprovalStatus::Rejected, +// pub fn dao_dao_contract() -> Box> { +// let contract = ContractWrapper::new( +// dao_dao_core::contract::execute, +// dao_dao_core::contract::instantiate, +// dao_dao_core::contract::query, // ) +// .with_reply(dao_dao_core::contract::reply) +// .with_migrate(dao_dao_core::contract::migrate); +// Box::new(contract) // } -// #[test] -// fn test_cw20_failed_always_refund() { -// test_cw20_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::Always, -// RefundReceiver::Proposer, -// ApprovalStatus::Approved, +// pub fn dao_voting_cw4_contract() -> Box> { +// let contract = ContractWrapper::new( +// dao_voting_cw4::contract::execute, +// dao_voting_cw4::contract::instantiate, +// dao_voting_cw4::contract::query, // ) +// .with_reply(dao_voting_cw4::contract::reply); +// Box::new(contract) // } -// #[test] -// fn test_cw20_rejected_always_refund() { -// test_cw20_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::Always, -// RefundReceiver::Proposer, -// ApprovalStatus::Rejected, -// ) +// pub fn snip721_base_contract() -> Box> { +// let contract = ContractWrapper::new( +// snip721_reference_impl::contract::execute, +// snip721_reference_impl::contract::instantiate, +// snip721_reference_impl::contract::query, +// ); +// Box::new(contract) // } -// #[test] -// fn test_native_passed_always_refund() { -// test_native_permutation( -// EndStatus::Passed, -// DepositRefundPolicy::Always, -// RefundReceiver::Proposer, -// ApprovalStatus::Approved, -// ) +// pub fn query_auth_contract() -> Box> { +// let contract = ContractWrapper::new( +// query_auth::contract::execute, +// query_auth::contract::instantiate, +// query_auth::contract::query, +// ); +// Box::new(contract) // } -// #[test] -// fn test_cw20_passed_always_refund() { -// test_cw20_permutation( -// EndStatus::Passed, -// DepositRefundPolicy::Always, -// RefundReceiver::Proposer, -// ApprovalStatus::Approved, -// ) -// } +// pub fn instantiate_with_cw4_groups_governance( +// app: &mut App, +// core_info: ContractInstantiationInfo, +// core_code_id: u64, +// core_code_hash: String, +// proposal_module_instantiate: Binary, +// initial_weights: Option>, +// ) -> ContractInfo { +// let cw4_info = app.store_code(cw4_group_contract()); +// let votemod_info = app.store_code(dao_voting_cw4_contract()); +// let snip20_info = app.store_code(snip20_base_contract()); +// let snip721_info = app.store_code(snip721_base_contract()); +// let query_auth = app.store_code(query_auth_contract()); +// let initial_weights = initial_weights.unwrap_or_default(); + +// // Remove duplicates so that we can test duplicate voting. +// let initial_weights: Vec = { +// let mut already_seen = vec![]; +// initial_weights +// .into_iter() +// .filter(|InitialBalance { address, .. }| { +// if already_seen.contains(address) { +// false +// } else { +// already_seen.push(address.clone()); +// true +// } +// }) +// .map(|InitialBalance { address, amount }| cw4::Member { +// addr: address, +// weight: amount.u128() as u64, +// }) +// .collect() +// }; -// #[test] -// fn test_native_passed_never_refund() { -// test_native_permutation( -// EndStatus::Passed, -// DepositRefundPolicy::Never, -// RefundReceiver::Dao, -// ApprovalStatus::Approved, -// ) -// } +// let governance_instantiate = dao_interface::msg::InstantiateMsg { +// dao_uri: None, +// admin: None, +// name: "DAO DAO".to_string(), +// description: "A DAO that builds DAOs".to_string(), +// image_url: None, +// voting_module_instantiate_info: ModuleInstantiateInfo { +// code_id: votemod_info.code_id, +// code_hash: votemod_info.code_hash, +// msg: to_binary(&dao_voting_cw4::msg::InstantiateMsg { +// group_contract: GroupContract::New { +// cw4_group_code_id: cw4_info.code_id, +// cw4_group_code_hash: cw4_info.code_hash, +// initial_members: initial_weights, +// query_auth: None, +// }, +// dao_code_hash: core_info.code_hash.clone(), +// }) +// .unwrap(), +// admin: Some(Admin::CoreModule {}), +// funds: vec![], +// label: "DAO DAO voting module".to_string(), +// }, +// proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { +// code_id: core_code_id, +// code_hash: core_code_hash, +// msg: proposal_module_instantiate, +// admin: Some(Admin::CoreModule {}), +// funds: vec![], +// label: "DAO DAO governance module".to_string(), +// }], +// initial_items: None, +// query_auth_code_id: query_auth.code_id, +// query_auth_code_hash: query_auth.code_hash, +// prng_seed: "seed".into(), +// snip20_code_hash: snip20_info.code_hash, +// snip721_code_hash: snip721_info.code_hash, +// }; -// #[test] -// fn test_cw20_passed_never_refund() { -// test_cw20_permutation( -// EndStatus::Passed, -// DepositRefundPolicy::Never, -// RefundReceiver::Dao, -// ApprovalStatus::Approved, -// ) -// } +// let info = app +// .instantiate_contract( +// core_info.clone(), +// Addr::unchecked(CREATOR_ADDR), +// &governance_instantiate, +// &[], +// "DAO DAO", +// None, +// ) +// .unwrap(); -// #[test] -// fn test_native_failed_never_refund() { -// test_native_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::Never, -// RefundReceiver::Dao, -// ApprovalStatus::Approved, -// ) -// } +// // Update the block so that weights appear. +// app.update_block(|block| block.height += 1); -// #[test] -// fn test_native_rejected_never_refund() { -// test_native_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::Never, -// RefundReceiver::Dao, -// ApprovalStatus::Rejected, -// ) +// info // } -// #[test] -// fn test_cw20_failed_never_refund() { -// test_cw20_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::Never, -// RefundReceiver::Dao, -// ApprovalStatus::Approved, +// fn cw_dao_proposal_single_contract() -> Box> { +// let contract = ContractWrapper::new( +// dps::contract::execute, +// dps::contract::instantiate, +// dps::contract::query, // ) +// .with_migrate(dps::contract::migrate) +// .with_reply(dps::contract::reply); +// Box::new(contract) // } -// #[test] -// fn test_cw20_rejected_never_refund() { -// test_cw20_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::Never, -// RefundReceiver::Dao, -// ApprovalStatus::Rejected, -// ) +// fn cw_pre_propose_base_proposal_single() -> Box> { +// let contract = ContractWrapper::new( +// dao_pre_propose_approval_single::contract::execute, +// dao_pre_propose_approval_single::contract::instantiate, +// dao_pre_propose_approval_single::contract::query, +// ); +// Box::new(contract) // } -// #[test] -// fn test_native_passed_passed_refund() { -// test_native_permutation( -// EndStatus::Passed, -// DepositRefundPolicy::OnlyPassed, -// RefundReceiver::Proposer, -// ApprovalStatus::Approved, -// ) +// fn snip20_base_contract() -> Box> { +// let contract = ContractWrapper::new( +// snip20_reference_impl::contract::execute, +// snip20_reference_impl::contract::instantiate, +// snip20_reference_impl::contract::query, +// ); +// Box::new(contract) +// } +// fn pre_propose_approver_contract() -> Box> { +// let contract = ContractWrapper::new( +// crate::contract::execute, +// crate::contract::instantiate, +// crate::contract::query, +// ); +// Box::new(contract) // } -// #[test] -// fn test_cw20_passed_passed_refund() { -// test_cw20_permutation( -// EndStatus::Passed, -// DepositRefundPolicy::OnlyPassed, -// RefundReceiver::Proposer, -// ApprovalStatus::Approved, -// ) +// fn get_proposal_module_approval_single_instantiate( +// app: &mut App, +// deposit_info: Option, +// open_proposal_submission: bool, +// proposal_module_code_hash: String, +// query_auth: ContractInfo, +// dao_code_hash: String, +// ) -> dps::msg::InstantiateMsg { +// let pre_propose_info = app.store_code(cw_pre_propose_base_proposal_single()); + +// dps::msg::InstantiateMsg { +// threshold: Threshold::AbsolutePercentage { +// percentage: PercentageThreshold::Majority {}, +// }, +// max_voting_period: secret_utils::Duration::Time(86400), +// min_voting_period: None, +// only_members_execute: false, +// allow_revoting: false, +// pre_propose_info: PreProposeInfo::ModuleMayPropose { +// info: ModuleInstantiateInfo { +// code_id: pre_propose_info.code_id, +// code_hash: pre_propose_info.code_hash.clone(), +// msg: to_binary(&InstantiateMsg { +// deposit_info, +// open_proposal_submission, +// extension: InstantiateExt { +// approver: APPROVER.to_string(), +// }, +// proposal_module_code_hash, +// }) +// .unwrap(), +// admin: Some(Admin::CoreModule {}), +// funds: vec![], +// label: "baby's first pre-propose module, needs supervision".to_string(), +// }, +// }, +// close_proposal_on_execution_failure: false, +// veto: None, +// dao_code_hash, +// query_auth: Some(RawContract::new( +// &query_auth.address.into_string(), +// &query_auth.code_hash, +// )), +// } // } -// #[test] -// fn test_native_failed_passed_refund() { -// test_native_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::OnlyPassed, -// RefundReceiver::Dao, -// ApprovalStatus::Approved, -// ) +// fn get_proposal_module_approver_instantiate( +// app: &mut App, +// _deposit_info: Option, +// _open_proposal_submission: bool, +// pre_propose_approval_contract_info: ContractInfo, +// proposal_module_code_hash: String, +// query_auth: ContractInfo, +// dao_code_hash: String, +// ) -> dps::msg::InstantiateMsg { +// let pre_propose_info = app.store_code(pre_propose_approver_contract()); + +// dps::msg::InstantiateMsg { +// threshold: Threshold::AbsolutePercentage { +// percentage: PercentageThreshold::Majority {}, +// }, +// max_voting_period: secret_utils::Duration::Time(86400), +// min_voting_period: None, +// only_members_execute: false, +// allow_revoting: false, +// pre_propose_info: PreProposeInfo::ModuleMayPropose { +// info: ModuleInstantiateInfo { +// code_id: pre_propose_info.code_id, +// code_hash: pre_propose_info.code_hash, +// msg: to_binary(&ApproverInstantiateMsg { +// pre_propose_approval_contract: pre_propose_approval_contract_info +// .address +// .into(), +// pre_propose_approval_contract_code_hash: pre_propose_approval_contract_info +// .code_hash, +// proposal_module_code_hash, +// }) +// .unwrap(), +// admin: Some(Admin::CoreModule {}), +// funds: vec![], +// label: "approver module maka naka shaka".to_string(), +// }, +// }, +// close_proposal_on_execution_failure: false, +// veto: None, +// dao_code_hash, +// query_auth: Some(RawContract::new( +// &query_auth.address.into_string(), +// &query_auth.code_hash, +// )), +// } // } -// #[test] -// fn test_native_rejected_passed_refund() { -// test_native_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::OnlyPassed, -// RefundReceiver::Dao, -// ApprovalStatus::Rejected, +// fn instantiate_snip20_base_default(app: &mut App) -> ContractInfo { +// let snip20_info = app.store_code(snip20_base_contract()); +// let snip20_instantiate = snip20_reference_impl::msg::InstantiateMsg { +// name: "snip20 token".to_string(), +// symbol: "sniptwenty".to_string(), +// decimals: 6, +// initial_balances: Some(vec![InitialBalance { +// address: "ekez".to_string(), +// amount: Uint128::new(10), +// }]), +// admin: None, +// prng_seed: to_binary("data").unwrap(), +// config: Some(InitConfig { +// public_total_supply: Some(true), +// enable_deposit: Some(true), +// enable_redeem: Some(true), +// enable_mint: Some(true), +// enable_burn: Some(true), +// can_modify_denoms: Some(true), +// }), +// supported_denoms: None, +// }; +// app.instantiate_contract( +// snip20_info, +// Addr::unchecked("ekez"), +// &snip20_instantiate, +// &[], +// "snip20-base", +// None, // ) +// .unwrap() // } -// #[test] -// fn test_cw20_failed_passed_refund() { -// test_cw20_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::OnlyPassed, -// RefundReceiver::Dao, -// ApprovalStatus::Approved, +// fn instantiate_query_auth(app: &mut App) -> ContractInfo { +// let query_auth_info = app.store_code(query_auth_contract()); +// let msg = shade_protocol::contract_interfaces::query_auth::InstantiateMsg { +// admin_auth: shade_protocol::Contract { +// address: Addr::unchecked("admin_contract"), +// code_hash: "code_hash".to_string(), +// }, +// prng_seed: to_binary("seed").unwrap(), +// }; + +// app.instantiate_contract( +// query_auth_info, +// Addr::unchecked(CREATOR_ADDR), +// &msg, +// &[], +// "query_auth", +// None, // ) +// .unwrap() // } -// #[test] -// fn test_cw20_rejected_passed_refund() { -// test_cw20_permutation( -// EndStatus::Failed, -// DepositRefundPolicy::OnlyPassed, -// RefundReceiver::Dao, -// ApprovalStatus::Rejected, -// ) +// fn create_viewing_key(app: &mut App, contract_info: ContractInfo, sender: &str) -> String { +// let msg = shade_protocol::contract_interfaces::query_auth::ExecuteMsg::CreateViewingKey { +// entropy: "entropy".to_string(), +// padding: None, +// }; +// let res = app +// .execute_contract(Addr::unchecked(sender), &contract_info, &msg, &[]) +// .unwrap(); +// let mut viewing_key = String::new(); +// let data: shade_protocol::contract_interfaces::query_auth::ExecuteAnswer = +// from_binary(&res.data.unwrap()).unwrap(); +// if let shade_protocol::contract_interfaces::query_auth::ExecuteAnswer::CreateViewingKey { +// key, +// } = data +// { +// viewing_key = key; +// }; +// viewing_key // } -// // See: -// #[test] -// fn test_multiple_open_proposals() { -// let mut app = App::default(); +// fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: &str) -> String { +// let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { +// entropy: "entropy".to_string(), +// padding: None, +// }; +// let res = app +// .execute_contract(Addr::unchecked(addr), &contract_info, &msg, &[]) +// .unwrap(); +// let mut viewing_key = String::new(); +// let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); +// if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { +// viewing_key = key; +// }; +// viewing_key +// } -// // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); +// struct DefaultTestSetup { +// core_contract_info: ContractInfo, +// proposal_single_contract_info: ContractInfo, +// pre_propose_contract_info: ContractInfo, +// approver_core_contract_info: ContractInfo, +// pre_propose_approver: ContractInfo, +// proposal_single_approver_contract_info: ContractInfo, +// } -// let DefaultTestSetup { -// core_addr: _, -// proposal_single, -// pre_propose, -// approver_core_addr: _, -// proposal_single_approver, -// pre_propose_approver: _, -// } = setup_default_test( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Native("ujuno".to_string()), +// fn setup_default_test( +// app: &mut App, +// deposit_info: Option, +// open_proposal_submission: bool, +// ) -> DefaultTestSetup { +// let dps_info = app.store_code(cw_dao_proposal_single_contract()); +// let query_auth = instantiate_query_auth(app); +// let core_info = app.store_code(dao_dao_contract()); +// println!("Dao code hash : {}", core_info.code_hash); + +// // Instantiate SubDAO with pre-propose-approval-single +// let proposal_module_instantiate = get_proposal_module_approval_single_instantiate( +// app, +// deposit_info.clone(), +// open_proposal_submission, +// dps_info.code_hash.clone(), +// query_auth.clone(), +// core_info.code_hash.clone(), +// ); +// let core_contract_info = instantiate_with_cw4_groups_governance( +// app, +// core_info.clone(), +// dps_info.code_id, +// dps_info.code_hash.clone(), +// to_binary(&proposal_module_instantiate).unwrap(), +// Some(vec![ +// InitialBalance { +// address: "ekez".to_string(), +// amount: Uint128::new(9), // }, -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Always, -// }), -// false, +// InitialBalance { +// address: "keze".to_string(), +// amount: Uint128::new(8), +// }, +// ]), // ); +// let proposal_modules: Vec = app +// .wrap() +// .query_wasm_smart( +// core_contract_info.code_hash.clone(), +// core_contract_info.address.clone(), +// &dao_interface::msg::QueryMsg::ProposalModules { +// start_after: None, +// limit: None, +// }, +// ) +// .unwrap(); -// mint_natives(&mut app, "ekez", coins(20, "ujuno")); -// let _first_pre_propose_id = -// make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(10, balance.u128()); - -// // Approver DAO approves prop, balance remains the same -// let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// approve_proposal( -// &mut app, -// proposal_single_approver.clone(), -// "ekez", -// approver_prop_id, +// // Make sure things were set up correctly. +// assert_eq!(proposal_modules.len(), 1); +// let proposal_single_address = proposal_modules.clone().into_iter().next().unwrap().address; +// let proposal_single_code_hash = proposal_modules.into_iter().next().unwrap().code_hash; +// let proposal_creation_policy = app +// .wrap() +// .query_wasm_smart( +// proposal_single_code_hash.clone(), +// proposal_single_address.clone(), +// &dps::msg::QueryMsg::ProposalCreationPolicy {}, +// ) +// .unwrap(); +// let pre_propose = match proposal_creation_policy { +// ProposalCreationPolicy::Module { addr, code_hash } => (addr, code_hash), +// _ => panic!("expected a module for the proposal creation policy"), +// }; +// assert_eq!( +// AnyContractInfo { +// addr: proposal_single_address.clone(), +// code_hash: proposal_single_code_hash.clone() +// }, +// get_proposal_module(app, pre_propose.0.clone(), pre_propose.1.clone()) // ); -// let first_id = get_latest_proposal_id(&app, proposal_single.clone()); -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(10, balance.u128()); - -// let _second_pre_propose_id = -// make_pre_proposal(&mut app, pre_propose, "ekez", &coins(10, "ujuno")); -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(0, balance.u128()); - -// // Approver DAO votes to approves, balance remains the same -// let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// approve_proposal(&mut app, proposal_single_approver, "ekez", approver_prop_id); -// let second_id = get_latest_proposal_id(&app, proposal_single.clone()); -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(0, balance.u128()); - -// // Finish up the first proposal. -// let new_status = vote( -// &mut app, -// proposal_single.clone(), -// "ekez", -// first_id, -// Vote::Yes, +// assert_eq!( +// AnyContractInfo { +// addr: core_contract_info.address.clone(), +// code_hash: core_contract_info.code_hash.clone() +// }, +// get_dao(app, pre_propose.0.clone(), pre_propose.1.clone()) // ); -// assert_eq!(Status::Passed, new_status); - -// // Still zero. -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(0, balance.u128()); -// execute_proposal(&mut app, proposal_single.clone(), "ekez", first_id); - -// // First proposal refunded. -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(10, balance.u128()); - -// // Finish up the second proposal. -// let new_status = vote( -// &mut app, -// proposal_single.clone(), -// "ekez", -// second_id, -// Vote::No, +// println!("Yha tk phuch gya"); +// // Instantiate SubDAO with pre-propose-approver +// let proposal_module_instantiate = get_proposal_module_approver_instantiate( +// app, +// deposit_info, +// open_proposal_submission, +// ContractInfo { +// address: pre_propose.0.clone(), +// code_hash: pre_propose.1.clone(), +// }, +// dps_info.code_hash.clone(), +// query_auth.clone(), +// core_contract_info.code_hash.clone(), // ); -// assert_eq!(Status::Rejected, new_status); - -// // Still zero. -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(10, balance.u128()); - -// close_proposal(&mut app, proposal_single, "ekez", second_id); - -// // All deposits have been refunded. -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(20, balance.u128()); -// } -// #[test] -// fn test_set_version() { -// let mut app = App::default(); +// let approver_core_contract_info = instantiate_with_cw4_groups_governance( +// app, +// core_info, +// dps_info.code_id, +// dps_info.code_hash, +// to_binary(&proposal_module_instantiate).unwrap(), +// Some(vec![ +// InitialBalance { +// address: "ekez".to_string(), +// amount: Uint128::new(9), +// }, +// InitialBalance { +// address: "keze".to_string(), +// amount: Uint128::new(8), +// }, +// ]), +// ); -// // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); +// println!("YHa nhi phuch [paya"); -// let DefaultTestSetup { -// core_addr: _, -// proposal_single: _, -// pre_propose: _, -// approver_core_addr: _, -// proposal_single_approver: _, -// pre_propose_approver, -// } = setup_default_test( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Native("ujuno".to_string()), +// let proposal_modules: Vec = app +// .wrap() +// .query_wasm_smart( +// approver_core_contract_info.code_hash.clone(), +// approver_core_contract_info.address.clone(), +// &dao_interface::msg::QueryMsg::ProposalModules { +// start_after: None, +// limit: None, // }, -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Always, -// }), -// false, -// ); +// ) +// .unwrap(); -// let info: ContractVersion = from_binary( -// app.wrap() -// .query_wasm_raw(pre_propose_approver, "contract_info".as_bytes()) -// .unwrap() -// .unwrap(), -// ) -// .unwrap(); +// // Make sure things were set up correctly. +// assert_eq!(proposal_modules.len(), 1); +// let proposal_single_approver_addr = +// proposal_modules.clone().into_iter().next().unwrap().address; +// let proposal_single_approver_code_hash = proposal_modules +// .clone() +// .into_iter() +// .next() +// .unwrap() +// .code_hash; + +// let proposal_creation_policy = app +// .wrap() +// .query_wasm_smart( +// proposal_single_approver_code_hash.clone(), +// proposal_single_approver_addr.clone(), +// &dps::msg::QueryMsg::ProposalCreationPolicy {}, +// ) +// .unwrap(); +// let pre_propose_approver = match proposal_creation_policy { +// ProposalCreationPolicy::Module { addr, code_hash } => (addr, code_hash), +// _ => panic!("expected a module for the proposal creation policy"), +// }; // assert_eq!( -// ContractVersion { -// contract: CONTRACT_NAME.to_string(), -// version: CONTRACT_VERSION.to_string() +// AnyContractInfo { +// addr: proposal_single_approver_addr.clone(), +// code_hash: proposal_single_approver_code_hash.clone(), // }, -// info -// ) -// } - -// #[test] -// fn test_permissions() { -// let mut app = App::default(); +// get_proposal_module( +// app, +// pre_propose_approver.0.clone(), +// pre_propose_approver.1.clone() +// ) +// ); +// assert_eq!( +// AnyContractInfo { +// addr: approver_core_contract_info.address.clone(), +// code_hash: approver_core_contract_info.code_hash.clone(), +// }, +// get_dao( +// app, +// pre_propose_approver.0.clone(), +// pre_propose_approver.1.clone() +// ) +// ); -// // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); +// DefaultTestSetup { +// core_contract_info, +// proposal_single_contract_info: ContractInfo { +// address: proposal_single_address, +// code_hash: proposal_single_code_hash, +// }, +// pre_propose_contract_info: ContractInfo { +// address: pre_propose.0, +// code_hash: pre_propose.1, +// }, +// approver_core_contract_info, +// proposal_single_approver_contract_info: ContractInfo { +// address: proposal_single_approver_addr, +// code_hash: proposal_single_approver_code_hash, +// }, +// pre_propose_approver: ContractInfo { +// address: pre_propose_approver.0, +// code_hash: pre_propose_approver.1, +// }, +// } +// } -// let DefaultTestSetup { -// core_addr, -// proposal_single: _, -// pre_propose, -// approver_core_addr: _, -// proposal_single_approver: _, -// pre_propose_approver: _, -// } = setup_default_test( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Native("ujuno".to_string()), +// fn make_pre_proposal( +// app: &mut App, +// pre_propose_contract_info: ContractInfo, +// proposer: &str, +// funds: &[Coin], +// auth: Auth, +// ) -> u64 { +// app.execute_contract( +// Addr::unchecked(proposer), +// &pre_propose_contract_info.clone(), +// &ExecuteMsg::Propose { +// auth, +// msg: ProposeMessage::Propose { +// title: "title".to_string(), +// description: "description".to_string(), +// msgs: vec![], // }, -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Always, -// }), -// false, // no open proposal submission. -// ); +// }, +// funds, +// ) +// .unwrap(); -// let err: PreProposeError = app -// .execute_contract( -// core_addr, -// pre_propose.clone(), -// &ExecuteMsg::ProposalCompletedHook { -// proposal_id: 1, -// new_status: Status::Closed, -// }, -// &[], -// ) -// .unwrap_err() -// .downcast() -// .unwrap(); -// assert_eq!(err, PreProposeError::NotModule {}); - -// // Non-members may not propose when open_propose_submission is -// // disabled. -// let err: PreProposeError = app -// .execute_contract( -// Addr::unchecked("nonmember"), -// pre_propose, -// &ExecuteMsg::Propose { -// msg: ProposeMessage::Propose { -// title: "I would like to join the DAO".to_string(), -// description: "though, I am currently not a member.".to_string(), -// msgs: vec![], +// // Query for pending proposal and return latest id +// let mut pending: Vec = app +// .wrap() +// .query_wasm_smart( +// pre_propose_contract_info.code_hash, +// pre_propose_contract_info.address, +// &QueryMsg::QueryExtension { +// msg: QueryExt::PendingProposals { +// start_after: None, +// limit: None, // }, // }, -// &[], // ) -// .unwrap_err() -// .downcast() // .unwrap(); -// assert_eq!(err, PreProposeError::NotMember {}); -// } - -// #[test] -// fn test_approval_and_rejection_permissions() { -// let mut app = App::default(); -// // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); +// println!("pending_proposal: {:?}", pending); +// // Return last item in list, id is first element of tuple +// pending.pop().unwrap().approval_id +// } -// let DefaultTestSetup { -// core_addr: _, -// proposal_single: _, -// pre_propose, -// approver_core_addr: _, -// proposal_single_approver: _, -// pre_propose_approver: _, -// } = setup_default_test( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Native("ujuno".to_string()), -// }, -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Always, -// }), -// true, // yes, open proposal submission. -// ); +// fn mint_natives(app: &mut App, receiver: &str, coins: Vec) { +// // Mint some ekez tokens for ekez so we can pay the deposit. +// app.sudo(secret_multi_test::SudoMsg::Bank(BankSudo::Mint { +// to_address: receiver.to_string(), +// amount: coins, +// })) +// .unwrap(); +// } -// // Non-member proposes. -// mint_natives(&mut app, "nonmember", coins(10, "ujuno")); -// let pre_propose_id = make_pre_proposal( -// &mut app, -// pre_propose.clone(), -// "nonmember", -// &coins(10, "ujuno"), -// ); +// fn increase_allowance( +// app: &mut App, +// sender: &str, +// receiver: &Addr, +// snip20_contract_info: ContractInfo, +// amount: Uint128, +// ) { +// app.execute_contract( +// Addr::unchecked(sender), +// &snip20_contract_info, +// &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { +// spender: receiver.to_string(), +// amount, +// expiration: None, +// padding: None, +// }, +// &[], +// ) +// .unwrap(); +// } -// // Only approver can propose -// let err: PreProposeError = app -// .execute_contract( -// Addr::unchecked("nonmember"), -// pre_propose.clone(), -// &ExecuteMsg::Extension { -// msg: ExecuteExt::Approve { id: pre_propose_id }, -// }, -// &[], -// ) -// .unwrap_err() -// .downcast() -// .unwrap(); -// assert_eq!(err, PreProposeError::Unauthorized {}); - -// // Only approver can propose -// let err: PreProposeError = app -// .execute_contract( -// Addr::unchecked("nonmember"), -// pre_propose, -// &ExecuteMsg::Extension { -// msg: ExecuteExt::Reject { id: pre_propose_id }, -// }, -// &[], -// ) -// .unwrap_err() -// .downcast() +// fn get_balance_snip20, C: Into, U: Into, K: Into>( +// app: &App, +// contract_addr: T, +// code_hash: C, +// address: U, +// key: K, +// ) -> Uint128 { +// let msg = snip20_reference_impl::msg::QueryMsg::Balance { +// address: address.into(), +// key: key.into(), +// }; +// let result: snip20_reference_impl::msg::QueryAnswer = app +// .wrap() +// .query_wasm_smart(code_hash, contract_addr, &msg) // .unwrap(); -// assert_eq!(err, PreProposeError::Unauthorized {}); +// let mut balance = Uint128::zero(); +// if let snip20_reference_impl::msg::QueryAnswer::Balance { amount } = result { +// balance = amount; +// } +// balance // } -// #[test] -// fn test_propose_open_proposal_submission() { -// let mut app = App::default(); - -// // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); - -// let DefaultTestSetup { -// core_addr: _, -// proposal_single, -// pre_propose, -// approver_core_addr: _, -// proposal_single_approver, -// pre_propose_approver, -// } = setup_default_test( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Native("ujuno".to_string()), -// }, -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Always, -// }), -// true, // yes, open proposal submission. -// ); +// fn get_balance_native(app: &App, who: &str, denom: &str) -> Uint128 { +// let res = app.wrap().query_balance(who, denom).unwrap(); +// res.amount +// } -// // Non-member proposes. -// mint_natives(&mut app, "nonmember", coins(10, "ujuno")); -// let pre_propose_id = make_pre_proposal(&mut app, pre_propose, "nonmember", &coins(10, "ujuno")); +// fn vote( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// id: u64, +// position: Vote, +// auth: Auth, +// ) -> Status { +// app.execute_contract( +// Addr::unchecked(sender), +// &module_contract_info.clone(), +// &dps::msg::ExecuteMsg::Vote { +// proposal_id: id, +// vote: position, +// rationale: None, +// auth, +// }, +// &[], +// ) +// .unwrap(); -// let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// let pre_propose_id_from_proposal: u64 = app +// let proposal: ProposalResponse = app // .wrap() // .query_wasm_smart( -// pre_propose_approver.clone(), -// &ApproverQueryMsg::QueryExtension { -// msg: ApproverQueryExt::PreProposeApprovalIdForApproverProposalId { -// id: approver_prop_id, -// }, -// }, +// module_contract_info.code_hash, +// module_contract_info.address, +// &dps::msg::QueryMsg::Proposal { proposal_id: id }, // ) // .unwrap(); -// assert_eq!(pre_propose_id_from_proposal, pre_propose_id); -// let proposal_id_from_pre_propose: u64 = app -// .wrap() +// proposal.proposal.status +// } + +// fn get_config(app: &App, module_addr: Addr, module_code_hash: String) -> Config { +// app.wrap() +// .query_wasm_smart(module_addr, module_code_hash, &QueryMsg::Config {}) +// .unwrap() +// } + +// fn get_dao(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { +// app.wrap() +// .query_wasm_smart(module_code_hash, module_addr, &QueryMsg::Dao {}) +// .unwrap() +// } + +// fn query_query_auth(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { +// app.wrap() // .query_wasm_smart( -// pre_propose_approver.clone(), -// &ApproverQueryMsg::QueryExtension { -// msg: ApproverQueryExt::ApproverProposalIdForPreProposeApprovalId { -// id: pre_propose_id, -// }, -// }, +// module_code_hash, +// module_addr, +// &dao_interface::msg::QueryMsg::QueryAuthInfo {}, // ) -// .unwrap(); -// assert_eq!(proposal_id_from_pre_propose, approver_prop_id); +// .unwrap() +// } -// // Approver DAO votes to approves -// approve_proposal(&mut app, proposal_single_approver, "ekez", approver_prop_id); -// let id = get_latest_proposal_id(&app, proposal_single.clone()); +// fn get_proposal_module(app: &App, module_addr: Addr, module_code_hash: String) -> AnyContractInfo { +// app.wrap() +// .query_wasm_smart(module_code_hash, module_addr, &QueryMsg::ProposalModule {}) +// .unwrap() +// } -// // Member votes. -// let new_status = vote(&mut app, proposal_single, "ekez", id, Vote::Yes); -// assert_eq!(Status::Passed, new_status) +// fn get_deposit_info( +// app: &App, +// module_addr: Addr, +// module_code_hash: String, +// id: u64, +// ) -> DepositInfoResponse { +// app.wrap() +// .query_wasm_smart( +// module_code_hash, +// module_addr, +// &QueryMsg::DepositInfo { proposal_id: id }, +// ) +// .unwrap() // } -// #[test] -// fn test_update_config() { -// let mut app = App::default(); +// fn get_proposals(app: &App, module_addr: Addr, module_code_hash: String) -> ProposalListResponse { +// app.wrap() +// .query_wasm_smart( +// module_code_hash, +// module_addr, +// &dps::msg::QueryMsg::ListProposals { +// start_after: None, +// limit: None, +// }, +// ) +// .unwrap() +// } -// // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); +// fn get_latest_proposal_id(app: &App, module_contract_info: ContractInfo) -> u64 { +// // Check prop was created in the main DAO +// let props: ProposalListResponse = app +// .wrap() +// .query_wasm_smart( +// module_contract_info.code_hash, +// module_contract_info.address, +// &dps::msg::QueryMsg::ListProposals { +// start_after: None, +// limit: None, +// }, +// ) +// .unwrap(); +// println!("proposals :::::: {:?}",props.proposals.len()); +// props.proposals[props.proposals.len() - 1].id +// } -// let DefaultTestSetup { -// core_addr, -// proposal_single, -// pre_propose, -// approver_core_addr: _, -// proposal_single_approver, -// pre_propose_approver: _, -// } = setup_default_test(&mut app, None, false); +// fn update_config( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// deposit_info: Option, +// open_proposal_submission: bool, +// ) -> Config { +// app.execute_contract( +// Addr::unchecked(sender), +// &module_contract_info.clone(), +// &ExecuteMsg::UpdateConfig { +// deposit_info, +// open_proposal_submission, +// }, +// &[], +// ) +// .unwrap(); -// let config = get_config(&app, pre_propose.clone()); -// assert_eq!( -// config, -// Config { -// deposit_info: None, -// open_proposal_submission: false -// } -// ); +// get_config( +// app, +// module_contract_info.address, +// module_contract_info.code_hash, +// ) +// } -// let _pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); +// fn update_config_should_fail( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// deposit_info: Option, +// open_proposal_submission: bool, +// ) -> PreProposeError { +// app.execute_contract( +// Addr::unchecked(sender), +// &module_contract_info, +// &ExecuteMsg::UpdateConfig { +// deposit_info, +// open_proposal_submission, +// }, +// &[], +// ) +// .unwrap_err() +// .downcast() +// .unwrap() +// } -// // Approver DAO votes to approves -// let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// approve_proposal( -// &mut app, -// proposal_single_approver.clone(), -// "ekez", -// approver_prop_id, -// ); -// let id = get_latest_proposal_id(&app, proposal_single.clone()); +// fn withdraw( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// denom: Option, +// key: String, +// ) { +// app.execute_contract( +// Addr::unchecked(sender), +// &module_contract_info, +// &ExecuteMsg::Withdraw { +// denom, +// key: Some(key), +// }, +// &[], +// ) +// .unwrap(); +// } -// update_config( -// &mut app, -// pre_propose.clone(), -// core_addr.as_str(), -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Native("ujuno".to_string()), -// }, -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Never, -// }), -// true, -// ); +// fn withdraw_should_fail( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// denom: Option, +// ) -> PreProposeError { +// app.execute_contract( +// Addr::unchecked(sender), +// &module_contract_info, +// &ExecuteMsg::Withdraw { denom, key: None }, +// &[], +// ) +// .unwrap_err() +// .downcast() +// .unwrap() +// } -// let config = get_config(&app, pre_propose.clone()); -// assert_eq!( -// config, -// Config { -// deposit_info: Some(CheckedDepositInfo { -// denom: cw_denom::CheckedDenom::Native("ujuno".to_string()), -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Never -// }), -// open_proposal_submission: true, -// } -// ); +// fn close_proposal( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// proposal_id: u64, +// ) { +// app.execute_contract( +// Addr::unchecked(sender), +// &module_contract_info, +// &dps::msg::ExecuteMsg::Close { proposal_id }, +// &[], +// ) +// .unwrap(); +// } -// // Old proposal should still have same deposit info. -// let info = get_deposit_info(&app, pre_propose.clone(), id); -// assert_eq!( -// info, -// DepositInfoResponse { -// deposit_info: None, -// proposer: Addr::unchecked("ekez"), -// } -// ); +// fn close_proposal_wrapper( +// app: &mut App, +// contract_info: ContractInfo, +// sender: &str, +// id: u64, +// _auth: Auth, +// ) { +// close_proposal(app, contract_info, sender, id); +// } -// // New proposals should have the new deposit info. -// mint_natives(&mut app, "ekez", coins(10, "ujuno")); -// let _new_pre_propose_id = -// make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); +// fn execute_proposal( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// proposal_id: u64, +// auth: Auth, +// ) { +// app.execute_contract( +// Addr::unchecked(sender), +// &module_contract_info, +// &dps::msg::ExecuteMsg::Execute { auth, proposal_id }, +// &[], +// ) +// .unwrap(); +// } -// // Approver DAO votes to approve prop -// let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// approve_proposal( -// &mut app, -// proposal_single_approver.clone(), -// "ekez", -// approver_prop_id, +// fn approve_proposal( +// app: &mut App, +// module_contract_info: ContractInfo, +// sender: &str, +// proposal_id: u64, +// auth: Auth, +// ) { +// // Approver votes on prop +// vote( +// app, +// module_contract_info.clone(), +// sender, +// proposal_id, +// Vote::Yes, +// auth.clone(), // ); -// let new_id = get_latest_proposal_id(&app, proposal_single_approver); +// // Approver executes prop +// execute_proposal(app, module_contract_info, sender, proposal_id, auth); +// } -// let info = get_deposit_info(&app, pre_propose.clone(), new_id); -// assert_eq!( -// info, -// DepositInfoResponse { -// deposit_info: Some(CheckedDepositInfo { -// denom: cw_denom::CheckedDenom::Native("ujuno".to_string()), -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Never -// }), -// proposer: Addr::unchecked("ekez"), -// } -// ); +// enum ApprovalStatus { +// Approved, +// Rejected, +// } -// // Both proposals should be allowed to complete. -// vote(&mut app, proposal_single.clone(), "ekez", id, Vote::Yes); -// vote(&mut app, proposal_single.clone(), "ekez", new_id, Vote::Yes); -// execute_proposal(&mut app, proposal_single.clone(), "ekez", id); -// execute_proposal(&mut app, proposal_single.clone(), "ekez", new_id); -// // Deposit should not have been refunded (never policy in use). -// let balance = get_balance_native(&app, "ekez", "ujuno"); -// assert_eq!(balance, Uint128::new(0)); +// enum EndStatus { +// Passed, +// Failed, +// } -// // Only the core module can update the config. -// let err = -// update_config_should_fail(&mut app, pre_propose, proposal_single.as_str(), None, true); -// assert_eq!(err, PreProposeError::NotDao {}); +// enum RefundReceiver { +// Proposer, +// Dao, // } -// #[test] -// fn test_withdraw() { +// fn test_native_permutation( +// end_status: EndStatus, +// refund_policy: DepositRefundPolicy, +// receiver: RefundReceiver, +// approval_status: ApprovalStatus, +// ) { // let mut app = App::default(); // // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); +// let _ = instantiate_snip20_base_default(&mut app); // let DefaultTestSetup { -// core_addr, -// proposal_single, -// pre_propose, -// approver_core_addr: _, -// proposal_single_approver, -// pre_propose_approver: _, -// } = setup_default_test(&mut app, None, false); - -// let err = withdraw_should_fail( -// &mut app, -// pre_propose.clone(), -// proposal_single.as_str(), -// Some(UncheckedDenom::Native("ujuno".to_string())), -// ); -// assert_eq!(err, PreProposeError::NotDao {}); - -// let err = withdraw_should_fail( -// &mut app, -// pre_propose.clone(), -// core_addr.as_str(), -// Some(UncheckedDenom::Native("ujuno".to_string())), -// ); -// assert_eq!(err, PreProposeError::NothingToWithdraw {}); - -// let err = withdraw_should_fail(&mut app, pre_propose.clone(), core_addr.as_str(), None); -// assert_eq!(err, PreProposeError::NoWithdrawalDenom {}); - -// // Turn on native deposits. -// update_config( +// core_contract_info, +// proposal_single_contract_info, +// pre_propose_contract_info, +// approver_core_contract_info:_, +// pre_propose_approver:_, +// proposal_single_approver_contract_info, +// } = setup_default_test( // &mut app, -// pre_propose.clone(), -// core_addr.as_str(), // Some(UncheckedDepositInfo { // denom: DepositToken::Token { // denom: UncheckedDenom::Native("ujuno".to_string()), // }, // amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Always, +// refund_policy, // }), // false, // ); -// // Withdraw with no specified denom - should fall back to the one -// // in the config. -// mint_natives(&mut app, pre_propose.as_str(), coins(10, "ujuno")); -// withdraw(&mut app, pre_propose.clone(), core_addr.as_str(), None); -// let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); -// assert_eq!(balance, Uint128::new(10)); - -// // Withdraw again, this time specifying a native denomination. -// mint_natives(&mut app, pre_propose.as_str(), coins(10, "ujuno")); -// withdraw( -// &mut app, -// pre_propose.clone(), -// core_addr.as_str(), -// Some(UncheckedDenom::Native("ujuno".to_string())), -// ); -// let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); -// assert_eq!(balance, Uint128::new(20)); +// let query_auth = query_query_auth( +// &app, +// core_contract_info.address.clone(), +// core_contract_info.code_hash.clone(), +// ); +// let viewing_key = create_viewing_key( +// &mut app, +// ContractInfo { +// address: query_auth.addr, +// code_hash: query_auth.code_hash, +// }, +// "ekez", +// ); -// // Make a proposal with the native tokens to put some in the system. // mint_natives(&mut app, "ekez", coins(10, "ujuno")); -// let _native_pre_propose_id = -// make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); - -// // Approver DAO votes to approve -// let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// approve_proposal( +// let _pre_propose_id = make_pre_proposal( // &mut app, -// proposal_single_approver.clone(), +// pre_propose_contract_info, // "ekez", -// approver_prop_id, +// &coins(10, "ujuno"), +// Auth::ViewingKey { +// key: viewing_key.clone(), +// address: "ekez".into(), +// }, // ); -// let native_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// // Update the config to use a cw20 token. -// let cw20_address = instantiate_cw20_base_default(&mut app); -// update_config( -// &mut app, -// pre_propose.clone(), -// core_addr.as_str(), -// Some(UncheckedDepositInfo { -// denom: DepositToken::Token { -// denom: UncheckedDenom::Cw20(cw20_address.to_string()), -// }, -// amount: Uint128::new(10), -// refund_policy: DepositRefundPolicy::Always, -// }), -// false, +// // Check no props created on main DAO yet +// let props = get_proposals( +// &app, +// proposal_single_contract_info.address.clone(), +// proposal_single_contract_info.code_hash.clone(), // ); +// assert_eq!(props.proposals.len(), 0); -// increase_allowance( -// &mut app, -// "ekez", -// &pre_propose, -// cw20_address.clone(), -// Uint128::new(10), -// ); -// let _cw20_pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); - -// // Approver DAO votes to approve -// let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); -// approve_proposal(&mut app, proposal_single_approver, "ekez", approver_prop_id); -// let cw20_id = get_latest_proposal_id(&app, proposal_single.clone()); - -// // There is now a pending proposal and cw20 tokens in the -// // pre-propose module that should be returned on that proposal's -// // completion. To make things interesting, we withdraw those -// // tokens which should cause the status change hook on the -// // proposal's execution to fail as we don't have sufficent balance -// // to return the deposit. -// withdraw(&mut app, pre_propose.clone(), core_addr.as_str(), None); -// let balance = get_balance_cw20(&app, &cw20_address, core_addr.as_str()); -// assert_eq!(balance, Uint128::new(10)); - -// // Proposal should still be executable! We just get removed from -// // the proposal module's hook receiver list. -// vote( -// &mut app, -// proposal_single.clone(), -// "ekez", -// cw20_id, -// Vote::Yes, -// ); -// execute_proposal(&mut app, proposal_single.clone(), "ekez", cw20_id); +// // Make sure it went away. +// let balance = get_balance_native(&app, "ekez", "ujuno"); +// assert_eq!(balance, Uint128::zero()); -// // Make sure the proposal module has fallen back to anyone can -// // propose becuase of our malfunction. -// let proposal_creation_policy: ProposalCreationPolicy = app -// .wrap() -// .query_wasm_smart( -// proposal_single.clone(), -// &dps::msg::QueryMsg::ProposalCreationPolicy {}, -// ) -// .unwrap(); +// // Approver approves or rejects proposal +// match approval_status { +// ApprovalStatus::Approved => { +// // Get approver proposal id +// let id = get_latest_proposal_id(&app, proposal_single_approver_contract_info.clone()); +// // Approver votes on prop +// vote( +// &mut app, +// proposal_single_approver_contract_info.clone(), +// "ekez", +// id, +// Vote::Yes, +// Auth::ViewingKey { +// key: viewing_key.clone(), +// address: "ekez".into(), +// }, +// ); +// // Approver executes prop +// execute_proposal( +// &mut app, +// proposal_single_approver_contract_info, +// "ekez", +// id, +// Auth::ViewingKey { +// key: viewing_key.clone(), +// address: "ekez".into(), +// }, +// ); +// // Check prop was created in the main DAO +// let id = get_latest_proposal_id(&app, proposal_single_contract_info.clone()); +// let props = get_proposals( +// &app, +// proposal_single_contract_info.address.clone(), +// proposal_single_contract_info.code_hash.clone(), +// ); +// assert_eq!(props.proposals.len(), 1); -// assert_eq!(proposal_creation_policy, ProposalCreationPolicy::Anyone {}); +// // Voting happens on newly created proposal +// #[allow(clippy::type_complexity)] +// let (position, expected_status, trigger_refund): ( +// _, +// _, +// fn(&mut App, ContractInfo, &str, u64, Auth) -> (), +// ) = match end_status { +// EndStatus::Passed => (Vote::Yes, Status::Passed, execute_proposal), +// EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal_wrapper), +// }; +// let new_status = vote( +// &mut app, +// proposal_single_contract_info.clone(), +// "ekez", +// id, +// position, +// Auth::ViewingKey { +// key: viewing_key.clone(), +// address: "ekez".into(), +// }, +// ); +// assert_eq!(new_status, expected_status); -// // Close out the native proposal and it's deposit as well. -// vote( -// &mut app, -// proposal_single.clone(), -// "ekez", -// native_id, -// Vote::No, -// ); -// close_proposal(&mut app, proposal_single.clone(), "ekez", native_id); -// withdraw( -// &mut app, -// pre_propose.clone(), -// core_addr.as_str(), -// Some(UncheckedDenom::Native("ujuno".to_string())), -// ); -// let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); -// assert_eq!(balance, Uint128::new(30)); +// // Close or execute the proposal to trigger a refund. +// trigger_refund( +// &mut app, +// proposal_single_contract_info, +// "ekez", +// id, +// Auth::ViewingKey { +// key: viewing_key.clone(), +// address: "ekez".into(), +// }, +// ); +// } +// ApprovalStatus::Rejected => { +// // Approver votes on prop +// // No proposal is created so there is no voting +// vote( +// &mut app, +// proposal_single_approver_contract_info.clone(), +// "ekez", +// 1, +// Vote::No, +// Auth::ViewingKey { +// key: viewing_key.clone(), +// address: "ekez".into(), +// }, +// ); +// // Approver executes prop +// close_proposal(&mut app, proposal_single_approver_contract_info, "ekez", 1); + +// // No prop created +// let props = get_proposals( +// &app, +// proposal_single_contract_info.address, +// proposal_single_contract_info.code_hash, +// ); +// assert_eq!(props.proposals.len(), 0); +// } +// }; + +// let (dao_expected, proposer_expected) = match receiver { +// RefundReceiver::Proposer => (0, 10), +// RefundReceiver::Dao => (10, 0), +// }; + +// let proposer_balance = get_balance_native(&app, "ekez", "ujuno"); +// let dao_balance = get_balance_native(&app, core_contract_info.address.as_str(), "ujuno"); +// assert_eq!(proposer_expected, proposer_balance.u128()); +// assert_eq!(dao_expected, dao_balance.u128()) // } +// // fn test_cw20_permutation( +// // end_status: EndStatus, +// // refund_policy: DepositRefundPolicy, +// // receiver: RefundReceiver, +// // approval_status: ApprovalStatus, +// // ) { +// // let mut app = App::default(); + +// // let cw20_address = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr, +// // proposal_single, +// // pre_propose, +// // approver_core_addr: _, +// // proposal_single_approver, +// // pre_propose_approver: _, +// // } = setup_default_test( +// // &mut app, +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Cw20(cw20_address.to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy, +// // }), +// // false, +// // ); + +// // increase_allowance( +// // &mut app, +// // "ekez", +// // &pre_propose, +// // cw20_address.clone(), +// // Uint128::new(10), +// // ); +// // let _pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); + +// // // Check no props created on main DAO yet +// // let props = get_proposals(&app, proposal_single.clone()); +// // assert_eq!(props.proposals.len(), 0); + +// // // Make sure it went await. +// // let balance = get_balance_cw20(&app, cw20_address.clone(), "ekez"); +// // assert_eq!(balance, Uint128::zero()); + +// // // Approver approves or rejects proposal +// // match approval_status { +// // ApprovalStatus::Approved => { +// // // Get approver proposal id +// // let id = get_latest_proposal_id(&app, proposal_single_approver.clone()); + +// // // Approver votes on prop +// // vote( +// // &mut app, +// // proposal_single_approver.clone(), +// // "ekez", +// // id, +// // Vote::Yes, +// // ); +// // // Approver executes prop +// // execute_proposal(&mut app, proposal_single_approver, "ekez", id); + +// // // Check prop was created in the main DAO +// // let id = get_latest_proposal_id(&app, proposal_single.clone()); +// // let props = get_proposals(&app, proposal_single.clone()); +// // assert_eq!(props.proposals.len(), 1); + +// // // Voting happens on newly created proposal +// // #[allow(clippy::type_complexity)] +// // let (position, expected_status, trigger_refund): ( +// // _, +// // _, +// // fn(&mut App, Addr, &str, u64) -> (), +// // ) = match end_status { +// // EndStatus::Passed => (Vote::Yes, Status::Passed, execute_proposal), +// // EndStatus::Failed => (Vote::No, Status::Rejected, close_proposal), +// // }; +// // let new_status = vote(&mut app, proposal_single.clone(), "ekez", id, position); +// // assert_eq!(new_status, expected_status); + +// // // Close or execute the proposal to trigger a refund. +// // trigger_refund(&mut app, proposal_single, "ekez", id); +// // } +// // ApprovalStatus::Rejected => { +// // // Approver votes on prop +// // // No proposal is created so there is no voting +// // vote( +// // &mut app, +// // proposal_single_approver.clone(), +// // "ekez", +// // 1, +// // Vote::No, +// // ); +// // // Approver executes prop +// // close_proposal(&mut app, proposal_single_approver, "ekez", 1); + +// // // No prop created +// // let props = get_proposals(&app, proposal_single); +// // assert_eq!(props.proposals.len(), 0); +// // } +// // }; + +// // let (dao_expected, proposer_expected) = match receiver { +// // RefundReceiver::Proposer => (0, 10), +// // RefundReceiver::Dao => (10, 0), +// // }; + +// // let proposer_balance = get_balance_cw20(&app, &cw20_address, "ekez"); +// // let dao_balance = get_balance_cw20(&app, &cw20_address, core_addr); +// // assert_eq!(proposer_expected, proposer_balance.u128()); +// // assert_eq!(dao_expected, dao_balance.u128()) +// // } + +// // #[test] +// // fn test_native_failed_always_refund() { +// // test_native_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Always, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_native_rejected_always_refund() { +// // test_native_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Always, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Rejected, +// // ) +// // } + +// // #[test] +// // fn test_cw20_failed_always_refund() { +// // test_cw20_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Always, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_cw20_rejected_always_refund() { +// // test_cw20_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Always, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Rejected, +// // ) +// // } + +// // #[test] +// // fn test_native_passed_always_refund() { +// // test_native_permutation( +// // EndStatus::Passed, +// // DepositRefundPolicy::Always, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_cw20_passed_always_refund() { +// // test_cw20_permutation( +// // EndStatus::Passed, +// // DepositRefundPolicy::Always, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_native_passed_never_refund() { +// // test_native_permutation( +// // EndStatus::Passed, +// // DepositRefundPolicy::Never, +// // RefundReceiver::Dao, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_cw20_passed_never_refund() { +// // test_cw20_permutation( +// // EndStatus::Passed, +// // DepositRefundPolicy::Never, +// // RefundReceiver::Dao, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_native_failed_never_refund() { +// // test_native_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Never, +// // RefundReceiver::Dao, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_native_rejected_never_refund() { +// // test_native_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Never, +// // RefundReceiver::Dao, +// // ApprovalStatus::Rejected, +// // ) +// // } + +// // #[test] +// // fn test_cw20_failed_never_refund() { +// // test_cw20_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Never, +// // RefundReceiver::Dao, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_cw20_rejected_never_refund() { +// // test_cw20_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::Never, +// // RefundReceiver::Dao, +// // ApprovalStatus::Rejected, +// // ) +// // } + +// // #[test] +// // fn test_native_passed_passed_refund() { +// // test_native_permutation( +// // EndStatus::Passed, +// // DepositRefundPolicy::OnlyPassed, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_cw20_passed_passed_refund() { +// // test_cw20_permutation( +// // EndStatus::Passed, +// // DepositRefundPolicy::OnlyPassed, +// // RefundReceiver::Proposer, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_native_failed_passed_refund() { +// // test_native_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::OnlyPassed, +// // RefundReceiver::Dao, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_native_rejected_passed_refund() { +// // test_native_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::OnlyPassed, +// // RefundReceiver::Dao, +// // ApprovalStatus::Rejected, +// // ) +// // } + +// // #[test] +// // fn test_cw20_failed_passed_refund() { +// // test_cw20_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::OnlyPassed, +// // RefundReceiver::Dao, +// // ApprovalStatus::Approved, +// // ) +// // } + +// // #[test] +// // fn test_cw20_rejected_passed_refund() { +// // test_cw20_permutation( +// // EndStatus::Failed, +// // DepositRefundPolicy::OnlyPassed, +// // RefundReceiver::Dao, +// // ApprovalStatus::Rejected, +// // ) +// // } + +// // See: // #[test] -// fn test_reset_approver() { +// fn test_multiple_open_proposals() { // let mut app = App::default(); // // Need to instantiate this so contract addresses match with cw20 test cases -// let _ = instantiate_cw20_base_default(&mut app); +// let _ = instantiate_snip20_base_default(&mut app); // let DefaultTestSetup { -// core_addr: _, -// proposal_single: _, -// pre_propose, -// approver_core_addr, -// proposal_single_approver: _, -// pre_propose_approver, +// pre_propose_approver: _, +// core_contract_info, +// proposal_single_contract_info, +// pre_propose_contract_info, +// approver_core_contract_info, +// proposal_single_approver_contract_info, // } = setup_default_test( // &mut app, // Some(UncheckedDepositInfo { @@ -2075,70 +1465,685 @@ fn test_native_failed_always_refund() { // false, // ); -// // Ensure approver is set to the pre_propose_approver -// let approver: Addr = app -// .wrap() -// .query_wasm_smart( -// pre_propose.clone(), -// &QueryMsg::QueryExtension { -// msg: QueryExt::Approver {}, -// }, -// ) -// .unwrap(); -// assert_eq!(approver, pre_propose_approver); - -// // Fail to change approver by non-approver. -// let err: PreProposeError = app -// .execute_contract( -// Addr::unchecked("someone"), -// pre_propose.clone(), -// &ExecuteMsg::Extension { -// msg: ExecuteExt::UpdateApprover { -// address: "someone".to_string(), -// }, -// }, -// &[], -// ) -// .unwrap_err() -// .downcast() -// .unwrap(); -// assert_eq!(err, PreProposeError::Unauthorized {}); - -// // Fail to reset approver back to approver DAO by non-approver. -// let err: PreProposeError = app -// .execute_contract( -// Addr::unchecked("someone"), -// pre_propose_approver.clone(), -// &ApproverExecuteMsg::Extension { -// msg: ApproverExecuteExt::ResetApprover {}, -// }, -// &[], -// ) -// .unwrap_err() -// .downcast() -// .unwrap(); -// assert_eq!(err, PreProposeError::Unauthorized {}); - -// // Reset approver back to approver DAO. -// app.execute_contract( -// approver_core_addr.clone(), -// pre_propose_approver.clone(), -// &ApproverExecuteMsg::Extension { -// msg: ApproverExecuteExt::ResetApprover {}, -// }, -// &[], -// ) -// .unwrap(); - -// // Ensure approver is reset back to the approver DAO -// let approver: Addr = app -// .wrap() -// .query_wasm_smart( -// pre_propose.clone(), -// &QueryMsg::QueryExtension { -// msg: QueryExt::Approver {}, -// }, -// ) -// .unwrap(); -// assert_eq!(approver, approver_core_addr); +// // mint_natives(&mut app, "ekez", coins(20, "ujuno")); +// // let _first_pre_propose_id = +// // make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(10, balance.u128()); + +// // // Approver DAO approves prop, balance remains the same +// // let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); +// // approve_proposal( +// // &mut app, +// // proposal_single_approver.clone(), +// // "ekez", +// // approver_prop_id, +// // ); +// // let first_id = get_latest_proposal_id(&app, proposal_single.clone()); +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(10, balance.u128()); + +// // let _second_pre_propose_id = +// // make_pre_proposal(&mut app, pre_propose, "ekez", &coins(10, "ujuno")); +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(0, balance.u128()); + +// // // Approver DAO votes to approves, balance remains the same +// // let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); +// // approve_proposal(&mut app, proposal_single_approver, "ekez", approver_prop_id); +// // let second_id = get_latest_proposal_id(&app, proposal_single.clone()); +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(0, balance.u128()); + +// // // Finish up the first proposal. +// // let new_status = vote( +// // &mut app, +// // proposal_single.clone(), +// // "ekez", +// // first_id, +// // Vote::Yes, +// // ); +// // assert_eq!(Status::Passed, new_status); + +// // // Still zero. +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(0, balance.u128()); + +// // execute_proposal(&mut app, proposal_single.clone(), "ekez", first_id); + +// // // First proposal refunded. +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(10, balance.u128()); + +// // // Finish up the second proposal. +// // let new_status = vote( +// // &mut app, +// // proposal_single.clone(), +// // "ekez", +// // second_id, +// // Vote::No, +// // ); +// // assert_eq!(Status::Rejected, new_status); + +// // // Still zero. +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(10, balance.u128()); + +// // close_proposal(&mut app, proposal_single, "ekez", second_id); + +// // // All deposits have been refunded. +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(20, balance.u128()); // } + +// // #[test] +// // fn test_set_version() { +// // let mut app = App::default(); + +// // // Need to instantiate this so contract addresses match with cw20 test cases +// // let _ = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr: _, +// // proposal_single: _, +// // pre_propose: _, +// // approver_core_addr: _, +// // proposal_single_approver: _, +// // pre_propose_approver, +// // } = setup_default_test( +// // &mut app, +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Native("ujuno".to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Always, +// // }), +// // false, +// // ); + +// // let info: ContractVersion = from_binary( +// // app.wrap() +// // .query_wasm_raw(pre_propose_approver, "contract_info".as_bytes()) +// // .unwrap() +// // .unwrap(), +// // ) +// // .unwrap(); +// // assert_eq!( +// // ContractVersion { +// // contract: CONTRACT_NAME.to_string(), +// // version: CONTRACT_VERSION.to_string() +// // }, +// // info +// // ) +// // } + +// // #[test] +// // fn test_permissions() { +// // let mut app = App::default(); + +// // // Need to instantiate this so contract addresses match with cw20 test cases +// // let _ = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr, +// // proposal_single: _, +// // pre_propose, +// // approver_core_addr: _, +// // proposal_single_approver: _, +// // pre_propose_approver: _, +// // } = setup_default_test( +// // &mut app, +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Native("ujuno".to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Always, +// // }), +// // false, // no open proposal submission. +// // ); + +// // let err: PreProposeError = app +// // .execute_contract( +// // core_addr, +// // pre_propose.clone(), +// // &ExecuteMsg::ProposalCompletedHook { +// // proposal_id: 1, +// // new_status: Status::Closed, +// // }, +// // &[], +// // ) +// // .unwrap_err() +// // .downcast() +// // .unwrap(); +// // assert_eq!(err, PreProposeError::NotModule {}); + +// // // Non-members may not propose when open_propose_submission is +// // // disabled. +// // let err: PreProposeError = app +// // .execute_contract( +// // Addr::unchecked("nonmember"), +// // pre_propose, +// // &ExecuteMsg::Propose { +// // msg: ProposeMessage::Propose { +// // title: "I would like to join the DAO".to_string(), +// // description: "though, I am currently not a member.".to_string(), +// // msgs: vec![], +// // }, +// // }, +// // &[], +// // ) +// // .unwrap_err() +// // .downcast() +// // .unwrap(); +// // assert_eq!(err, PreProposeError::NotMember {}); +// // } + +// // #[test] +// // fn test_approval_and_rejection_permissions() { +// // let mut app = App::default(); + +// // // Need to instantiate this so contract addresses match with cw20 test cases +// // let _ = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr: _, +// // proposal_single: _, +// // pre_propose, +// // approver_core_addr: _, +// // proposal_single_approver: _, +// // pre_propose_approver: _, +// // } = setup_default_test( +// // &mut app, +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Native("ujuno".to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Always, +// // }), +// // true, // yes, open proposal submission. +// // ); + +// // // Non-member proposes. +// // mint_natives(&mut app, "nonmember", coins(10, "ujuno")); +// // let pre_propose_id = make_pre_proposal( +// // &mut app, +// // pre_propose.clone(), +// // "nonmember", +// // &coins(10, "ujuno"), +// // ); + +// // // Only approver can propose +// // let err: PreProposeError = app +// // .execute_contract( +// // Addr::unchecked("nonmember"), +// // pre_propose.clone(), +// // &ExecuteMsg::Extension { +// // msg: ExecuteExt::Approve { id: pre_propose_id }, +// // }, +// // &[], +// // ) +// // .unwrap_err() +// // .downcast() +// // .unwrap(); +// // assert_eq!(err, PreProposeError::Unauthorized {}); + +// // // Only approver can propose +// // let err: PreProposeError = app +// // .execute_contract( +// // Addr::unchecked("nonmember"), +// // pre_propose, +// // &ExecuteMsg::Extension { +// // msg: ExecuteExt::Reject { id: pre_propose_id }, +// // }, +// // &[], +// // ) +// // .unwrap_err() +// // .downcast() +// // .unwrap(); +// // assert_eq!(err, PreProposeError::Unauthorized {}); +// // } + +// // #[test] +// // fn test_propose_open_proposal_submission() { +// // let mut app = App::default(); + +// // // Need to instantiate this so contract addresses match with cw20 test cases +// // let _ = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr: _, +// // proposal_single, +// // pre_propose, +// // approver_core_addr: _, +// // proposal_single_approver, +// // pre_propose_approver, +// // } = setup_default_test( +// // &mut app, +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Native("ujuno".to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Always, +// // }), +// // true, // yes, open proposal submission. +// // ); + +// // // Non-member proposes. +// // mint_natives(&mut app, "nonmember", coins(10, "ujuno")); +// // let pre_propose_id = make_pre_proposal(&mut app, pre_propose, "nonmember", &coins(10, "ujuno")); + +// // let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); +// // let pre_propose_id_from_proposal: u64 = app +// // .wrap() +// // .query_wasm_smart( +// // pre_propose_approver.clone(), +// // &ApproverQueryMsg::QueryExtension { +// // msg: ApproverQueryExt::PreProposeApprovalIdForApproverProposalId { +// // id: approver_prop_id, +// // }, +// // }, +// // ) +// // .unwrap(); +// // assert_eq!(pre_propose_id_from_proposal, pre_propose_id); + +// // let proposal_id_from_pre_propose: u64 = app +// // .wrap() +// // .query_wasm_smart( +// // pre_propose_approver.clone(), +// // &ApproverQueryMsg::QueryExtension { +// // msg: ApproverQueryExt::ApproverProposalIdForPreProposeApprovalId { +// // id: pre_propose_id, +// // }, +// // }, +// // ) +// // .unwrap(); +// // assert_eq!(proposal_id_from_pre_propose, approver_prop_id); + +// // // Approver DAO votes to approves +// // approve_proposal(&mut app, proposal_single_approver, "ekez", approver_prop_id); +// // let id = get_latest_proposal_id(&app, proposal_single.clone()); + +// // // Member votes. +// // let new_status = vote(&mut app, proposal_single, "ekez", id, Vote::Yes); +// // assert_eq!(Status::Passed, new_status) +// // } + +// // #[test] +// // fn test_update_config() { +// // let mut app = App::default(); + +// // // Need to instantiate this so contract addresses match with cw20 test cases +// // let _ = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr, +// // proposal_single, +// // pre_propose, +// // approver_core_addr: _, +// // proposal_single_approver, +// // pre_propose_approver: _, +// // } = setup_default_test(&mut app, None, false); + +// // let config = get_config(&app, pre_propose.clone()); +// // assert_eq!( +// // config, +// // Config { +// // deposit_info: None, +// // open_proposal_submission: false +// // } +// // ); + +// // let _pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); + +// // // Approver DAO votes to approves +// // let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); +// // approve_proposal( +// // &mut app, +// // proposal_single_approver.clone(), +// // "ekez", +// // approver_prop_id, +// // ); +// // let id = get_latest_proposal_id(&app, proposal_single.clone()); + +// // update_config( +// // &mut app, +// // pre_propose.clone(), +// // core_addr.as_str(), +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Native("ujuno".to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Never, +// // }), +// // true, +// // ); + +// // let config = get_config(&app, pre_propose.clone()); +// // assert_eq!( +// // config, +// // Config { +// // deposit_info: Some(CheckedDepositInfo { +// // denom: cw_denom::CheckedDenom::Native("ujuno".to_string()), +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Never +// // }), +// // open_proposal_submission: true, +// // } +// // ); + +// // // Old proposal should still have same deposit info. +// // let info = get_deposit_info(&app, pre_propose.clone(), id); +// // assert_eq!( +// // info, +// // DepositInfoResponse { +// // deposit_info: None, +// // proposer: Addr::unchecked("ekez"), +// // } +// // ); + +// // // New proposals should have the new deposit info. +// // mint_natives(&mut app, "ekez", coins(10, "ujuno")); +// // let _new_pre_propose_id = +// // make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + +// // // Approver DAO votes to approve prop +// // let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); +// // approve_proposal( +// // &mut app, +// // proposal_single_approver.clone(), +// // "ekez", +// // approver_prop_id, +// // ); +// // let new_id = get_latest_proposal_id(&app, proposal_single_approver); + +// // let info = get_deposit_info(&app, pre_propose.clone(), new_id); +// // assert_eq!( +// // info, +// // DepositInfoResponse { +// // deposit_info: Some(CheckedDepositInfo { +// // denom: cw_denom::CheckedDenom::Native("ujuno".to_string()), +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Never +// // }), +// // proposer: Addr::unchecked("ekez"), +// // } +// // ); + +// // // Both proposals should be allowed to complete. +// // vote(&mut app, proposal_single.clone(), "ekez", id, Vote::Yes); +// // vote(&mut app, proposal_single.clone(), "ekez", new_id, Vote::Yes); +// // execute_proposal(&mut app, proposal_single.clone(), "ekez", id); +// // execute_proposal(&mut app, proposal_single.clone(), "ekez", new_id); +// // // Deposit should not have been refunded (never policy in use). +// // let balance = get_balance_native(&app, "ekez", "ujuno"); +// // assert_eq!(balance, Uint128::new(0)); + +// // // Only the core module can update the config. +// // let err = +// // update_config_should_fail(&mut app, pre_propose, proposal_single.as_str(), None, true); +// // assert_eq!(err, PreProposeError::NotDao {}); +// // } + +// // #[test] +// // fn test_withdraw() { +// // let mut app = App::default(); + +// // // Need to instantiate this so contract addresses match with cw20 test cases +// // let _ = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr, +// // proposal_single, +// // pre_propose, +// // approver_core_addr: _, +// // proposal_single_approver, +// // pre_propose_approver: _, +// // } = setup_default_test(&mut app, None, false); + +// // let err = withdraw_should_fail( +// // &mut app, +// // pre_propose.clone(), +// // proposal_single.as_str(), +// // Some(UncheckedDenom::Native("ujuno".to_string())), +// // ); +// // assert_eq!(err, PreProposeError::NotDao {}); + +// // let err = withdraw_should_fail( +// // &mut app, +// // pre_propose.clone(), +// // core_addr.as_str(), +// // Some(UncheckedDenom::Native("ujuno".to_string())), +// // ); +// // assert_eq!(err, PreProposeError::NothingToWithdraw {}); + +// // let err = withdraw_should_fail(&mut app, pre_propose.clone(), core_addr.as_str(), None); +// // assert_eq!(err, PreProposeError::NoWithdrawalDenom {}); + +// // // Turn on native deposits. +// // update_config( +// // &mut app, +// // pre_propose.clone(), +// // core_addr.as_str(), +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Native("ujuno".to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Always, +// // }), +// // false, +// // ); + +// // // Withdraw with no specified denom - should fall back to the one +// // // in the config. +// // mint_natives(&mut app, pre_propose.as_str(), coins(10, "ujuno")); +// // withdraw(&mut app, pre_propose.clone(), core_addr.as_str(), None); +// // let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); +// // assert_eq!(balance, Uint128::new(10)); + +// // // Withdraw again, this time specifying a native denomination. +// // mint_natives(&mut app, pre_propose.as_str(), coins(10, "ujuno")); +// // withdraw( +// // &mut app, +// // pre_propose.clone(), +// // core_addr.as_str(), +// // Some(UncheckedDenom::Native("ujuno".to_string())), +// // ); +// // let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); +// // assert_eq!(balance, Uint128::new(20)); + +// // // Make a proposal with the native tokens to put some in the system. +// // mint_natives(&mut app, "ekez", coins(10, "ujuno")); +// // let _native_pre_propose_id = +// // make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &coins(10, "ujuno")); + +// // // Approver DAO votes to approve +// // let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); +// // approve_proposal( +// // &mut app, +// // proposal_single_approver.clone(), +// // "ekez", +// // approver_prop_id, +// // ); +// // let native_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); + +// // // Update the config to use a cw20 token. +// // let cw20_address = instantiate_cw20_base_default(&mut app); +// // update_config( +// // &mut app, +// // pre_propose.clone(), +// // core_addr.as_str(), +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Cw20(cw20_address.to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Always, +// // }), +// // false, +// // ); + +// // increase_allowance( +// // &mut app, +// // "ekez", +// // &pre_propose, +// // cw20_address.clone(), +// // Uint128::new(10), +// // ); +// // let _cw20_pre_propose_id = make_pre_proposal(&mut app, pre_propose.clone(), "ekez", &[]); + +// // // Approver DAO votes to approve +// // let approver_prop_id = get_latest_proposal_id(&app, proposal_single_approver.clone()); +// // approve_proposal(&mut app, proposal_single_approver, "ekez", approver_prop_id); +// // let cw20_id = get_latest_proposal_id(&app, proposal_single.clone()); + +// // // There is now a pending proposal and cw20 tokens in the +// // // pre-propose module that should be returned on that proposal's +// // // completion. To make things interesting, we withdraw those +// // // tokens which should cause the status change hook on the +// // // proposal's execution to fail as we don't have sufficent balance +// // // to return the deposit. +// // withdraw(&mut app, pre_propose.clone(), core_addr.as_str(), None); +// // let balance = get_balance_cw20(&app, &cw20_address, core_addr.as_str()); +// // assert_eq!(balance, Uint128::new(10)); + +// // // Proposal should still be executable! We just get removed from +// // // the proposal module's hook receiver list. +// // vote( +// // &mut app, +// // proposal_single.clone(), +// // "ekez", +// // cw20_id, +// // Vote::Yes, +// // ); +// // execute_proposal(&mut app, proposal_single.clone(), "ekez", cw20_id); + +// // // Make sure the proposal module has fallen back to anyone can +// // // propose becuase of our malfunction. +// // let proposal_creation_policy: ProposalCreationPolicy = app +// // .wrap() +// // .query_wasm_smart( +// // proposal_single.clone(), +// // &dps::msg::QueryMsg::ProposalCreationPolicy {}, +// // ) +// // .unwrap(); + +// // assert_eq!(proposal_creation_policy, ProposalCreationPolicy::Anyone {}); + +// // // Close out the native proposal and it's deposit as well. +// // vote( +// // &mut app, +// // proposal_single.clone(), +// // "ekez", +// // native_id, +// // Vote::No, +// // ); +// // close_proposal(&mut app, proposal_single.clone(), "ekez", native_id); +// // withdraw( +// // &mut app, +// // pre_propose.clone(), +// // core_addr.as_str(), +// // Some(UncheckedDenom::Native("ujuno".to_string())), +// // ); +// // let balance = get_balance_native(&app, core_addr.as_str(), "ujuno"); +// // assert_eq!(balance, Uint128::new(30)); +// // } + +// // #[test] +// // fn test_reset_approver() { +// // let mut app = App::default(); + +// // // Need to instantiate this so contract addresses match with cw20 test cases +// // let _ = instantiate_cw20_base_default(&mut app); + +// // let DefaultTestSetup { +// // core_addr: _, +// // proposal_single: _, +// // pre_propose, +// // approver_core_addr, +// // proposal_single_approver: _, +// // pre_propose_approver, +// // } = setup_default_test( +// // &mut app, +// // Some(UncheckedDepositInfo { +// // denom: DepositToken::Token { +// // denom: UncheckedDenom::Native("ujuno".to_string()), +// // }, +// // amount: Uint128::new(10), +// // refund_policy: DepositRefundPolicy::Always, +// // }), +// // false, +// // ); + +// // // Ensure approver is set to the pre_propose_approver +// // let approver: Addr = app +// // .wrap() +// // .query_wasm_smart( +// // pre_propose.clone(), +// // &QueryMsg::QueryExtension { +// // msg: QueryExt::Approver {}, +// // }, +// // ) +// // .unwrap(); +// // assert_eq!(approver, pre_propose_approver); + +// // // Fail to change approver by non-approver. +// // let err: PreProposeError = app +// // .execute_contract( +// // Addr::unchecked("someone"), +// // pre_propose.clone(), +// // &ExecuteMsg::Extension { +// // msg: ExecuteExt::UpdateApprover { +// // address: "someone".to_string(), +// // }, +// // }, +// // &[], +// // ) +// // .unwrap_err() +// // .downcast() +// // .unwrap(); +// // assert_eq!(err, PreProposeError::Unauthorized {}); + +// // // Fail to reset approver back to approver DAO by non-approver. +// // let err: PreProposeError = app +// // .execute_contract( +// // Addr::unchecked("someone"), +// // pre_propose_approver.clone(), +// // &ApproverExecuteMsg::Extension { +// // msg: ApproverExecuteExt::ResetApprover {}, +// // }, +// // &[], +// // ) +// // .unwrap_err() +// // .downcast() +// // .unwrap(); +// // assert_eq!(err, PreProposeError::Unauthorized {}); + +// // // Reset approver back to approver DAO. +// // app.execute_contract( +// // approver_core_addr.clone(), +// // pre_propose_approver.clone(), +// // &ApproverExecuteMsg::Extension { +// // msg: ApproverExecuteExt::ResetApprover {}, +// // }, +// // &[], +// // ) +// // .unwrap(); + +// // // Ensure approver is reset back to the approver DAO +// // let approver: Addr = app +// // .wrap() +// // .query_wasm_smart( +// // pre_propose.clone(), +// // &QueryMsg::QueryExtension { +// // msg: QueryExt::Approver {}, +// // }, +// // ) +// // .unwrap(); +// // assert_eq!(approver, approver_core_addr); +// // } diff --git a/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml b/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml index 9531c23..b1f7dde 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml @@ -35,7 +35,7 @@ dao-voting = { workspace = true } cw-denom = { workspace = true } dao-interface = { workspace = true } dao-hooks = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } snip721-reference-impl = { workspace = true } shade-protocol = { workspace = true } query_auth = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs index 6de95c3..12b261e 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs @@ -24,7 +24,7 @@ use secret_multi_test::{ use secret_utils::Duration; use shade_protocol::basic_staking::Auth; use shade_protocol::utils::asset::RawContract; -use snip20_reference_impl::msg::{InitConfig, InitialBalance}; +use snip20_base::msg::{InitConfig, InitialBalance}; use crate::contract::*; const CREATOR_ADDR: &str = "creator"; @@ -59,15 +59,6 @@ pub fn dao_voting_cw4_contract() -> Box> { Box::new(contract) } -pub fn snip721_base_contract() -> Box> { - let contract = ContractWrapper::new( - snip721_reference_impl::contract::execute, - snip721_reference_impl::contract::instantiate, - snip721_reference_impl::contract::query, - ); - Box::new(contract) -} - pub fn query_auth_contract() -> Box> { let contract = ContractWrapper::new( query_auth::contract::execute, @@ -87,8 +78,6 @@ pub fn instantiate_with_cw4_groups_governance( ) -> ContractInfo { let cw4_info = app.store_code(cw4_group_contract()); let votemod_info = app.store_code(dao_voting_cw4_contract()); - let snip20_info = app.store_code(snip20_base_contract()); - let snip721_info = app.store_code(snip721_base_contract()); let query_auth = app.store_code(query_auth_contract()); let initial_weights = initial_weights.unwrap_or_default(); @@ -128,7 +117,6 @@ pub fn instantiate_with_cw4_groups_governance( initial_members: initial_weights, query_auth: None, }, - dao_code_hash: core_info.code_hash.clone(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -147,8 +135,6 @@ pub fn instantiate_with_cw4_groups_governance( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "seed".into(), - snip20_code_hash: snip20_info.code_hash, - snip721_code_hash: snip721_info.code_hash, }; let addr = app @@ -185,9 +171,9 @@ fn cw_pre_propose_base_proposal_single() -> Box> { fn snip20_base_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -196,9 +182,7 @@ fn get_default_proposal_module_instantiate( app: &mut App, deposit_info: Option, open_proposal_submission: bool, - proposal_module_code_hash: String, query_auth: ContractInfo, - dao_code_hash: String, ) -> cpm::msg::InstantiateMsg { let pre_propose_info = app.store_code(cw_pre_propose_base_proposal_single()); @@ -218,7 +202,6 @@ fn get_default_proposal_module_instantiate( deposit_info, open_proposal_submission, extension: Empty::default(), - proposal_module_code_hash, }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -228,7 +211,6 @@ fn get_default_proposal_module_instantiate( }, close_proposal_on_execution_failure: false, veto: None, - dao_code_hash, query_auth: Some(RawContract::new( &query_auth.address.into_string(), &query_auth.code_hash, @@ -238,7 +220,7 @@ fn get_default_proposal_module_instantiate( fn instantiate_snip20_base_default(app: &mut App) -> ContractInfo { let snip20_info = app.store_code(snip20_base_contract()); - let snip20_instantiate = snip20_reference_impl::msg::InstantiateMsg { + let snip20_instantiate = snip20_base::msg::InstantiateMsg { name: "snip20 token".to_string(), symbol: "sniptwenty".to_string(), decimals: 6, @@ -311,7 +293,7 @@ fn create_viewing_key(app: &mut App, contract_info: ContractInfo, sender: &str) } fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: &str) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { entropy: "entropy".to_string(), padding: None, }; @@ -319,8 +301,8 @@ fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: & .execute_contract(Addr::unchecked(addr), &contract_info, &msg, &[]) .unwrap(); let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { viewing_key = key; }; viewing_key @@ -344,9 +326,7 @@ fn setup_default_test( app, deposit_info, open_proposal_submission, - cpm_nfo.code_hash.clone(), query_auth.clone(), - core_info.code_hash.clone(), ); let core_contract_info = instantiate_with_cw4_groups_governance( @@ -534,7 +514,7 @@ fn increase_allowance( app.execute_contract( Addr::unchecked(sender), &snip20_contract_info, - &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { + &snip20_base::msg::ExecuteMsg::IncreaseAllowance { spender: receiver.to_string(), amount, expiration: None, @@ -552,16 +532,16 @@ fn get_balance_snip20, C: Into, U: Into, K: Into address: U, key: K, ) -> Uint128 { - let msg = snip20_reference_impl::msg::QueryMsg::Balance { + let msg = snip20_base::msg::QueryMsg::Balance { address: address.into(), key: key.into(), }; - let result: snip20_reference_impl::msg::QueryAnswer = app + let result: snip20_base::msg::QueryAnswer = app .wrap() .query_wasm_smart(code_hash, contract_addr, &msg) .unwrap(); let mut balance = Uint128::zero(); - if let snip20_reference_impl::msg::QueryAnswer::Balance { amount } = result { + if let snip20_base::msg::QueryAnswer::Balance { amount } = result { balance = amount; } balance @@ -1614,7 +1594,6 @@ fn test_instantiate_with_zero_native_deposit() { }), open_proposal_submission: false, extension: Empty::default(), - proposal_module_code_hash: cpm_info.code_hash.clone(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -1624,7 +1603,6 @@ fn test_instantiate_with_zero_native_deposit() { }, close_proposal_on_execution_failure: false, veto: None, - dao_code_hash: core_info.code_hash.clone(), query_auth: None, } }; @@ -1687,7 +1665,6 @@ fn test_instantiate_with_zero_cw20_deposit() { }), open_proposal_submission: false, extension: Empty::default(), - proposal_module_code_hash: cpm_info.code_hash.clone(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -1697,7 +1674,6 @@ fn test_instantiate_with_zero_cw20_deposit() { }, close_proposal_on_execution_failure: false, veto: None, - dao_code_hash: core_info.code_hash.clone(), query_auth: None, } }; diff --git a/contracts/pre-propose/dao-pre-propose-single/Cargo.toml b/contracts/pre-propose/dao-pre-propose-single/Cargo.toml index 70cd28a..445970e 100644 --- a/contracts/pre-propose/dao-pre-propose-single/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-single/Cargo.toml @@ -29,7 +29,7 @@ secret-utils = { workspace = true } cw4-group = { workspace = true } cw-denom = { workspace = true } dao-interface = { workspace = true } -snip20-reference-impl ={ workspace = true } +snip20-base ={ workspace = true } dao-proposal-single ={ workspace = true } cw-hooks = { workspace = true } shade-protocol ={ workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-single/src/tests.rs b/contracts/pre-propose/dao-pre-propose-single/src/tests.rs index b4afce8..5b37933 100644 --- a/contracts/pre-propose/dao-pre-propose-single/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-single/src/tests.rs @@ -23,7 +23,7 @@ use secret_multi_test::{ use secret_utils::Duration; use shade_protocol::basic_staking::Auth; use shade_protocol::utils::asset::RawContract; -use snip20_reference_impl::msg::{InitConfig, InitialBalance}; +use snip20_base::msg::{InitConfig, InitialBalance}; const CREATOR_ADDR: &str = "creator"; pub fn cw4_group_contract() -> Box> { @@ -56,15 +56,6 @@ pub fn dao_voting_cw4_contract() -> Box> { Box::new(contract) } -pub fn snip721_base_contract() -> Box> { - let contract = ContractWrapper::new( - snip721_reference_impl::contract::execute, - snip721_reference_impl::contract::instantiate, - snip721_reference_impl::contract::query, - ); - Box::new(contract) -} - pub fn query_auth_contract() -> Box> { let contract = ContractWrapper::new( query_auth::contract::execute, @@ -84,8 +75,6 @@ pub fn instantiate_with_cw4_groups_governance( ) -> ContractInfo { let cw4_info = app.store_code(cw4_group_contract()); let votemod_info = app.store_code(dao_voting_cw4_contract()); - let snip20_info = app.store_code(snip20_base_contract()); - let snip721_info = app.store_code(snip721_base_contract()); let query_auth = app.store_code(query_auth_contract()); let initial_weights = initial_weights.unwrap_or_default(); @@ -125,7 +114,6 @@ pub fn instantiate_with_cw4_groups_governance( initial_members: initial_weights, query_auth: None, }, - dao_code_hash: core_info.code_hash.clone(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -144,8 +132,6 @@ pub fn instantiate_with_cw4_groups_governance( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "seed".into(), - snip20_code_hash: snip20_info.code_hash, - snip721_code_hash: snip721_info.code_hash, }; let addr = app @@ -183,9 +169,9 @@ fn cw_pre_propose_base_proposal_single() -> Box> { fn snip20_base_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -194,9 +180,7 @@ fn get_default_proposal_module_instantiate( app: &mut App, deposit_info: Option, open_proposal_submission: bool, - proposal_module_code_hash: String, query_auth: ContractInfo, - dao_code_hash: String, ) -> dps::msg::InstantiateMsg { let pre_propose_info = app.store_code(cw_pre_propose_base_proposal_single()); @@ -216,7 +200,6 @@ fn get_default_proposal_module_instantiate( deposit_info, open_proposal_submission, extension: Empty::default(), - proposal_module_code_hash, }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -226,7 +209,6 @@ fn get_default_proposal_module_instantiate( }, close_proposal_on_execution_failure: false, veto: None, - dao_code_hash: dao_code_hash.into(), query_auth: Some(RawContract::new( &query_auth.address.into_string(), &query_auth.code_hash, @@ -236,7 +218,7 @@ fn get_default_proposal_module_instantiate( fn instantiate_snip20_base_default(app: &mut App) -> ContractInfo { let snip20_info = app.store_code(snip20_base_contract()); - let snip20_instantiate = snip20_reference_impl::msg::InstantiateMsg { + let snip20_instantiate = snip20_base::msg::InstantiateMsg { name: "snip20 token".to_string(), symbol: "sniptwenty".to_string(), decimals: 6, @@ -309,7 +291,7 @@ fn create_viewing_key(app: &mut App, contract_info: ContractInfo, sender: &str) } fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: &str) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { entropy: "entropy".to_string(), padding: None, }; @@ -317,8 +299,8 @@ fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: & .execute_contract(Addr::unchecked(addr), &contract_info, &msg, &[]) .unwrap(); let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { viewing_key = key; }; viewing_key @@ -342,9 +324,7 @@ fn setup_default_test( app, deposit_info, open_proposal_submission, - dps_info.code_hash.clone(), query_auth, - core_info.code_hash.clone(), ); let core_contract_info = instantiate_with_cw4_groups_governance( @@ -491,7 +471,7 @@ fn increase_allowance( app.execute_contract( Addr::unchecked(sender), &snip20_contract_info, - &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { + &snip20_base::msg::ExecuteMsg::IncreaseAllowance { spender: receiver.to_string(), amount, expiration: None, @@ -547,16 +527,16 @@ fn get_balance_snip20, C: Into, U: Into, K: Into address: U, key: K, ) -> Uint128 { - let msg = snip20_reference_impl::msg::QueryMsg::Balance { + let msg = snip20_base::msg::QueryMsg::Balance { address: address.into(), key: key.into(), }; - let result: snip20_reference_impl::msg::QueryAnswer = app + let result: snip20_base::msg::QueryAnswer = app .wrap() .query_wasm_smart(code_hash, contract_addr, &msg) .unwrap(); let mut balance = Uint128::zero(); - if let snip20_reference_impl::msg::QueryAnswer::Balance { amount } = result { + if let snip20_base::msg::QueryAnswer::Balance { amount } = result { balance = amount; } balance @@ -1594,7 +1574,6 @@ fn test_instantiate_with_zero_native_deposit() { }), open_proposal_submission: false, extension: Empty::default(), - proposal_module_code_hash: "".into(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -1604,7 +1583,6 @@ fn test_instantiate_with_zero_native_deposit() { }, close_proposal_on_execution_failure: false, veto: None, - dao_code_hash: "todo!()".into(), query_auth: None, } }; @@ -1667,7 +1645,6 @@ fn test_instantiate_with_zero_snip20_deposit() { }), open_proposal_submission: false, extension: Empty::default(), - proposal_module_code_hash: "todo!()".into(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -1677,7 +1654,6 @@ fn test_instantiate_with_zero_snip20_deposit() { }, close_proposal_on_execution_failure: false, veto: None, - dao_code_hash: "todo!()".into(), query_auth: None, } }; diff --git a/contracts/proposal/dao-proposal-condorcet/Cargo.toml b/contracts/proposal/dao-proposal-condorcet/Cargo.toml index 4b90829..4976849 100644 --- a/contracts/proposal/dao-proposal-condorcet/Cargo.toml +++ b/contracts/proposal/dao-proposal-condorcet/Cargo.toml @@ -32,6 +32,7 @@ secret-toolkit ={ workspace = true } secret-cw-controllers ={ workspace = true } shade-protocol ={ workspace = true } query_auth ={ workspace = true } +dao-utils ={ workspace = true} [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/contracts/proposal/dao-proposal-condorcet/src/config.rs b/contracts/proposal/dao-proposal-condorcet/src/config.rs index 73969e5..29ba1ea 100644 --- a/contracts/proposal/dao-proposal-condorcet/src/config.rs +++ b/contracts/proposal/dao-proposal-condorcet/src/config.rs @@ -13,7 +13,6 @@ pub struct UncheckedConfig { pub voting_period: Duration, pub min_voting_period: Option, pub close_proposals_on_execution_failure: bool, - pub dao_code_hash: String, } #[cw_serde] diff --git a/contracts/proposal/dao-proposal-condorcet/src/contract.rs b/contracts/proposal/dao-proposal-condorcet/src/contract.rs index eb799ef..c466433 100644 --- a/contracts/proposal/dao-proposal-condorcet/src/contract.rs +++ b/contracts/proposal/dao-proposal-condorcet/src/contract.rs @@ -6,6 +6,7 @@ use cosmwasm_std::{ }; use dao_interface::state::AnyContractInfo; +use dao_utils::query::get_contract_code_hash; use dao_voting::reply::TaggedReplyId; use dao_voting::voting::{get_total_power, get_voting_power}; use secret_cw2::set_contract_version; @@ -35,7 +36,7 @@ pub fn instantiate( deps.storage, &AnyContractInfo { addr: info.sender.clone(), - code_hash: msg.dao_code_hash.clone(), + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().into()).unwrap_or_default(), }, )?; CONFIG.save(deps.storage, &msg.into_checked()?)?; diff --git a/contracts/proposal/dao-proposal-condorcet/src/testing/instantiation.rs b/contracts/proposal/dao-proposal-condorcet/src/testing/instantiation.rs index e45c43c..560e193 100644 --- a/contracts/proposal/dao-proposal-condorcet/src/testing/instantiation.rs +++ b/contracts/proposal/dao-proposal-condorcet/src/testing/instantiation.rs @@ -25,7 +25,6 @@ fn test_instantiate_conflicting_proposal_durations() { voting_period: Duration::Height(10), min_voting_period: Some(Duration::Height(11)), close_proposals_on_execution_failure: true, - dao_code_hash: "dao_code_hash".to_string(), }) .build(); } @@ -40,7 +39,6 @@ fn test_instantiate_conflicting_duration_types() { voting_period: Duration::Height(10), min_voting_period: Some(Duration::Time(9)), close_proposals_on_execution_failure: true, - dao_code_hash: "dao_code_hash".to_string(), }) .build(); } @@ -52,7 +50,6 @@ fn test_instantiate_open_til_expiry() { voting_period: Duration::Height(10), min_voting_period: Some(Duration::Height(10)), close_proposals_on_execution_failure: true, - dao_code_hash: "dao_code_hash".to_string(), }) .build(); SuiteBuilder::with_config(UncheckedConfig { @@ -60,7 +57,6 @@ fn test_instantiate_open_til_expiry() { voting_period: Duration::Time(10), min_voting_period: Some(Duration::Time(10)), close_proposals_on_execution_failure: true, - dao_code_hash: "dao_code_hash".to_string(), }) .build(); } diff --git a/contracts/proposal/dao-proposal-condorcet/src/testing/suite.rs b/contracts/proposal/dao-proposal-condorcet/src/testing/suite.rs index 4b107c0..fff71a7 100644 --- a/contracts/proposal/dao-proposal-condorcet/src/testing/suite.rs +++ b/contracts/proposal/dao-proposal-condorcet/src/testing/suite.rs @@ -37,7 +37,6 @@ impl Default for SuiteBuilder { voting_period: Duration::Time(60 * 60 * 24 * 7), min_voting_period: Some(Duration::Time(60 * 60 * 24)), close_proposals_on_execution_failure: true, - dao_code_hash: "dao_code_hash".to_string(), }, with_proposal: None, with_voters: vec![("sender".to_string(), 10)], diff --git a/contracts/proposal/dao-proposal-multiple/src/contract.rs b/contracts/proposal/dao-proposal-multiple/src/contract.rs index c4e2e7e..eec0f1c 100644 --- a/contracts/proposal/dao-proposal-multiple/src/contract.rs +++ b/contracts/proposal/dao-proposal-multiple/src/contract.rs @@ -13,6 +13,7 @@ use dao_hooks::vote::new_vote_hooks; use dao_interface::replies::parse_reply_address_from_event; use dao_interface::state::{AnyContractInfo, VotingModuleInfo}; use dao_interface::voting::IsActiveResponse; +use dao_utils::query::get_contract_code_hash; use dao_voting::reply::{ failed_pre_propose_module_hook_id, mask_proposal_execution_proposal_id, TaggedReplyId, }; @@ -64,7 +65,7 @@ pub fn instantiate( DAO.save( deps.storage, &AnyContractInfo { - code_hash: msg.dao_code_hash, + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().into()).unwrap_or_default(), addr: info.sender.clone(), }, )?; diff --git a/contracts/proposal/dao-proposal-multiple/src/msg.rs b/contracts/proposal/dao-proposal-multiple/src/msg.rs index 583e1d1..473200a 100644 --- a/contracts/proposal/dao-proposal-multiple/src/msg.rs +++ b/contracts/proposal/dao-proposal-multiple/src/msg.rs @@ -46,10 +46,6 @@ pub struct InstantiateMsg { /// During this period an oversight account (`veto.vetoer`) can /// veto the proposal. pub veto: Option, - - // dao code hash - pub dao_code_hash: String, - pub query_auth: Option, } diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index 7ef6e99..fcd7c98 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -8,7 +8,6 @@ use shade_protocol::utils::asset::RawContract; pub(crate) fn get_default_token_dao_proposal_module_instantiate( query_auth: RawContract, - dao_code_hash: String, ) -> InstantiateMsg { InstantiateMsg { veto: None, @@ -21,7 +20,6 @@ pub(crate) fn get_default_token_dao_proposal_module_instantiate( allow_revoting: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, close_proposal_on_execution_failure: true, - dao_code_hash, query_auth: Some(query_auth), } } diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs index 58a7059..a3cc5f0 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs @@ -41,9 +41,7 @@ fn setup_test(sender: &str) -> CommonTest { RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, - }, - "dao_dao_code_hash".to_string(), - ); + } ); let proposal_multiple_contract_info = app .instantiate_contract( proposal_module_contract_info, diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index 1c3e864..92e91f4 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -53,7 +53,7 @@ dao-voting-snip721-staked = { workspace = true } dao-pre-propose-single = { workspace = true } cw-denom = { workspace = true } snip20-stake = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } snip721-reference-impl = { workspace = true } cw4 = { workspace = true } cw4-group = { workspace = true } diff --git a/contracts/proposal/dao-proposal-single/src/contract.rs b/contracts/proposal/dao-proposal-single/src/contract.rs index f3afbc5..a1b5ca9 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -13,6 +13,7 @@ use dao_hooks::vote::new_vote_hooks; use dao_interface::replies::parse_reply_address_from_event; use dao_interface::state::{AnyContractInfo, VotingModuleInfo}; use dao_interface::voting::IsActiveResponse; +use dao_utils::query::get_contract_code_hash; use dao_voting::pre_propose::{PreProposeInfo, ProposalCreationPolicy}; use dao_voting::proposal::{ SingleChoiceProposeMsg as ProposeMsg, DEFAULT_LIMIT, MAX_PROPOSAL_SIZE, @@ -63,7 +64,7 @@ pub fn instantiate( DAO.save( deps.storage, &AnyContractInfo { - code_hash: msg.dao_code_hash, + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().into()).unwrap_or_default(), addr: info.sender.clone(), }, )?; @@ -1153,14 +1154,13 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result - // match sub_msg_response.data { - // Some(data) => Ok(Response::new() - // .add_attribute("update_pre_propose_module", address.clone().to_string()) - // .set_data(data)), - // None => Ok(Response::new() - // .add_attribute("update_pre_propose_module", address.to_string())), - // } - Ok(Response::new().add_attribute("update_pre_propose_module", address.to_string())) + match sub_msg_response.data { + Some(data) => Ok(Response::new() + .add_attribute("update_pre_propose_module", address.clone().to_string()) + .set_data(data)), + None => Ok(Response::new() + .add_attribute("update_pre_propose_module", address.to_string())), + } } SubMsgResult::Err(_) => todo!(), }, diff --git a/contracts/proposal/dao-proposal-single/src/msg.rs b/contracts/proposal/dao-proposal-single/src/msg.rs index 9c6db11..27c0d41 100644 --- a/contracts/proposal/dao-proposal-single/src/msg.rs +++ b/contracts/proposal/dao-proposal-single/src/msg.rs @@ -49,9 +49,6 @@ pub struct InstantiateMsg { /// During this period an oversight account (`veto.vetoer`) can /// veto the proposal. pub veto: Option, - /// Code hash of dao - pub dao_code_hash: String, - pub query_auth: Option, } diff --git a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs index cc083ca..ef104d1 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs @@ -359,7 +359,6 @@ pub fn test_executed_prop_state_remains_after_vote_swing() { false, ), close_proposal_on_execution_failure: true, - dao_code_hash: "".to_string(), query_auth: None, }; @@ -556,7 +555,6 @@ pub fn test_passed_prop_state_remains_after_vote_swing() { false, ), close_proposal_on_execution_failure: true, - dao_code_hash: "".into(), query_auth: None, }; @@ -635,7 +633,7 @@ pub fn test_passed_prop_state_remains_after_vote_swing() { ); // if the proposal passes, it should mint 100_000_000 tokens to "threshold" - let msg = snip20_reference_impl::msg::ExecuteMsg::Mint { + let msg = snip20_base::msg::ExecuteMsg::Mint { recipient: "threshold".to_string(), amount: Uint128::new(100_000_000), memo: None, diff --git a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs index 668fdd2..79adf7b 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs @@ -5,9 +5,9 @@ use secret_multi_test::{Contract, ContractWrapper}; pub(crate) fn snip20_base_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -21,15 +21,6 @@ pub(crate) fn cw4_group_contract() -> Box> { Box::new(contract) } -pub(crate) fn snip721_base_contract() -> Box> { - let contract = ContractWrapper::new( - snip721_reference_impl::contract::execute, - snip721_reference_impl::contract::instantiate, - snip721_reference_impl::contract::query, - ); - Box::new(contract) -} - pub(crate) fn snip20_stake_contract() -> Box> { let contract = ContractWrapper::new( snip20_stake::contract::execute, @@ -78,15 +69,6 @@ pub(crate) fn native_staked_balances_voting_contract() -> Box Box> { - let contract = ContractWrapper::new( - dao_voting_snip721_staked::contract::execute, - dao_voting_snip721_staked::contract::instantiate, - dao_voting_snip721_staked::contract::query, - ); - Box::new(contract) -} - pub(crate) fn cw_core_contract() -> Box> { let contract = ContractWrapper::new( dao_dao_core::contract::execute, diff --git a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs index f736c13..6ad44e8 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs @@ -133,7 +133,6 @@ where allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info, - dao_code_hash: "".into(), query_auth: None, }; @@ -178,7 +177,7 @@ where address: token_addr.clone(), code_hash: code_hash.clone(), }, - &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { + &snip20_base::msg::ExecuteMsg::IncreaseAllowance { spender: pre_propose_module.address.clone().to_string(), amount, expiration: None, diff --git a/contracts/proposal/dao-proposal-single/src/testing/execute.rs b/contracts/proposal/dao-proposal-single/src/testing/execute.rs index 300a1d8..eb1c73a 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/execute.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/execute.rs @@ -10,7 +10,7 @@ use dao_voting::{ proposal::SingleChoiceProposeMsg as ProposeMsg, voting::Vote, }; use shade_protocol::basic_staking::Auth; -use snip20_reference_impl::msg::InitialBalance; +use snip20_base::msg::InitialBalance; use crate::{ msg::{ExecuteMsg, QueryMsg}, @@ -63,7 +63,7 @@ pub(crate) fn make_proposal( address: addr, code_hash, }, - &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { + &snip20_base::msg::ExecuteMsg::IncreaseAllowance { spender: pre_propose.to_string(), amount, expiration: None, @@ -346,7 +346,7 @@ pub(crate) fn mint_snip20s( address: snip20_contract.clone(), code_hash: snip20_contract_code_hash.clone(), }, - &snip20_reference_impl::msg::ExecuteMsg::Mint { + &snip20_base::msg::ExecuteMsg::Mint { recipient: receiver.to_string(), amount: Uint128::new(amount), memo: None, @@ -361,7 +361,7 @@ pub(crate) fn mint_snip20s( pub(crate) fn instantiate_sni20_base_default(app: &mut App) -> ContractInfo { let snip20_info = app.store_code(snip20_base_contract()); - let snip20_instantiate = snip20_reference_impl::msg::InstantiateMsg { + let snip20_instantiate = snip20_base::msg::InstantiateMsg { name: "snip20 token".to_string(), symbol: "sniptwenty".to_string(), decimals: 6, @@ -605,7 +605,7 @@ pub(crate) fn create_snip20_viewing_key( contract_info: ContractInfo, info: MessageInfo, ) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { entropy: "entropy".to_string(), padding: None, }; @@ -613,8 +613,8 @@ pub(crate) fn create_snip20_viewing_key( .execute_contract(info.sender, &contract_info, &msg, &[]) .unwrap(); let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { viewing_key = key; }; viewing_key diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 4a066b0..89e0651 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -16,7 +16,6 @@ use dao_voting::{ threshold::{ActiveThreshold, PercentageThreshold, Threshold::ThresholdQuorum}, }; use dao_voting_cw4::msg::GroupContract; -use snip721_reference_impl::msg::ReceiverInfo; use crate::msg::InstantiateMsg; @@ -25,7 +24,6 @@ use super::{ cw4_group_contract, cw4_voting_contract, cw_core_contract, native_staked_balances_voting_contract, proposal_single_contract, query_auth_contract, snip20_base_contract, snip20_stake_contract, snip20_staked_balances_voting_contract, - snip721_base_contract, snip721_stake_contract, }, execute::create_viewing_key, CREATOR_ADDR, @@ -38,8 +36,6 @@ pub(crate) fn get_pre_propose_info( ) -> PreProposeInfo { let pre_propose_contract = app.store_code(crate::testing::contracts::pre_propose_single_contract()); - let proposal_single_contract = - app.store_code(crate::testing::contracts::proposal_single_contract()); PreProposeInfo::ModuleMayPropose { info: ModuleInstantiateInfo { code_id: pre_propose_contract.code_id, @@ -48,7 +44,6 @@ pub(crate) fn get_pre_propose_info( deposit_info, open_proposal_submission, extension: Empty::default(), - proposal_module_code_hash: proposal_single_contract.code_hash.clone(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -59,7 +54,6 @@ pub(crate) fn get_pre_propose_info( } pub(crate) fn get_default_token_dao_proposal_module_instantiate(app: &mut App) -> InstantiateMsg { - let dao_info = app.store_code(cw_core_contract()); InstantiateMsg { veto: None, threshold: ThresholdQuorum { @@ -82,7 +76,6 @@ pub(crate) fn get_default_token_dao_proposal_module_instantiate(app: &mut App) - false, ), close_proposal_on_execution_failure: true, - dao_code_hash: dao_info.code_hash, query_auth: None, } } @@ -91,7 +84,6 @@ pub(crate) fn get_default_token_dao_proposal_module_instantiate(app: &mut App) - pub(crate) fn get_default_non_token_dao_proposal_module_instantiate( app: &mut App, ) -> InstantiateMsg { - let dao_info = app.store_code(cw_core_contract()); InstantiateMsg { veto: None, @@ -105,172 +97,10 @@ pub(crate) fn get_default_non_token_dao_proposal_module_instantiate( allow_revoting: false, pre_propose_info: get_pre_propose_info(app, None, false), close_proposal_on_execution_failure: true, - dao_code_hash: dao_info.code_hash, query_auth: None, } } -pub(crate) fn _instantiate_with_staked_snip721_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> ContractInfo { - let proposal_module_info = app.store_code(proposal_single_contract()); - let query_auth = app.store_code(query_auth_contract()); - let snip20_info = app.store_code(snip20_base_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![InitialBalance { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|InitialBalance { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let snip721_info = app.store_code(snip721_base_contract()); - let snip721_stake_info = app.store_code(snip721_stake_contract()); - let core_info = app.store_code(cw_core_contract()); - - let nft_contract_info = app - .instantiate_contract( - snip721_info.clone(), - Addr::unchecked("ekez"), - &snip721_reference_impl::msg::InstantiateMsg { - symbol: "token".to_string(), - name: "ekez token best token".to_string(), - admin: Some("ekez".to_string()), - entropy: "entropy".to_string(), - royalty_info: None, - config: None, - post_init_callback: None, - }, - &[], - "nft-staking", - None, - ) - .unwrap(); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - dao_uri: None, - image_url: None, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: snip721_stake_info.code_id, - code_hash: snip721_stake_info.code_hash.clone(), - msg: to_binary(&dao_voting_snip721_staked::msg::InstantiateMsg { - unstaking_duration: None, - nft_contract: dao_voting_snip721_staked::msg::NftContract::Existing { - address: nft_contract_info.address.clone().to_string(), - code_hash: nft_contract_info.code_hash.clone(), - }, - active_threshold: None, - dao_code_hash: core_info.code_hash.clone(), - query_auth: None, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_info.code_id, - code_hash: proposal_module_info.code_hash.clone(), - msg: to_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - query_auth_code_id: query_auth.code_id, - query_auth_code_hash: query_auth.code_hash, - prng_seed: "seed".to_string(), - snip20_code_hash: snip20_info.code_hash, - snip721_code_hash: snip721_info.code_hash.clone(), - }; - - let core_contract_info = app - .instantiate_contract( - core_info.clone(), - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_contract_info.code_hash.clone(), - core_contract_info.address.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let staking_addr = core_state.voting_module; - let staking_code_hash = core_state.voting_module_code_hash; - - for InitialBalance { address, amount } in initial_balances { - for i in 0..amount.u128() { - app.execute_contract( - Addr::unchecked("ekez"), - &nft_contract_info.clone(), - &snip721_reference_impl::msg::ExecuteMsg::MintNft { - token_id: format!("{address}_{i}").into(), - owner: Some(address.clone()), - public_metadata: None, - private_metadata: None, - serial_number: None, - royalty_info: None, - transferable: Some(true), - memo: None, - padding: None, - }, - &[], - ) - .unwrap(); - app.execute_contract( - Addr::unchecked(address.clone()), - &nft_contract_info.clone(), - &snip721_reference_impl::msg::ExecuteMsg::SendNft { - contract: staking_addr.to_string(), - token_id: format!("{address}_{i}"), - msg: Some(to_binary("").unwrap()), - receiver_info: Some(ReceiverInfo { - recipient_code_hash: staking_code_hash.clone(), - also_implements_batch_receive_nft: None, - }), - memo: None, - padding: None, - }, - &[], - ) - .unwrap(); - } - } - - // Update the block so that staked balances appear. - app.update_block(|block| block.height += 1); - - core_contract_info -} - pub(crate) fn instantiate_with_native_staked_balances_governance( app: &mut App, proposal_module_instantiate: InstantiateMsg, @@ -320,7 +150,6 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( }, unstaking_duration: None, active_threshold: None, - dao_code_hash: core_info.code_hash.clone(), query_auth: None, }) .unwrap(), @@ -340,8 +169,6 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "seeed".to_string(), - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), }; let core_contract_info = app @@ -476,7 +303,6 @@ pub(crate) fn instantiate_with_staked_balances_governance( unstaking_duration: Some(Duration::Height(6)), initial_dao_balance: None, }, - dao_code_hash: core_info.code_hash.clone(), query_auth: None, }) .unwrap(), @@ -496,8 +322,6 @@ pub(crate) fn instantiate_with_staked_balances_governance( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "seed".to_string(), - snip20_code_hash: snip20_info.code_hash.clone(), - snip721_code_hash: "".to_string(), }; let core_contract_info = app @@ -565,7 +389,7 @@ pub(crate) fn instantiate_with_staked_balances_governance( address: token_contract.addr.clone(), code_hash: token_contract.code_hash.clone(), }, - &snip20_reference_impl::msg::ExecuteMsg::Send { + &snip20_base::msg::ExecuteMsg::Send { amount, msg: Some( to_binary(&snip20_stake::msg::ReceiveMsg::Stake { @@ -637,7 +461,6 @@ pub(crate) fn instantiate_with_staking_active_threshold( staking_code_hash: snip20_staking_info.code_hash.clone(), }, active_threshold, - dao_code_hash: core_info.code_hash.clone(), query_auth: None, }) .unwrap(), @@ -657,8 +480,6 @@ pub(crate) fn instantiate_with_staking_active_threshold( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "seed".into(), - snip20_code_hash: "todo!()".to_string(), - snip721_code_hash: "todo!()".to_string(), }; app.instantiate_contract( @@ -726,7 +547,6 @@ pub(crate) fn instantiate_with_cw4_groups_governance( cw4_group_code_hash: cw4_info.code_hash, query_auth: None, }, - dao_code_hash: core_info.code_hash.clone(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -745,8 +565,6 @@ pub(crate) fn instantiate_with_cw4_groups_governance( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "todo!()".to_string(), - snip20_code_hash: "todo!()".to_string(), - snip721_code_hash: "todo!()".to_string(), }; let addr = app diff --git a/contracts/proposal/dao-proposal-single/src/testing/mod.rs b/contracts/proposal/dao-proposal-single/src/testing/mod.rs index 59cf467..c70b1f6 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/mod.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/mod.rs @@ -1,5 +1,5 @@ mod adversarial_tests; -mod contracts; +pub mod contracts; mod do_votes; mod execute; mod instantiate; diff --git a/contracts/proposal/dao-proposal-single/src/testing/queries.rs b/contracts/proposal/dao-proposal-single/src/testing/queries.rs index 2ff4c8b..a383d81 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/queries.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/queries.rs @@ -258,17 +258,17 @@ pub(crate) fn query_balance_cw20< address: U, key: K, ) -> Uint128 { - let msg = snip20_reference_impl::msg::QueryMsg::Balance { + let msg = snip20_base::msg::QueryMsg::Balance { address: address.into(), key: key.into(), }; let mut balance_amount = Uint128::zero(); - let result: snip20_reference_impl::msg::QueryAnswer = app + let result: snip20_base::msg::QueryAnswer = app .wrap() .query_wasm_smart(code_hash, contract_addr, &msg) .unwrap(); match result { - snip20_reference_impl::msg::QueryAnswer::Balance { amount } => { + snip20_base::msg::QueryAnswer::Balance { amount } => { balance_amount = amount; } _ => (), diff --git a/contracts/proposal/dao-proposal-single/src/testing/tests.rs b/contracts/proposal/dao-proposal-single/src/testing/tests.rs index f6c54c6..34ba6c9 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/tests.rs @@ -536,7 +536,7 @@ fn test_proposal_message_execution() { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -757,7 +757,7 @@ fn test_proposal_message_timelock_execution() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -968,7 +968,7 @@ fn test_open_proposal_veto_unauthorized() { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1076,7 +1076,7 @@ fn test_open_proposal_veto_with_early_veto_flag_disabled() { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1179,7 +1179,7 @@ fn test_open_proposal_veto_with_no_timelock() { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1290,7 +1290,7 @@ fn test_vetoed_proposal_veto() { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1421,7 +1421,7 @@ fn test_open_proposal_veto_early() { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1538,7 +1538,7 @@ fn test_timelocked_proposal_veto_unauthorized() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1699,7 +1699,7 @@ fn test_timelocked_proposal_veto_expired_timelock() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1840,7 +1840,7 @@ fn test_timelocked_proposal_execute_no_early_exec() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -1985,7 +1985,7 @@ fn test_timelocked_proposal_execute_early() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -2144,7 +2144,7 @@ fn test_timelocked_proposal_execute_active_timelock_unauthorized() -> anyhow::Re WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -2295,7 +2295,7 @@ fn test_timelocked_proposal_execute_expired_timelock_not_vetoer() -> anyhow::Res WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -2460,7 +2460,7 @@ fn test_proposal_message_timelock_veto() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -2678,7 +2678,7 @@ fn test_proposal_message_timelock_early_execution() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -2842,7 +2842,7 @@ fn test_proposal_message_timelock_veto_before_passed() { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -2987,7 +2987,7 @@ fn test_veto_only_members_execute_proposal() -> anyhow::Result<()> { WasmMsg::Execute { contract_addr: gov_token_info.addr.clone().to_string(), code_hash: gov_token_info.code_hash.clone(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Mint { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Mint { recipient: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), memo: None, @@ -3508,7 +3508,7 @@ fn test_cant_execute_not_member_when_proposal_created() { address: gov_token_info.addr.clone(), code_hash: gov_token_info.code_hash.clone(), }, - &snip20_reference_impl::msg::ExecuteMsg::Send { + &snip20_base::msg::ExecuteMsg::Send { recipient: staking_contract.addr.clone().to_string(), recipient_code_hash: Some(staking_contract.code_hash.clone()), amount: Uint128::new(10_000_000), @@ -4217,7 +4217,7 @@ fn test_active_threshold_absolute() { .unwrap(); assert!(matches!(err, ContractError::InactiveDao {})); - let msg = snip20_reference_impl::msg::ExecuteMsg::Send { + let msg = snip20_base::msg::ExecuteMsg::Send { recipient: staking_contract.addr.clone().to_string(), amount: Uint128::new(100), msg: Some( @@ -4378,7 +4378,7 @@ fn test_active_threshold_percent() { .unwrap(); assert!(matches!(err, ContractError::InactiveDao {})); - let msg = snip20_reference_impl::msg::ExecuteMsg::Send { + let msg = snip20_base::msg::ExecuteMsg::Send { recipient: staking_contract.addr.clone().to_string(), amount: Uint128::new(20_000_000), msg: Some( @@ -5637,7 +5637,6 @@ fn test_proposal_count_initialized_to_zero() { allow_revoting: false, pre_propose_info, close_proposal_on_execution_failure: true, - dao_code_hash: "todo!()".into(), query_auth: None, }, Some(vec![ diff --git a/contracts/staking/snip20-stake-external-rewards/Cargo.toml b/contracts/staking/snip20-stake-external-rewards/Cargo.toml index a84024a..c3e56bc 100644 --- a/contracts/staking/snip20-stake-external-rewards/Cargo.toml +++ b/contracts/staking/snip20-stake-external-rewards/Cargo.toml @@ -36,7 +36,7 @@ cw20-stake-external-rewards-v1 = { workspace = true } cw20-013 = { package = "cw20", version = "0.13" } shade-protocol ={ workspace = true } query_auth ={ workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } [dev-dependencies] secret-multi-test = { workspace = true } diff --git a/contracts/staking/snip20-stake-external-rewards/src/contract.rs b/contracts/staking/snip20-stake-external-rewards/src/contract.rs index ab66594..0fc192f 100644 --- a/contracts/staking/snip20-stake-external-rewards/src/contract.rs +++ b/contracts/staking/snip20-stake-external-rewards/src/contract.rs @@ -501,7 +501,7 @@ mod tests { use cw_ownable::{Action, Ownership, OwnershipError}; use secret_utils::Duration; use shade_protocol::basic_staking::Auth; - use snip20_reference_impl::msg::{ExecuteMsg as Snip20ExecuteMsg, InitialBalance, QueryAnswer}; + use snip20_base::msg::{ExecuteMsg as Snip20ExecuteMsg, InitialBalance, QueryAnswer}; use secret_multi_test::{ next_block, App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, @@ -535,9 +535,9 @@ mod tests { pub fn contract_snip20() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -557,7 +557,7 @@ mod tests { fn instantiate_snip20(app: &mut App, initial_balances: Vec) -> ContractInfo { let snip20_info = app.store_code(contract_snip20()); - let msg = snip20_reference_impl::msg::InstantiateMsg { + let msg = snip20_base::msg::InstantiateMsg { name: String::from("Test"), symbol: String::from("TEST"), decimals: 6, @@ -683,7 +683,7 @@ mod tests { contract_info: ContractInfo, info: MessageInfo, ) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { entropy: "entropy".to_string(), padding: None, }; @@ -691,9 +691,9 @@ mod tests { .execute_contract(info.sender, &contract_info, &msg, &[]) .unwrap(); let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { viewing_key = key; }; viewing_key @@ -790,8 +790,8 @@ mod tests { address: String, key: String, ) -> Uint128 { - let msg = snip20_reference_impl::msg::QueryMsg::Balance { address, key }; - let result: snip20_reference_impl::msg::QueryAnswer = app + let msg = snip20_base::msg::QueryMsg::Balance { address, key }; + let result: snip20_base::msg::QueryAnswer = app .wrap() .query_wasm_smart(snip20_info.code_hash, snip20_info.address.to_string(), &msg) .unwrap(); @@ -1522,7 +1522,7 @@ mod tests { }, }) .unwrap(); - let fund_msg = snip20_reference_impl::msg::ExecuteMsg::Send { + let fund_msg = snip20_base::msg::ExecuteMsg::Send { recipient: reward_contract_info.clone().address.into_string(), recipient_code_hash: Some(reward_contract_info.clone().code_hash), amount: Uint128::new(100), @@ -1592,7 +1592,7 @@ mod tests { }, }) .unwrap(); - let fund_msg = snip20_reference_impl::msg::ExecuteMsg::Send { + let fund_msg = snip20_base::msg::ExecuteMsg::Send { recipient: reward_contract_info.clone().address.into_string(), recipient_code_hash: Some(reward_contract_info.clone().code_hash), amount: Uint128::new(100), diff --git a/contracts/staking/snip20-stake-reward-distributor/Cargo.toml b/contracts/staking/snip20-stake-reward-distributor/Cargo.toml index c5eee78..06a3224 100644 --- a/contracts/staking/snip20-stake-reward-distributor/Cargo.toml +++ b/contracts/staking/snip20-stake-reward-distributor/Cargo.toml @@ -31,7 +31,7 @@ secret-toolkit = { workspace = true } secret-storage-plus = { workspace = true} secret-cw-controllers = { workspace = true } secret-cw2 = { workspace = true } -snip20-reference-impl ={ workspace=true } +snip20-base ={ workspace=true } schemars={ workspace=true } shade-protocol = { workspace = true} query_auth ={ workspace = true } diff --git a/contracts/staking/snip20-stake-reward-distributor/src/contract.rs b/contracts/staking/snip20-stake-reward-distributor/src/contract.rs index c305f9e..23b8a91 100644 --- a/contracts/staking/snip20-stake-reward-distributor/src/contract.rs +++ b/contracts/staking/snip20-stake-reward-distributor/src/contract.rs @@ -3,11 +3,10 @@ use std::cmp::min; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - from_binary, to_binary, Addr, CosmosMsg, Reply, StdError, SubMsg, SubMsgResult, Uint128, - WasmMsg, + from_slice, to_binary, Addr, CosmosMsg, Reply, StdError, SubMsg, SubMsgResult, Uint128, WasmMsg }; use secret_toolkit::utils::HandleCallback; -use snip20_reference_impl::msg::{ExecuteAnswer, QueryAnswer}; +use snip20_base::msg::{ExecuteAnswer, QueryAnswer}; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InfoResponse, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -178,7 +177,7 @@ pub fn execute_update_owner( } pub fn validate_snip20(deps: Deps, snip20_addr: Addr, snip20_code_hash: String) -> bool { - let response: Result = + let response: Result = deps.querier.query_wasm_smart( snip20_code_hash, snip20_addr, @@ -208,7 +207,7 @@ fn get_distribution_msg(deps: Deps, env: &Env) -> Result StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { match msg.id { - EXECUTE_TOKEN_VIEWING_KEY_ID => { - match msg.result { - SubMsgResult::Ok(res) => { - // let mut token_viewing_key=TOKEN_VIEWING_KEY.load(deps.storage).unwrap_or_default(); - let data: snip20_reference_impl::msg::ExecuteAnswer = - from_binary(&res.data.unwrap())?; - let mut viewing_key = String::new(); - if let ExecuteAnswer::CreateViewingKey { key } = data { - viewing_key = key; - } - TOKEN_VIEWING_KEY.save(deps.storage, &viewing_key)?; - Ok(Response::new().add_attribute("action", "create_token_viewing_key")) - } - SubMsgResult::Err(_) => Err(ContractError::TokenExecuteError {}), + EXECUTE_TOKEN_VIEWING_KEY_ID => match msg.result { + SubMsgResult::Ok(res) => { + let raw_data = res.data.ok_or_else(|| StdError::generic_err("No data returned"))?; + + // Extract JSON payload starting from the '{' character + let json_data = &raw_data.0[raw_data.0.iter().position(|&b| b == b'{').unwrap_or(0)..]; + let key = match from_slice::(json_data)? { + ExecuteAnswer::CreateViewingKey { key } => key, + _ => return Err(ContractError::Std(StdError::generic_err("Unexpected response type"))), + }; + + TOKEN_VIEWING_KEY.save(deps.storage, &key)?; + Ok(Response::new().add_attribute("action", "create_token_viewing_key")) } - } + SubMsgResult::Err(_) => Err(ContractError::Std(StdError::generic_err("Token execution error"))), + }, _ => Err(ContractError::UnknownReplyId { id: msg.id }), + } } diff --git a/contracts/staking/snip20-stake-reward-distributor/src/tests.rs b/contracts/staking/snip20-stake-reward-distributor/src/tests.rs index 2486458..badabe4 100644 --- a/contracts/staking/snip20-stake-reward-distributor/src/tests.rs +++ b/contracts/staking/snip20-stake-reward-distributor/src/tests.rs @@ -8,16 +8,16 @@ use cosmwasm_std::{from_binary, to_binary, Addr, ContractInfo, Empty, Uint128}; use cw_ownable::{Action, Expiration, Ownership, OwnershipError}; use secret_multi_test::{next_block, App, Contract, ContractWrapper, Executor}; use shade_protocol::utils::asset::RawContract; -use snip20_reference_impl::msg::InitialBalance; +use snip20_base::msg::InitialBalance; const OWNER: &str = "owner"; const OWNER2: &str = "owner2"; pub fn cw20_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -53,7 +53,7 @@ fn distributor_contract() -> Box> { fn instantiate_snip20(app: &mut App, initial_balances: Vec) -> ContractInfo { let contract_info = app.store_code(cw20_contract()); - let msg = snip20_reference_impl::msg::InstantiateMsg { + let msg = snip20_base::msg::InstantiateMsg { name: String::from("Test"), symbol: String::from("TEST"), decimals: 6, @@ -141,16 +141,16 @@ fn get_balance_snip20, C: Into, U: Into, K: Into address: U, key: K, ) -> Uint128 { - let msg = snip20_reference_impl::msg::QueryMsg::Balance { + let msg = snip20_base::msg::QueryMsg::Balance { address: address.into(), key: key.into(), }; - let result: snip20_reference_impl::msg::QueryAnswer = app + let result: snip20_base::msg::QueryAnswer = app .wrap() .query_wasm_smart(code_hash, contract_addr, &msg) .unwrap(); let mut balance = Uint128::zero(); - if let snip20_reference_impl::msg::QueryAnswer::Balance { amount } = result { + if let snip20_base::msg::QueryAnswer::Balance { amount } = result { balance = amount; } balance @@ -175,7 +175,7 @@ fn get_owner(app: &App, contract: &Addr, code_hash: String) -> Ownership { } fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: Addr) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { entropy: "entropy".to_string(), padding: None, }; @@ -183,8 +183,8 @@ fn create_viewing_key_snip20(app: &mut App, contract_info: ContractInfo, addr: A .execute_contract(addr, &contract_info, &msg, &[]) .unwrap(); let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { viewing_key = key; }; viewing_key @@ -361,7 +361,7 @@ fn test_distribute() { let distributor_contract_info = instantiate_distributor(&mut app, msg); - let msg = snip20_reference_impl::msg::ExecuteMsg::Transfer { + let msg = snip20_base::msg::ExecuteMsg::Transfer { recipient: distributor_contract_info.clone().address.to_string(), amount: Uint128::from(1000u128), memo: None, @@ -669,7 +669,7 @@ fn test_withdraw() { }; let distributor_contract_info = instantiate_distributor(&mut app, msg); - let msg = snip20_reference_impl::msg::ExecuteMsg::Transfer { + let msg = snip20_base::msg::ExecuteMsg::Transfer { recipient: distributor_contract_info.clone().address.to_string(), amount: Uint128::from(1000u128), memo: None, @@ -795,7 +795,7 @@ fn test_dao_deploy() { ) .unwrap(); - let msg = snip20_reference_impl::msg::ExecuteMsg::Transfer { + let msg = snip20_base::msg::ExecuteMsg::Transfer { recipient: distributor_contract_info.clone().address.to_string(), amount: Uint128::from(1000u128), memo: None, diff --git a/contracts/staking/snip20-stake/Cargo.toml b/contracts/staking/snip20-stake/Cargo.toml index 2a4f4ea..5700923 100644 --- a/contracts/staking/snip20-stake/Cargo.toml +++ b/contracts/staking/snip20-stake/Cargo.toml @@ -38,7 +38,7 @@ secret-storage-plus = { workspace = true} secret-cw-controllers = { workspace = true } secret-cw2 = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } subtle = { version = "2.2.3", default-features = false } base64 = "0.12.3" diff --git a/contracts/staking/snip20-stake/src/contract.rs b/contracts/staking/snip20-stake/src/contract.rs index acb8dbf..7149a81 100644 --- a/contracts/staking/snip20-stake/src/contract.rs +++ b/contracts/staking/snip20-stake/src/contract.rs @@ -31,7 +31,7 @@ use shade_protocol::query_auth::helpers::{ authenticate_permit, authenticate_vk, PermitAuthentication, }; use shade_protocol::Contract; -use snip20_reference_impl::msg::QueryAnswer; +use snip20_base::msg::QueryAnswer; pub(crate) const CONTRACT_NAME: &str = "crates.io:snip20-stake"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -52,7 +52,7 @@ pub fn instantiate( // though this provides some protection against mistakes where the // wrong address is provided. let token_address = deps.api.addr_validate(&msg.token_address)?; - let token_info: snip20_reference_impl::msg::QueryAnswer = deps.querier.query_wasm_smart( + let token_info: snip20_base::msg::QueryAnswer = deps.querier.query_wasm_smart( msg.token_code_hash.clone().unwrap(), &token_address, &secret_toolkit::snip20::QueryMsg::TokenInfo {}, diff --git a/contracts/staking/snip20-stake/src/tests.rs b/contracts/staking/snip20-stake/src/tests.rs index 79cda3f..7fe0d42 100644 --- a/contracts/staking/snip20-stake/src/tests.rs +++ b/contracts/staking/snip20-stake/src/tests.rs @@ -8,7 +8,7 @@ use secret_multi_test::{next_block, App, AppResponse, Contract, ContractWrapper, use secret_utils::Duration; use secret_utils::Expiration::AtHeight; use shade_protocol::basic_staking::Auth; -use snip20_reference_impl::msg::InitialBalance; +use snip20_base::msg::InitialBalance; use std::borrow::BorrowMut; use crate::msg::{ @@ -35,9 +35,9 @@ fn contract_staking() -> Box> { fn contract_snip20() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -98,7 +98,7 @@ fn instantiate_query_auth(app: &mut App) -> ContractInfo { fn instantiate_snip20(app: &mut App, initial_balances: Vec) -> ContractInfo { let snip20_info = app.store_code(contract_snip20()); - let msg = snip20_reference_impl::msg::InstantiateMsg { + let msg = snip20_base::msg::InstantiateMsg { name: String::from("Test"), symbol: String::from("TEST"), decimals: 6, diff --git a/contracts/test/dao-voting-snip20-balance/Cargo.toml b/contracts/test/dao-voting-snip20-balance/Cargo.toml index f219215..2749c03 100644 --- a/contracts/test/dao-voting-snip20-balance/Cargo.toml +++ b/contracts/test/dao-voting-snip20-balance/Cargo.toml @@ -25,7 +25,7 @@ secret-utils = { workspace = true } thiserror = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } -snip20-reference-impl = { workspace = true} +snip20-base = { workspace = true} shade-protocol ={ workspace = true } secret-toolkit ={ workspace = true } diff --git a/contracts/test/dao-voting-snip20-balance/src/contract.rs b/contracts/test/dao-voting-snip20-balance/src/contract.rs index fdee66c..36bf682 100644 --- a/contracts/test/dao-voting-snip20-balance/src/contract.rs +++ b/contracts/test/dao-voting-snip20-balance/src/contract.rs @@ -7,7 +7,7 @@ use cosmwasm_std::{ use dao_interface::replies::parse_reply_address_from_event; use dao_interface::state::AnyContractInfo; use secret_cw2::set_contract_version; -use snip20_reference_impl::msg::QueryAnswer; +use snip20_base::msg::QueryAnswer; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, TokenInfo}; @@ -67,7 +67,7 @@ pub fn instantiate( return Err(ContractError::InitialBalancesError {}); } - let init_msg = snip20_reference_impl::msg::InstantiateMsg { + let init_msg = snip20_base::msg::InstantiateMsg { name, symbol, decimals, @@ -149,10 +149,10 @@ pub fn query_voting_power_at_height( let token = TOKEN.load(deps.storage)?; let address = deps.api.addr_validate(&address)?; let mut balance_amount = Uint128::zero(); - let balance: snip20_reference_impl::msg::QueryAnswer = deps.querier.query_wasm_smart( + let balance: snip20_base::msg::QueryAnswer = deps.querier.query_wasm_smart( token.code_hash, token.addr, - &snip20_reference_impl::msg::QueryMsg::Balance { + &snip20_base::msg::QueryMsg::Balance { address: address.to_string(), key, }, @@ -170,10 +170,10 @@ pub fn query_voting_power_at_height( pub fn query_total_power_at_height(deps: Deps, env: Env) -> StdResult { let token = TOKEN.load(deps.storage)?; let mut supply = Uint128::zero(); - let info: snip20_reference_impl::msg::QueryAnswer = deps.querier.query_wasm_smart( + let info: snip20_base::msg::QueryAnswer = deps.querier.query_wasm_smart( token.code_hash, token.addr, - &snip20_reference_impl::msg::QueryMsg::TokenInfo {}, + &snip20_base::msg::QueryMsg::TokenInfo {}, )?; if let QueryAnswer::TokenInfo { total_supply, .. } = info { supply = total_supply.unwrap_or_default(); diff --git a/contracts/test/dao-voting-snip20-balance/src/msg.rs b/contracts/test/dao-voting-snip20-balance/src/msg.rs index 9773004..0a76efe 100644 --- a/contracts/test/dao-voting-snip20-balance/src/msg.rs +++ b/contracts/test/dao-voting-snip20-balance/src/msg.rs @@ -15,7 +15,7 @@ pub enum TokenInfo { name: String, symbol: String, decimals: u8, - initial_balances: Vec, + initial_balances: Vec, }, } diff --git a/contracts/test/dao-voting-snip20-balance/src/tests.rs b/contracts/test/dao-voting-snip20-balance/src/tests.rs index 8ab0e5e..f778544 100644 --- a/contracts/test/dao-voting-snip20-balance/src/tests.rs +++ b/contracts/test/dao-voting-snip20-balance/src/tests.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Addr, ContractInfo, Empty, Uint128}; use dao_interface::voting::InfoResponse; use secret_cw2::ContractVersion; use secret_multi_test::{App, Contract, ContractInstantiationInfo, ContractWrapper, Executor}; -use snip20_reference_impl::msg::InitialBalance; +use snip20_base::msg::InitialBalance; use crate::msg::{InstantiateMsg, QueryMsg}; @@ -11,9 +11,9 @@ const CREATOR_ADDR: &str = "creator"; fn snip20_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index 986ad8e..766715b 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -8,6 +8,7 @@ use cw4::{MemberListResponse, MemberResponse, TotalWeightResponse}; use dao_interface::replies::parse_reply_address_from_event; // use cw4_group::msg::InstantiateMsg as Cw4GroupInstantiateMsg; use dao_interface::state::AnyContractInfo; +use dao_utils::query::get_contract_code_hash; use secret_cw2::{get_contract_version, set_contract_version, ContractVersion}; use secret_toolkit::utils::InitCallback; use shade_protocol::basic_staking::Auth; @@ -34,7 +35,7 @@ pub fn instantiate( DAO.save( deps.storage, &AnyContractInfo { - code_hash: msg.dao_code_hash, + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().into_string()).unwrap_or_default(), addr: info.sender.clone(), }, )?; @@ -106,8 +107,9 @@ pub fn instantiate( .add_attribute("action", "instantiate") .add_submessage(sub_msg)) } - GroupContract::Existing { address, code_hash } => { + GroupContract::Existing { address } => { let group_contract = deps.api.addr_validate(&address.clone())?; + let code_hash = get_contract_code_hash(deps.querier, group_contract.clone().into_string()).unwrap_or_default(); // Validate valid group contract that has at least one member. let res: MemberListResponse = deps.querier.query_wasm_smart( diff --git a/contracts/voting/dao-voting-cw4/src/msg.rs b/contracts/voting/dao-voting-cw4/src/msg.rs index ef3c4a0..c62b7a1 100644 --- a/contracts/voting/dao-voting-cw4/src/msg.rs +++ b/contracts/voting/dao-voting-cw4/src/msg.rs @@ -6,7 +6,6 @@ use shade_protocol::utils::asset::RawContract; pub enum GroupContract { Existing { address: String, - code_hash: String, }, New { cw4_group_code_id: u64, @@ -19,7 +18,6 @@ pub enum GroupContract { #[cw_serde] pub struct InstantiateMsg { pub group_contract: GroupContract, - pub dao_code_hash: String, } #[cw_serde] diff --git a/contracts/voting/dao-voting-cw4/src/tests.rs b/contracts/voting/dao-voting-cw4/src/tests.rs index d5ae0f3..9158c6d 100644 --- a/contracts/voting/dao-voting-cw4/src/tests.rs +++ b/contracts/voting/dao-voting-cw4/src/tests.rs @@ -150,7 +150,6 @@ fn setup_test_case(app: &mut App) -> (ContractInfo, ContractInfo) { &query_auth.code_hash.clone().to_string(), )), }, - dao_code_hash: "todo!()".into(), }, ), query_auth, @@ -173,7 +172,6 @@ fn test_instantiate() { initial_members: [].into(), query_auth: None, }, - dao_code_hash: "".to_string(), }; let _err = app .instantiate_contract( @@ -207,7 +205,6 @@ fn test_instantiate() { ], query_auth: None, }, - dao_code_hash: "".to_string(), }; let _err = app .instantiate_contract( @@ -265,9 +262,7 @@ pub fn test_instantiate_existing_contract() { &InstantiateMsg { group_contract: GroupContract::Existing { address: cw4_contract_info.address.clone().to_string(), - code_hash: cw4_contract_info.code_hash.clone(), }, - dao_code_hash: "todo!()".into(), }, &[], "voting module", @@ -304,9 +299,7 @@ pub fn test_instantiate_existing_contract() { let msg = InstantiateMsg { group_contract: GroupContract::Existing { address: cw4_contract_info.address.clone().to_string(), - code_hash: cw4_contract_info.code_hash.clone(), }, - dao_code_hash: "".into(), }; let _err = app .instantiate_contract( @@ -396,10 +389,10 @@ fn test_contract_info() { ) .unwrap(); assert_eq!( - dao_contract, + dao_contract.clone(), AnyContractInfo { addr: Addr::unchecked(DAO_ADDR), - code_hash: "todo!()".into(), + code_hash: dao_contract.code_hash, } ); } @@ -909,7 +902,6 @@ fn test_duplicate_member() { ], query_auth: None, }, - dao_code_hash: "".into(), }; // Previous versions voting power was 100, due to no dedup. // Now we error diff --git a/contracts/voting/dao-voting-snip20-staked/Cargo.toml b/contracts/voting/dao-voting-snip20-staked/Cargo.toml index c6364d6..1a9a34f 100644 --- a/contracts/voting/dao-voting-snip20-staked/Cargo.toml +++ b/contracts/voting/dao-voting-snip20-staked/Cargo.toml @@ -25,7 +25,7 @@ schemars={ workspace=true } secret-storage-plus = { workspace = true } secret-utils = { workspace = true } secret-cw2 = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } secret-toolkit = { workspace = true} snip20-stake = { workspace = true, features = ["library"] } thiserror = { workspace = true } diff --git a/contracts/voting/dao-voting-snip20-staked/src/contract.rs b/contracts/voting/dao-voting-snip20-staked/src/contract.rs index e0fea6c..4064af3 100644 --- a/contracts/voting/dao-voting-snip20-staked/src/contract.rs +++ b/contracts/voting/dao-voting-snip20-staked/src/contract.rs @@ -16,12 +16,13 @@ use dao_interface::msg::InitialBalance; use dao_interface::replies::parse_reply_address_from_event; use dao_interface::state::AnyContractInfo; use dao_interface::voting::IsActiveResponse; +use dao_utils::query::get_contract_code_hash; use dao_voting::threshold::ActiveThreshold; use dao_voting::threshold::ActiveThresholdResponse; use secret_cw2::{get_contract_version, set_contract_version, ContractVersion}; use secret_toolkit::utils::InitCallback; use shade_protocol::basic_staking::Auth; -use snip20_reference_impl::msg::QueryAnswer; +use snip20_base::msg::QueryAnswer; use std::convert::TryInto; pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-snip20-staked"; @@ -48,7 +49,7 @@ pub fn instantiate( deps.storage, &AnyContractInfo { addr: info.sender.clone(), - code_hash: msg.dao_code_hash, + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().to_string()).unwrap_or_default(), }, )?; @@ -64,10 +65,10 @@ pub fn instantiate( match msg.token_info { Snip20TokenInfo::Existing { address, - code_hash, staking_contract, } => { let address = deps.api.addr_validate(&address)?; + let code_hash = get_contract_code_hash(deps.querier, address.clone().into()).unwrap_or_default(); let token_contract = AnyContractInfo { addr: address.clone(), code_hash: code_hash.clone(), @@ -86,10 +87,10 @@ pub fn instantiate( match staking_contract { StakingInfo::Existing { staking_contract_address, - staking_contract_code_hash, } => { let staking_contract_address = deps.api.addr_validate(&staking_contract_address)?; + let staking_contract_code_hash = get_contract_code_hash(deps.querier, staking_contract_address.clone().into()).unwrap_or_default(); let staking_contract = AnyContractInfo { addr: staking_contract_address.clone(), code_hash: staking_contract_code_hash.clone(), diff --git a/contracts/voting/dao-voting-snip20-staked/src/msg.rs b/contracts/voting/dao-voting-snip20-staked/src/msg.rs index 523a9b6..bb89ad7 100644 --- a/contracts/voting/dao-voting-snip20-staked/src/msg.rs +++ b/contracts/voting/dao-voting-snip20-staked/src/msg.rs @@ -15,8 +15,6 @@ pub enum StakingInfo { Existing { /// Address of an already instantiated staking contract. staking_contract_address: String, - /// code hash of an already instantiated staking contract. - staking_contract_code_hash: String, }, New { /// Code ID for staking contract to instantiate. @@ -38,8 +36,6 @@ pub enum Snip20TokenInfo { Existing { /// Address of an already instantiated cw20 token contract. address: String, - /// Code hash of an already instantiated cw20 token contract. - code_hash: String, /// Information about the staking contract to use. staking_contract: StakingInfo, }, @@ -66,7 +62,6 @@ pub struct InstantiateMsg { /// The number or percentage of tokens that must be staked /// for the DAO to be active pub active_threshold: Option, - pub dao_code_hash: String, pub query_auth: Option, } diff --git a/contracts/voting/dao-voting-snip20-staked/src/tests.rs b/contracts/voting/dao-voting-snip20-staked/src/tests.rs index 5d49737..5507c1c 100644 --- a/contracts/voting/dao-voting-snip20-staked/src/tests.rs +++ b/contracts/voting/dao-voting-snip20-staked/src/tests.rs @@ -16,7 +16,7 @@ use secret_multi_test::{ next_block, App, Contract, ContractInstantiationInfo, ContractWrapper, Executor, }; use shade_protocol::{basic_staking::Auth, utils::asset::RawContract}; -use snip20_reference_impl::msg::{InitConfig, InitialBalance as Snip20InitialBalance, QueryAnswer}; +use snip20_base::msg::{InitConfig, InitialBalance as Snip20InitialBalance, QueryAnswer}; use crate::{ contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, @@ -55,9 +55,9 @@ fn contract_query_auth() -> Box> { fn snip20_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } @@ -128,7 +128,7 @@ fn stake_tokens( auth: Auth, amount: u128, ) { - let msg = snip20_reference_impl::msg::ExecuteMsg::Send { + let msg = snip20_base::msg::ExecuteMsg::Send { recipient: staking_addr.to_string(), recipient_code_hash: Some(staking_code_hash), amount: Uint128::new(amount), @@ -168,7 +168,7 @@ fn create_viewing_key(app: &mut App, contract_info: ContractInfo, sender: &str) } fn create_snip20_viewing_key(app: &mut App, contract_info: ContractInfo, sender: &str) -> String { - let msg = snip20_reference_impl::msg::ExecuteMsg::CreateViewingKey { + let msg = snip20_base::msg::ExecuteMsg::CreateViewingKey { entropy: "entropy".to_string(), padding: None, }; @@ -176,8 +176,8 @@ fn create_snip20_viewing_key(app: &mut App, contract_info: ContractInfo, sender: .execute_contract(Addr::unchecked(sender), &contract_info, &msg, &[]) .unwrap(); let mut viewing_key = String::new(); - let data: snip20_reference_impl::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); - if let snip20_reference_impl::msg::ExecuteAnswer::CreateViewingKey { key } = data { + let data: snip20_base::msg::ExecuteAnswer = from_binary(&res.data.unwrap()).unwrap(); + if let snip20_base::msg::ExecuteAnswer::CreateViewingKey { key } = data { viewing_key = key; } viewing_key @@ -210,7 +210,6 @@ fn test_instantiate_zero_supply() { initial_dao_balance: Some(Uint128::zero()), }, active_threshold: None, - dao_code_hash: "".into(), query_auth: None, }, ); @@ -240,7 +239,6 @@ fn test_instantiate_no_balances() { initial_dao_balance: Some(Uint128::zero()), }, active_threshold: None, - dao_code_hash: "".into(), query_auth: None, }, ); @@ -276,7 +274,6 @@ fn test_instantiate_zero_active_threshold_count() { count: Uint128::new(0), }), query_auth: None, - dao_code_hash: "".into(), }, ); } @@ -309,7 +306,6 @@ fn test_contract_info() { }, active_threshold: None, query_auth: None, - dao_code_hash: "".into(), }, ); @@ -397,7 +393,6 @@ fn test_new_snip20() { &query_auth_info.address.clone().to_string(), &query_auth_info.code_hash.clone(), )), - dao_code_hash: "".into(), }, ); @@ -429,7 +424,7 @@ fn test_new_snip20() { .query_wasm_smart( snip20token_info.code_hash.clone(), snip20token_info.addr.clone(), - &snip20_reference_impl::msg::QueryMsg::TokenInfo {}, + &snip20_base::msg::QueryMsg::TokenInfo {}, ) .unwrap(); if let QueryAnswer::TokenInfo { @@ -460,7 +455,7 @@ fn test_new_snip20() { .query_wasm_smart( snip20token_info.code_hash.clone(), snip20token_info.addr.clone(), - &snip20_reference_impl::msg::QueryMsg::Minters {}, + &snip20_base::msg::QueryMsg::Minters {}, ) .unwrap(); if let QueryAnswer::Minters { minters } = res { @@ -492,7 +487,7 @@ fn test_new_snip20() { .query_wasm_smart( snip20token_info.code_hash.clone(), snip20token_info.addr.clone(), - &snip20_reference_impl::msg::QueryMsg::Balance { + &snip20_base::msg::QueryMsg::Balance { address: DAO_ADDR.to_string(), key: token_viewing_key.clone(), }, @@ -648,7 +643,7 @@ fn test_existing_snip20_new_staking() { .instantiate_contract( snip20_info, Addr::unchecked(CREATOR_ADDR), - &snip20_reference_impl::msg::InstantiateMsg { + &snip20_base::msg::InstantiateMsg { name: "DAO DAO".to_string(), symbol: "DAO".to_string(), decimals: 3, @@ -676,7 +671,6 @@ fn test_existing_snip20_new_staking() { InstantiateMsg { token_info: crate::msg::Snip20TokenInfo::Existing { address: snip20token_info.address.clone().to_string(), - code_hash: snip20token_info.code_hash.clone(), staking_contract: StakingInfo::New { staking_code_id: staking_info.code_id, staking_code_hash: staking_info.code_hash.clone(), @@ -689,7 +683,6 @@ fn test_existing_snip20_new_staking() { &query_auth_info.address.into(), &query_auth_info.code_hash, )), - dao_code_hash: "".into(), }, ); @@ -721,7 +714,7 @@ fn test_existing_snip20_new_staking() { .query_wasm_smart( snip20token_info.code_hash.clone(), snip20token_info.addr.clone(), - &snip20_reference_impl::msg::QueryMsg::TokenInfo {}, + &snip20_base::msg::QueryMsg::TokenInfo {}, ) .unwrap(); if let QueryAnswer::TokenInfo { @@ -752,7 +745,7 @@ fn test_existing_snip20_new_staking() { .query_wasm_smart( snip20token_info.code_hash.clone(), snip20token_info.addr.clone(), - &snip20_reference_impl::msg::QueryMsg::Minters {}, + &snip20_base::msg::QueryMsg::Minters {}, ) .unwrap(); if let QueryAnswer::Minters { minters } = res { @@ -900,7 +893,7 @@ fn test_existing_snip20_existing_staking() { .instantiate_contract( snip20_info.clone(), Addr::unchecked(CREATOR_ADDR), - &snip20_reference_impl::msg::InstantiateMsg { + &snip20_base::msg::InstantiateMsg { name: "DAO DAO".to_string(), symbol: "DAO".to_string(), decimals: 3, @@ -928,7 +921,6 @@ fn test_existing_snip20_existing_staking() { InstantiateMsg { token_info: crate::msg::Snip20TokenInfo::Existing { address: snip20token_info.address.clone().to_string(), - code_hash: snip20token_info.code_hash.clone(), staking_contract: StakingInfo::New { staking_code_id: staking_info.code_id, staking_code_hash: staking_info.code_hash.clone(), @@ -937,7 +929,6 @@ fn test_existing_snip20_existing_staking() { }, }, active_threshold: None, - dao_code_hash: "".into(), query_auth: Some(RawContract::new( &query_auth_info.address.clone().into(), &query_auth_info.code_hash.clone(), @@ -974,7 +965,7 @@ fn test_existing_snip20_existing_staking() { .query_wasm_smart( snip20token_info.code_hash.clone(), snip20token_info.addr.clone(), - &snip20_reference_impl::msg::QueryMsg::TokenInfo {}, + &snip20_base::msg::QueryMsg::TokenInfo {}, ) .unwrap(); if let QueryAnswer::TokenInfo { @@ -1005,10 +996,8 @@ fn test_existing_snip20_existing_staking() { InstantiateMsg { token_info: crate::msg::Snip20TokenInfo::Existing { address: snip20token_info.addr.clone().to_string(), - code_hash: snip20token_info.code_hash.clone(), staking_contract: StakingInfo::Existing { staking_contract_address: staking_info.addr.clone().to_string(), - staking_contract_code_hash: staking_info.code_hash.clone(), }, }, active_threshold: None, @@ -1016,7 +1005,6 @@ fn test_existing_snip20_existing_staking() { &query_auth_info.address.clone().into(), &query_auth_info.code_hash.clone(), )), - dao_code_hash: "".into(), }, ); @@ -1133,7 +1121,7 @@ fn test_existing_snip20_existing_staking() { .instantiate_contract( snip20_info, Addr::unchecked(CREATOR_ADDR), - &snip20_reference_impl::msg::InstantiateMsg { + &snip20_base::msg::InstantiateMsg { name: "DAO DAO MISMATCH".to_string(), symbol: "DAOM".to_string(), decimals: 3, @@ -1162,10 +1150,8 @@ fn test_existing_snip20_existing_staking() { &InstantiateMsg { token_info: crate::msg::Snip20TokenInfo::Existing { address: different_token.address.to_string(), - code_hash: different_token.code_hash, staking_contract: StakingInfo::Existing { staking_contract_address: staking_info.addr.to_string(), - staking_contract_code_hash: staking_info.code_hash, }, }, active_threshold: None, @@ -1173,7 +1159,6 @@ fn test_existing_snip20_existing_staking() { &query_auth_info.address.clone().into(), &query_auth_info.code_hash.clone(), )), - dao_code_hash: "".into(), }, &[], "voting module", @@ -1203,7 +1188,7 @@ fn test_different_heights() { .instantiate_contract( snip20_info, Addr::unchecked(CREATOR_ADDR), - &snip20_reference_impl::msg::InstantiateMsg { + &snip20_base::msg::InstantiateMsg { name: "DAO DAO".to_string(), symbol: "DAO".to_string(), decimals: 3, @@ -1231,7 +1216,6 @@ fn test_different_heights() { InstantiateMsg { token_info: crate::msg::Snip20TokenInfo::Existing { address: snip20token_info.address.clone().to_string(), - code_hash: snip20token_info.code_hash.clone(), staking_contract: StakingInfo::New { staking_code_id: staking_info.code_id, staking_code_hash: staking_info.code_hash.clone(), @@ -1240,7 +1224,6 @@ fn test_different_heights() { }, }, active_threshold: None, - dao_code_hash: "".into(), query_auth: Some(RawContract::new( &query_auth_info.address.to_string(), &query_auth_info.code_hash, @@ -1493,7 +1476,6 @@ fn test_active_threshold_absolute_count() { active_threshold: Some(ActiveThreshold::AbsoluteCount { count: Uint128::new(100), }), - dao_code_hash: "".into(), query_auth: Some(RawContract::new( &query_auth_info.address.to_string(), &query_auth_info.code_hash, @@ -1598,7 +1580,6 @@ fn test_active_threshold_percent() { active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(20), }), - dao_code_hash: "".into(), query_auth: Some(RawContract::new( &query_auth_info.address.to_string(), &query_auth_info.code_hash, @@ -1703,7 +1684,6 @@ fn test_active_threshold_percent_rounds_up() { active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(50), }), - dao_code_hash: "".into(), query_auth: Some(RawContract::new( &query_auth_info.address.to_string(), &query_auth_info.code_hash, @@ -1814,7 +1794,6 @@ fn test_active_threshold_none() { }, active_threshold: None, query_auth: None, - dao_code_hash: "".into(), }, ); @@ -1858,7 +1837,6 @@ fn test_update_active_threshold() { }, active_threshold: None, query_auth: None, - dao_code_hash: "".into(), }, ); @@ -1943,7 +1921,6 @@ fn test_active_threshold_percentage_gt_100() { percent: Decimal::percent(120), }), query_auth: None, - dao_code_hash: "".into(), }, ); } @@ -1978,7 +1955,6 @@ fn test_active_threshold_percentage_lte_0() { active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(0), }), - dao_code_hash: "".into(), query_auth: None, }, ); @@ -2015,7 +1991,6 @@ fn test_active_threshold_absolute_count_invalid() { count: Uint128::new(10000), }), query_auth: None, - dao_code_hash: "".into(), }, ); } diff --git a/contracts/voting/dao-voting-snip721-roles/src/contract.rs b/contracts/voting/dao-voting-snip721-roles/src/contract.rs index 628dec3..36a7d7c 100644 --- a/contracts/voting/dao-voting-snip721-roles/src/contract.rs +++ b/contracts/voting/dao-voting-snip721-roles/src/contract.rs @@ -8,6 +8,7 @@ use cw4::{MemberResponse, TotalWeightResponse}; use dao_interface::replies::parse_reply_address_from_event; use dao_interface::state::AnyContractInfo; use dao_snip721_extensions::roles::{ExecuteExt, MetadataExt, QueryExt}; +use dao_utils::query::get_contract_code_hash; use secret_cw2::set_contract_version; use shade_protocol::basic_staking::Auth; @@ -34,15 +35,15 @@ pub fn instantiate( deps.storage, &AnyContractInfo { addr: info.sender.clone(), - code_hash: msg.dao_code_hash, + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().into()).unwrap_or_default(), }, )?; match msg.nft_contract { - NftRolesContract::Existing { address, code_hash } => { + NftRolesContract::Existing { address } => { let config = Config { nft_address: deps.api.addr_validate(&address)?, - nft_code_hash: code_hash.clone(), + nft_code_hash: get_contract_code_hash(deps.querier, address.clone()).unwrap_or_default(), }; CONFIG.save(deps.storage, &config)?; diff --git a/contracts/voting/dao-voting-snip721-roles/src/msg.rs b/contracts/voting/dao-voting-snip721-roles/src/msg.rs index 0ae9c53..1756cd9 100644 --- a/contracts/voting/dao-voting-snip721-roles/src/msg.rs +++ b/contracts/voting/dao-voting-snip721-roles/src/msg.rs @@ -27,8 +27,6 @@ pub enum NftRolesContract { Existing { /// Address of an already instantiated snip721-weighted-roles token contract. address: String, - /// code hash of an already instantiated snip721-weighted-roles token contract. - code_hash: String, }, New { /// Code ID for snip721 roles contract. @@ -64,7 +62,6 @@ pub enum NftRolesContract { pub struct InstantiateMsg { /// Info about the associated NFT contract pub nft_contract: NftRolesContract, - pub dao_code_hash: String, } #[cw_serde] diff --git a/contracts/voting/dao-voting-snip721-roles/src/testing/mod.rs b/contracts/voting/dao-voting-snip721-roles/src/testing/mod.rs index 112fd06..a9850f4 100644 --- a/contracts/voting/dao-voting-snip721-roles/src/testing/mod.rs +++ b/contracts/voting/dao-voting-snip721-roles/src/testing/mod.rs @@ -59,7 +59,6 @@ pub(crate) fn setup_test(initial_nfts: Vec) -> CommonTest { &query_auth.code_hash.clone(), )), }, - dao_code_hash: "".into(), }, &[], "snip721_voting", diff --git a/contracts/voting/dao-voting-snip721-roles/src/testing/tests.rs b/contracts/voting/dao-voting-snip721-roles/src/testing/tests.rs index e8fd766..062ddf0 100644 --- a/contracts/voting/dao-voting-snip721-roles/src/testing/tests.rs +++ b/contracts/voting/dao-voting-snip721-roles/src/testing/tests.rs @@ -52,9 +52,7 @@ fn test_use_existing_nft_contract() { &InstantiateMsg { nft_contract: NftRolesContract::Existing { address: snip721_info.address.clone().to_string(), - code_hash: snip721_info.code_hash.clone(), }, - dao_code_hash: "".into(), }, &[], "cw721_voting", diff --git a/contracts/voting/dao-voting-snip721-staked/Cargo.toml b/contracts/voting/dao-voting-snip721-staked/Cargo.toml index 0ebbd34..3a06175 100644 --- a/contracts/voting/dao-voting-snip721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-snip721-staked/Cargo.toml @@ -42,6 +42,7 @@ snip721-reference-impl = {workspace=true} schemars = {workspace=true} serde ={ workspace = true } shade-protocol ={ workspace = true } +dao-utils ={ workspace = true} [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/voting/dao-voting-snip721-staked/src/contract.rs b/contracts/voting/dao-voting-snip721-staked/src/contract.rs index d1eede5..8ee60af 100644 --- a/contracts/voting/dao-voting-snip721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-snip721-staked/src/contract.rs @@ -10,6 +10,7 @@ use dao_hooks::nft_stake::{stake_nft_hook_msgs, unstake_nft_hook_msgs}; use dao_interface::replies::parse_reply_address_from_event; use dao_interface::state::AnyContractInfo; use dao_interface::{nft::NftFactoryCallback, voting::IsActiveResponse}; +use dao_utils::query::get_contract_code_hash; use dao_voting::duration::validate_duration; use dao_voting::threshold::{ assert_valid_absolute_count_threshold, assert_valid_percentage_threshold, ActiveThreshold, @@ -89,7 +90,7 @@ pub fn instantiate( deps.storage, &AnyContractInfo { addr: info.sender.clone(), - code_hash: msg.dao_code_hash, + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().into()).unwrap_or_default(), }, )?; @@ -107,11 +108,10 @@ pub fn instantiate( // NFT contracts. For new NFT contracts, we will check this in the reply. if let NftContract::Existing { ref address, - ref code_hash, } = msg.nft_contract { let nft_supply: snip721::NumTokens = deps.querier.query_wasm_smart( - code_hash, + get_contract_code_hash(deps.querier, address.to_string()).unwrap_or_default(), address, &snip721::Snip721QueryMsg::NumTokens { viewer: None }, )?; @@ -131,11 +131,11 @@ pub fn instantiate( StakedNftsTotalStore::save(deps.storage, env.block.height, Uint128::zero())?; match msg.nft_contract { - NftContract::Existing { address, code_hash } => { + NftContract::Existing { address } => { let config = Config { nft_address: deps.api.addr_validate(&address)?, unstaking_duration: msg.unstaking_duration, - nft_code_hash: code_hash.clone(), + nft_code_hash: get_contract_code_hash(deps.querier, address.clone()).unwrap_or_default(), query_auth: msg .query_auth .unwrap_or_default() diff --git a/contracts/voting/dao-voting-snip721-staked/src/msg.rs b/contracts/voting/dao-voting-snip721-staked/src/msg.rs index 60b1de4..9b67faf 100644 --- a/contracts/voting/dao-voting-snip721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-snip721-staked/src/msg.rs @@ -12,8 +12,6 @@ pub enum NftContract { Existing { /// Address of an already instantiated snip721 or sg721 token contract. address: String, - /// code hash of an already instantiated snip721 or sg721 token contract. - code_hash: String, }, /// Creates a new NFT collection used for staking and governance. New { @@ -46,9 +44,6 @@ pub struct InstantiateMsg { /// The number or percentage of tokens that must be staked /// for the DAO to be active pub active_threshold: Option, - - pub dao_code_hash: String, - pub query_auth: Option, } diff --git a/contracts/voting/dao-voting-snip721-staked/src/testing/mod.rs b/contracts/voting/dao-voting-snip721-staked/src/testing/mod.rs index f040732..68c34ac 100644 --- a/contracts/voting/dao-voting-snip721-staked/src/testing/mod.rs +++ b/contracts/voting/dao-voting-snip721-staked/src/testing/mod.rs @@ -48,11 +48,9 @@ pub(crate) fn setup_test(unstaking_duration: Option) -> CommonTest { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration, active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.address.to_string(), code_hash: query_auth.code_hash.clone(), diff --git a/contracts/voting/dao-voting-snip721-staked/src/testing/tests.rs b/contracts/voting/dao-voting-snip721-staked/src/testing/tests.rs index 33c0c1a..31fdaea 100644 --- a/contracts/voting/dao-voting-snip721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-snip721-staked/src/testing/tests.rs @@ -51,11 +51,9 @@ fn test_instantiate_with_new_cw721_collection() -> anyhow::Result<()> { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration: None, active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { code_hash: query_auth.code_hash.clone(), address: query_auth.address.clone().to_string(), @@ -604,7 +602,6 @@ fn test_instantiate_with_invalid_duration_fails() { unstaking_duration: None, active_threshold: None, query_auth: None, - dao_code_hash: "".to_string(), }, &[], "snip721_voting", @@ -660,7 +657,6 @@ fn test_instantiate_zero_active_threshold_count() { count: Uint128::zero(), }), query_auth: None, - dao_code_hash: "".to_string(), }, &[], "snip721_voting", @@ -682,14 +678,12 @@ fn test_instantiate_invalid_active_threshold_count_existing_nft() { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash, }, unstaking_duration: None, active_threshold: Some(ActiveThreshold::AbsoluteCount { count: Uint128::new(100), }), query_auth: None, - dao_code_hash: "".to_string(), }, &[], "snip721_voting", @@ -718,13 +712,11 @@ fn test_active_threshold_absolute_count() { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration: None, active_threshold: Some(ActiveThreshold::AbsoluteCount { count: Uint128::new(3), }), - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { code_hash: query_auth.code_hash.clone(), address: query_auth.address.clone().to_string(), @@ -789,13 +781,11 @@ fn test_active_threshold_percent() { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration: None, active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(20), }), - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { code_hash: query_auth.code_hash.clone(), address: query_auth.address.clone().to_string(), @@ -862,13 +852,11 @@ fn test_active_threshold_percent_rounds_up() { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration: None, active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(50), }), - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { code_hash: query_auth.code_hash.clone(), address: query_auth.address.clone().to_string(), @@ -942,11 +930,9 @@ fn test_update_active_threshold() { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration: None, active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { code_hash: query_auth.code_hash.clone(), address: query_auth.address.clone().to_string(), @@ -1024,13 +1010,11 @@ fn test_active_threshold_percentage_gt_100() { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration: None, active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(120), }), - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { code_hash: query_auth.code_hash.clone(), address: query_auth.address.clone().to_string(), @@ -1067,13 +1051,11 @@ fn test_active_threshold_percentage_lte_0() { &InstantiateMsg { nft_contract: NftContract::Existing { address: nft.address.to_string(), - code_hash: nft.code_hash.clone(), }, unstaking_duration: None, active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(0), }), - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { code_hash: query_auth.code_hash.clone(), address: query_auth.address.clone().to_string(), @@ -1129,7 +1111,6 @@ fn test_invalid_instantiate_msg() { count: Uint128::zero(), }), query_auth: None, - dao_code_hash: "".to_string(), }, &[], "snip721_voting", @@ -1174,7 +1155,6 @@ fn test_no_initial_nfts_fails() { count: Uint128::zero(), }), query_auth: None, - dao_code_hash: "".to_string(), }, &[], "snip721_voting", diff --git a/contracts/voting/dao-voting-token-staked/Cargo.toml b/contracts/voting/dao-voting-token-staked/Cargo.toml index 1e03a40..00a71a6 100644 --- a/contracts/voting/dao-voting-token-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-staked/Cargo.toml @@ -46,6 +46,7 @@ schemars ={ workspace = true } bincode ={ workspace=true } shade-protocol ={ workspace = true} query_auth = { workspace = true} +dao-utils ={ workspace = true} [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/voting/dao-voting-token-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs index 448266d..6461fc9 100644 --- a/contracts/voting/dao-voting-token-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -15,6 +15,7 @@ use dao_interface::{ DenomResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }, }; +use dao_utils::query::get_contract_code_hash; use dao_voting::{ duration::validate_duration, threshold::{ @@ -72,8 +73,8 @@ pub fn instantiate( DAO.save( deps.storage, &AnyContractInfo { - addr: info.sender, - code_hash: msg.dao_code_hash, + addr: info.sender.clone(), + code_hash: get_contract_code_hash(deps.querier, info.sender.into_string()).unwrap_or_default(), }, )?; diff --git a/contracts/voting/dao-voting-token-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs index b81a9ce..76f4459 100644 --- a/contracts/voting/dao-voting-token-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -38,7 +38,6 @@ pub struct InstantiateMsg { /// The number or percentage of tokens that must be staked /// for the DAO to be active pub active_threshold: Option, - pub dao_code_hash: String, pub query_auth: Option, } diff --git a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs index 6d9320b..cdabff9 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs @@ -289,7 +289,6 @@ fn test_instantiate_existing() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -307,7 +306,6 @@ fn test_instantiate_existing() { }, unstaking_duration: None, active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -348,7 +346,6 @@ fn test_instantiate_invalid_unstaking_duration_height() { }, unstaking_duration: Some(Duration::Height(0)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -374,7 +371,6 @@ fn test_instantiate_invalid_unstaking_duration_time() { }, unstaking_duration: Some(Duration::Time(0)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -402,7 +398,6 @@ fn test_stake_invalid_denom() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -443,7 +438,6 @@ fn test_stake_valid_denom() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -485,7 +479,6 @@ fn test_unstake_none_staked() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -525,7 +518,6 @@ fn test_unstake_zero_tokens() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -565,7 +557,6 @@ fn test_unstake_invalid_balance() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -620,7 +611,6 @@ fn test_unstake() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -710,7 +700,6 @@ fn test_unstake_no_unstaking_duration() { }, unstaking_duration: None, active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -787,7 +776,6 @@ fn test_claim_no_claims() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -817,7 +805,6 @@ fn test_claim_claim_not_reached() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -876,7 +863,6 @@ fn test_claim() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -967,7 +953,6 @@ fn test_update_config_invalid_sender() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -995,7 +980,6 @@ fn test_update_config_as_owner() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1039,7 +1023,6 @@ fn test_update_config_invalid_duration() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1067,7 +1050,6 @@ fn test_query_dao() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1088,7 +1070,7 @@ fn test_query_dao() { dao, AnyContractInfo { addr: Addr::unchecked(DAO_ADDR), - code_hash: "dao_code_hash".to_string() + code_hash: "".to_string() } ); } @@ -1109,7 +1091,6 @@ fn test_query_info() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1147,7 +1128,6 @@ fn test_query_claims() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1244,7 +1224,6 @@ fn test_query_get_config() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1279,7 +1258,6 @@ fn test_voting_power_queries() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1502,7 +1480,6 @@ fn test_active_threshold_none() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1543,7 +1520,6 @@ fn test_active_threshold_percentage_gt_100() { active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(120), }), - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1573,7 +1549,6 @@ fn test_active_threshold_percentage_lte_0() { active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(0), }), - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, @@ -1598,7 +1573,6 @@ fn test_add_remove_hooks() { }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, - dao_code_hash: "dao_code_hash".to_string(), query_auth: Some(RawContract { address: query_auth.clone().address.to_string(), code_hash: query_auth.clone().code_hash, diff --git a/packages/cw-denom/Cargo.toml b/packages/cw-denom/Cargo.toml index 2451828..6b60b17 100644 --- a/packages/cw-denom/Cargo.toml +++ b/packages/cw-denom/Cargo.toml @@ -11,7 +11,7 @@ version = { workspace = true } cosmwasm-std = {workspace=true} cosmwasm-storage = {workspace=true} cosmwasm-schema = { workspace = true } -snip20-reference-impl ={ workspace = true} +snip20-base ={ workspace = true} thiserror = { workspace = true } secret-toolkit = { workspace = true } diff --git a/packages/cw-denom/src/lib.rs b/packages/cw-denom/src/lib.rs index eb60b71..57b829c 100644 --- a/packages/cw-denom/src/lib.rs +++ b/packages/cw-denom/src/lib.rs @@ -131,16 +131,16 @@ impl CheckedDenom { CheckedDenom::Native(denom) => Ok(querier.query_balance(who, denom)?.amount), CheckedDenom::Snip20(address, code_hash) => { let mut res = Uint128::zero(); - let balance: snip20_reference_impl::msg::QueryAnswer = querier.query_wasm_smart( + let balance: snip20_base::msg::QueryAnswer = querier.query_wasm_smart( code_hash, address, - &snip20_reference_impl::msg::QueryMsg::Balance { + &snip20_base::msg::QueryMsg::Balance { address: who.to_string(), key, }, )?; match balance { - snip20_reference_impl::msg::QueryAnswer::Balance { amount } => { + snip20_base::msg::QueryAnswer::Balance { amount } => { res = amount; } _ => (), @@ -166,7 +166,7 @@ impl CheckedDenom { CheckedDenom::Snip20(address, code_hash) => WasmMsg::Execute { contract_addr: address.to_string(), code_hash: code_hash.to_string(), - msg: to_binary(&snip20_reference_impl::msg::ExecuteMsg::Transfer { + msg: to_binary(&snip20_base::msg::ExecuteMsg::Transfer { recipient: who.to_string(), amount, memo: None, diff --git a/packages/dao-interface/src/msg.rs b/packages/dao-interface/src/msg.rs index d146cef..86e8f25 100644 --- a/packages/dao-interface/src/msg.rs +++ b/packages/dao-interface/src/msg.rs @@ -1,4 +1,4 @@ -use crate::state::{AnyContractInfo, Config}; +use crate::state::Config; use crate::{query::SubDao, state::ModuleInstantiateInfo}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Binary, CosmosMsg, Empty, Uint128}; @@ -51,8 +51,6 @@ pub struct InstantiateMsg { pub query_auth_code_id: u64, pub query_auth_code_hash: String, pub prng_seed: String, - pub snip20_code_hash: String, - pub snip721_code_hash: String, } /// Snip20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg @@ -255,7 +253,7 @@ pub enum QueryMsg { /// Returns the total voting power at a given block height. #[returns(crate::voting::TotalPowerAtHeightResponse)] TotalPowerAtHeight { height: Option }, - #[returns(AnyContractInfo)] + #[returns(crate::state::AnyContractInfo)] QueryAuthInfo {}, } diff --git a/packages/dao-interface/src/replies.rs b/packages/dao-interface/src/replies.rs index edae8cb..8716055 100644 --- a/packages/dao-interface/src/replies.rs +++ b/packages/dao-interface/src/replies.rs @@ -20,15 +20,9 @@ pub enum ReplyError { #[cw_serde] pub enum ReplyEvent { - VotingModuleInstantiate { - code_hash: String, - }, - ProposalModuleInstantiate { - code_hash: String, - }, - PreProposalModuleInstantiate { - code_hash: String, - }, + VotingModuleInstantiate {}, + ProposalModuleInstantiate {}, + PreProposalModuleInstantiate {}, Snip20ModuleInstantiate {}, Snip20ModuleCreateViewingKey { contract_address: String, @@ -52,7 +46,6 @@ pub enum ReplyEvent { /// NOTE: the pre-propose-base package depends on it being the case /// that the core module instantiates its proposal module. proposal_modules_instantiate_info: Vec, - code_hash: String, }, InstantiateGroupContract {}, } diff --git a/packages/dao-interface/src/state.rs b/packages/dao-interface/src/state.rs index 26c63c3..639e405 100644 --- a/packages/dao-interface/src/state.rs +++ b/packages/dao-interface/src/state.rs @@ -45,6 +45,21 @@ pub struct AnyContractInfo { pub code_hash: String, } +impl Default for AnyContractInfo { + fn default() -> Self { + AnyContractInfo { + addr: Addr::unchecked(""), + code_hash: String::new(), + } + } +} + +impl AnyContractInfo { + pub fn new(addr: Addr, code_hash: String) -> Self { + AnyContractInfo { addr, code_hash } + } +} + /// The status of a proposal module. #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] diff --git a/packages/dao-pre-propose-base/Cargo.toml b/packages/dao-pre-propose-base/Cargo.toml index 4074fff..1ac5acb 100644 --- a/packages/dao-pre-propose-base/Cargo.toml +++ b/packages/dao-pre-propose-base/Cargo.toml @@ -31,6 +31,7 @@ serde = { workspace = true } schemars = { workspace = true } thiserror = { workspace = true } shade-protocol ={ workspace = true } +dao-utils ={ workspace = true} [dev-dependencies] secret-multi-test = { workspace = true } diff --git a/packages/dao-pre-propose-base/src/execute.rs b/packages/dao-pre-propose-base/src/execute.rs index f35c061..b229735 100644 --- a/packages/dao-pre-propose-base/src/execute.rs +++ b/packages/dao-pre-propose-base/src/execute.rs @@ -4,6 +4,7 @@ use cosmwasm_std::{ }; use cw_hooks::HookItem; +use dao_utils::query::get_contract_code_hash; use secret_cw2::set_contract_version; use cw_denom::UncheckedDenom; @@ -48,13 +49,13 @@ where deps.storage, &AnyContractInfo { addr: info.sender.clone(), - code_hash: msg.proposal_module_code_hash.clone(), + code_hash: get_contract_code_hash(deps.querier, info.sender.clone().into()).unwrap_or_default(), }, )?; // Query the proposal module for its DAO. let dao_info: AnyContractInfo = deps.querier.query_wasm_smart( - msg.proposal_module_code_hash.clone(), + get_contract_code_hash(deps.querier, info.sender.clone().into()).unwrap_or_default(), info.sender.clone(), &CwCoreQuery::Dao {}, )?; diff --git a/packages/dao-pre-propose-base/src/lib.rs b/packages/dao-pre-propose-base/src/lib.rs index 6ca223e..be00400 100644 --- a/packages/dao-pre-propose-base/src/lib.rs +++ b/packages/dao-pre-propose-base/src/lib.rs @@ -5,5 +5,5 @@ pub mod execute; pub mod msg; pub mod state; -// #[cfg(test)] -// mod tests; +#[cfg(test)] +mod tests; diff --git a/packages/dao-pre-propose-base/src/msg.rs b/packages/dao-pre-propose-base/src/msg.rs index 27493cb..d549258 100644 --- a/packages/dao-pre-propose-base/src/msg.rs +++ b/packages/dao-pre-propose-base/src/msg.rs @@ -16,7 +16,6 @@ pub struct InstantiateMsg { /// proposals in the DAO. Otherwise, any address may create a /// proposal so long as they pay the deposit. pub open_proposal_submission: bool, - pub proposal_module_code_hash: String, /// Extension for instantiation. The default implementation will /// do nothing with this data. pub extension: InstantiateExt, diff --git a/packages/dao-pre-propose-base/src/tests.rs b/packages/dao-pre-propose-base/src/tests.rs index 1bc68e7..60287a3 100644 --- a/packages/dao-pre-propose-base/src/tests.rs +++ b/packages/dao-pre-propose-base/src/tests.rs @@ -1,203 +1,204 @@ -use cw_hooks::HooksResponse; -use dao_voting::status::Status; -use cosmwasm_std::{ - from_binary, - testing::{mock_dependencies, mock_env, mock_info}, - to_binary, Addr, Binary, ContractResult, Empty, Response, SubMsg, WasmMsg, -}; - -use crate::{ - error::PreProposeError, - msg::{ExecuteMsg, QueryMsg}, - state::{Config, PreProposeContract}, -}; - -type Contract = PreProposeContract; - -#[test] -fn test_completed_hook_status_invariant() { - let mut deps = mock_dependencies(); - let info = mock_info("pm", &[]); - - let module = Contract::default(); - - module - .proposal_module - .save(&mut deps.storage, &Addr::unchecked("pm")) - .unwrap(); - - let res = module.execute( - deps.as_mut(), - mock_env(), - info, - ExecuteMsg::ProposalCompletedHook { - proposal_id: 1, - new_status: Status::Passed, - }, - ); - - assert_eq!( - res.unwrap_err(), - PreProposeError::NotCompleted { - status: Status::Passed - } - ); -} - -#[test] -fn test_completed_hook_auth() { - let mut deps = mock_dependencies(); - let info = mock_info("evil", &[]); - let module = Contract::default(); - - module - .proposal_module - .save(&mut deps.storage, &Addr::unchecked("pm")) - .unwrap(); - - let res = module.execute( - deps.as_mut(), - mock_env(), - info, - ExecuteMsg::ProposalCompletedHook { - proposal_id: 1, - new_status: Status::Passed, - }, - ); - - assert_eq!(res.unwrap_err(), PreProposeError::NotModule {}); -} - -#[test] -fn test_proposal_submitted_hooks() { - let mut deps = mock_dependencies(); - let module = Contract::default(); - - module - .dao - .save(&mut deps.storage, &Addr::unchecked("d")) - .unwrap(); - module - .proposal_module - .save(&mut deps.storage, &Addr::unchecked("pm")) - .unwrap(); - module - .config - .save( - &mut deps.storage, - &Config { - deposit_info: None, - open_proposal_submission: true, - }, - ) - .unwrap(); - - // The DAO can add a hook. - let info = mock_info("d", &[]); - module - .execute_add_proposal_submitted_hook(deps.as_mut(), info, "one".to_string()) - .unwrap(); - let hooks: HooksResponse = from_binary( - &module - .query( - deps.as_ref(), - mock_env(), - QueryMsg::ProposalSubmittedHooks {}, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!(hooks.hooks, vec!["one".to_string()]); - - // Non-DAO addresses can not add hooks. - let info = mock_info("n", &[]); - let err = module - .execute_add_proposal_submitted_hook(deps.as_mut(), info, "two".to_string()) - .unwrap_err(); - assert_eq!(err, PreProposeError::NotDao {}); - - deps.querier.update_wasm(|_| { - // for responding to the next proposal ID query that gets fired by propose. - cosmwasm_std::SystemResult::Ok(ContractResult::Ok(to_binary(&1u64).unwrap())) - }); - - // The hooks fire when a proposal is created. - let res = module - .execute( - deps.as_mut(), - mock_env(), - mock_info("a", &[]), - ExecuteMsg::Propose { - msg: Empty::default(), - }, - ) - .unwrap(); - assert_eq!( - res.messages[1], - SubMsg::new(WasmMsg::Execute { - contract_addr: "one".to_string(), - code_hash: mock_env().contract.code_hash, - msg: to_binary(&Empty::default()).unwrap(), - funds: vec![], - }) - ); - - // Non-DAO addresses can not remove hooks. - let info = mock_info("n", &[]); - let err = module - .execute_remove_proposal_submitted_hook(deps.as_mut(), info, "one".to_string()) - .unwrap_err(); - assert_eq!(err, PreProposeError::NotDao {}); - - // The DAO can remove a hook. - let info = mock_info("d", &[]); - module - .execute_remove_proposal_submitted_hook(deps.as_mut(), info, "one".to_string()) - .unwrap(); - let hooks: HooksResponse = from_binary( - &module - .query( - deps.as_ref(), - mock_env(), - QueryMsg::ProposalSubmittedHooks {}, - ) - .unwrap(), - ) - .unwrap(); - assert!(hooks.hooks.is_empty()); -} - -#[test] -fn test_query_ext_does_nothing() { - let deps = mock_dependencies(); - let module = Contract::default(); - - let res = module - .query( - deps.as_ref(), - mock_env(), - QueryMsg::QueryExtension { - msg: Empty::default(), - }, - ) - .unwrap(); - assert_eq!(res, Binary::default()) -} - -#[test] -fn test_execute_ext_does_nothing() { - let mut deps = mock_dependencies(); - let module = Contract::default(); - - let res = module - .execute( - deps.as_mut(), - mock_env(), - mock_info("addr", &[]), - ExecuteMsg::Extension { - msg: Empty::default(), - }, - ) - .unwrap(); - assert_eq!(res, Response::default()) -} +// use cw_hooks::HooksResponse; +// use dao_interface::state::AnyContractInfo; +// use dao_voting::status::Status; +// use cosmwasm_std::{ +// from_binary, +// testing::{mock_dependencies, mock_env, mock_info}, +// to_binary, Addr, Binary, ContractResult, Empty, Response, SubMsg, WasmMsg, +// }; + +// use crate::{ +// error::PreProposeError, +// msg::{ExecuteMsg, QueryMsg}, +// state::{Config, PreProposeContract}, +// }; + +// type Contract = PreProposeContract; + +// #[test] +// fn test_completed_hook_status_invariant() { +// let mut deps = mock_dependencies(); +// let info = mock_info("pm", &[]); + +// let module = Contract::default(); + +// module +// .proposal_module +// .save(&mut deps.storage, &AnyContractInfo::default()) +// .unwrap(); + +// let res = module.execute( +// deps.as_mut(), +// mock_env(), +// info, +// ExecuteMsg::ProposalCompletedHook { +// proposal_id: 1, +// new_status: Status::Passed, +// }, +// ); + +// assert_eq!( +// res.unwrap_err(), +// PreProposeError::NotCompleted { +// status: Status::Passed +// } +// ); +// } + +// #[test] +// fn test_completed_hook_auth() { +// let mut deps = mock_dependencies(); +// let info = mock_info("evil", &[]); +// let module = Contract::default(); + +// module +// .proposal_module +// .save(&mut deps.storage, &AnyContractInfo::default()) +// .unwrap(); + +// let res = module.execute( +// deps.as_mut(), +// mock_env(), +// info, +// ExecuteMsg::ProposalCompletedHook { +// proposal_id: 1, +// new_status: Status::Passed, +// }, +// ); + +// assert_eq!(res.unwrap_err(), PreProposeError::NotModule {}); +// } + +// #[test] +// fn test_proposal_submitted_hooks() { +// let mut deps = mock_dependencies(); +// let module = Contract::default(); + +// module +// .dao +// .save(&mut deps.storage, &AnyContractInfo::default()) +// .unwrap(); +// module +// .proposal_module +// .save(&mut deps.storage, &AnyContractInfo::default()) +// .unwrap(); +// module +// .config +// .save( +// &mut deps.storage, +// &Config { +// deposit_info: None, +// open_proposal_submission: true, +// }, +// ) +// .unwrap(); + +// // The DAO can add a hook. +// let info = mock_info("d", &[]); +// module +// .execute_add_proposal_submitted_hook(deps.as_mut(), info, "one".to_string()) +// .unwrap(); +// let hooks: HooksResponse = from_binary( +// &module +// .query( +// deps.as_ref(), +// mock_env(), +// QueryMsg::ProposalSubmittedHooks {}, +// ) +// .unwrap(), +// ) +// .unwrap(); +// assert_eq!(hooks.hooks, vec!["one".to_string()]); + +// // Non-DAO addresses can not add hooks. +// let info = mock_info("n", &[]); +// let err = module +// .execute_add_proposal_submitted_hook(deps.as_mut(), info, "two".to_string()) +// .unwrap_err(); +// assert_eq!(err, PreProposeError::NotDao {}); + +// deps.querier.update_wasm(|_| { +// // for responding to the next proposal ID query that gets fired by propose. +// cosmwasm_std::SystemResult::Ok(ContractResult::Ok(to_binary(&1u64).unwrap())) +// }); + +// // The hooks fire when a proposal is created. +// let res = module +// .execute( +// deps.as_mut(), +// mock_env(), +// mock_info("a", &[]), +// ExecuteMsg::Propose { +// msg: Empty::default(), +// }, +// ) +// .unwrap(); +// assert_eq!( +// res.messages[1], +// SubMsg::new(WasmMsg::Execute { +// contract_addr: "one".to_string(), +// code_hash: mock_env().contract.code_hash, +// msg: to_binary(&Empty::default()).unwrap(), +// funds: vec![], +// }) +// ); + +// // Non-DAO addresses can not remove hooks. +// let info = mock_info("n", &[]); +// let err = module +// .execute_remove_proposal_submitted_hook(deps.as_mut(), info, "one".to_string()) +// .unwrap_err(); +// assert_eq!(err, PreProposeError::NotDao {}); + +// // The DAO can remove a hook. +// let info = mock_info("d", &[]); +// module +// .execute_remove_proposal_submitted_hook(deps.as_mut(), info, "one".to_string()) +// .unwrap(); +// let hooks: HooksResponse = from_binary( +// &module +// .query( +// deps.as_ref(), +// mock_env(), +// QueryMsg::ProposalSubmittedHooks {}, +// ) +// .unwrap(), +// ) +// .unwrap(); +// assert!(hooks.hooks.is_empty()); +// } + +// #[test] +// fn test_query_ext_does_nothing() { +// let deps = mock_dependencies(); +// let module = Contract::default(); + +// let res = module +// .query( +// deps.as_ref(), +// mock_env(), +// QueryMsg::QueryExtension { +// msg: Empty::default(), +// }, +// ) +// .unwrap(); +// assert_eq!(res, Binary::default()) +// } + +// #[test] +// fn test_execute_ext_does_nothing() { +// let mut deps = mock_dependencies(); +// let module = Contract::default(); + +// let res = module +// .execute( +// deps.as_mut(), +// mock_env(), +// mock_info("addr", &[]), +// ExecuteMsg::Extension { +// msg: Empty::default(), +// }, +// ) +// .unwrap(); +// assert_eq!(res, Response::default()) +// } diff --git a/packages/dao-testing/Cargo.toml b/packages/dao-testing/Cargo.toml index e33a8b0..7fa273f 100644 --- a/packages/dao-testing/Cargo.toml +++ b/packages/dao-testing/Cargo.toml @@ -24,7 +24,7 @@ cosmwasm-std = { workspace = true } secret-multi-test = { workspace = true } secret-utils = { workspace = true } secret-cw2 = { workspace = true } -snip20-reference-impl = { workspace = true } +snip20-base = { workspace = true } cw4 = { workspace = true } cw4-group = { workspace = true } rand = { workspace = true } diff --git a/packages/dao-testing/src/contracts.rs b/packages/dao-testing/src/contracts.rs index dfc9913..ea24a12 100644 --- a/packages/dao-testing/src/contracts.rs +++ b/packages/dao-testing/src/contracts.rs @@ -5,9 +5,9 @@ use secret_multi_test::{Contract, ContractWrapper}; pub fn snip20_base_contract() -> Box> { let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, + snip20_base::contract::execute, + snip20_base::contract::instantiate, + snip20_base::contract::query, ); Box::new(contract) } diff --git a/packages/dao-testing/src/helpers.rs b/packages/dao-testing/src/helpers.rs index 1d845ab..7859f76 100644 --- a/packages/dao-testing/src/helpers.rs +++ b/packages/dao-testing/src/helpers.rs @@ -12,7 +12,6 @@ use shade_protocol::basic_staking::Auth; use crate::contracts::{ cw4_group_contract, dao_dao_contract, dao_voting_cw4_contract, query_auth_contract, snip20_base_contract, snip20_stake_contract, snip20_staked_balances_voting_contract, - snip721_base_contract, }; const CREATOR_ADDR: &str = "creator"; @@ -148,7 +147,6 @@ pub fn instantiate_with_staked_balances_governance( }; let snip20_info = app.store_code(snip20_base_contract()); - let snip721_info = app.store_code(snip721_base_contract()); let snip20_stake_info = app.store_code(snip20_stake_contract()); let staked_balances_voting_info = app.store_code(snip20_staked_balances_voting_contract()); let core_contract_info = app.store_code(dao_dao_contract()); @@ -177,7 +175,6 @@ pub fn instantiate_with_staked_balances_governance( unstaking_duration: Some(Duration::Height(6)), initial_dao_balance: None, }, - dao_code_hash: core_contract_info.code_hash.clone(), query_auth: None, }) .unwrap(), @@ -197,8 +194,6 @@ pub fn instantiate_with_staked_balances_governance( query_auth_code_id: query_auth_info.code_id, query_auth_code_hash: query_auth_info.code_hash, prng_seed: "Seed".into(), - snip20_code_hash: snip20_info.code_hash.clone(), - snip721_code_hash: snip721_info.code_hash, }; let core_info = app @@ -265,7 +260,7 @@ pub fn instantiate_with_staked_balances_governance( address: token_contract.addr.clone(), code_hash: token_contract.code_hash.clone(), }, - &snip20_reference_impl::msg::ExecuteMsg::Send { + &snip20_base::msg::ExecuteMsg::Send { recipient: staking_contract.addr.clone().to_string(), recipient_code_hash: Some(staking_contract.code_hash.clone()), amount, @@ -303,7 +298,6 @@ pub fn instantiate_with_staking_active_threshold( active_threshold: Option, ) -> ContractInfo { let snip20_info = app.store_code(snip20_base_contract()); - let snip721_info = app.store_code(snip721_base_contract()); let snip20_staking_info = app.store_code(snip20_stake_contract()); let governance_info = app.store_code(dao_dao_contract()); let votemod_info = app.store_code(snip20_staked_balances_voting_contract()); @@ -345,7 +339,6 @@ pub fn instantiate_with_staking_active_threshold( initial_dao_balance: None, }, active_threshold, - dao_code_hash: governance_info.code_hash.clone(), query_auth: None, }) .unwrap(), @@ -365,8 +358,6 @@ pub fn instantiate_with_staking_active_threshold( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "Seed".to_string(), - snip20_code_hash: snip20_info.code_hash, - snip721_code_hash: snip721_info.code_hash, }; app.instantiate_contract( @@ -390,8 +381,6 @@ pub fn instantiate_with_cw4_groups_governance( let cw4_info = app.store_code(cw4_group_contract()); let core_info = app.store_code(dao_dao_contract()); let votemod_info = app.store_code(dao_voting_cw4_contract()); - let snip20_info = app.store_code(snip20_base_contract()); - let snip721_info = app.store_code(snip721_base_contract()); let query_auth = app.store_code(query_auth_contract()); let initial_weights = initial_weights.unwrap_or_default(); @@ -431,7 +420,6 @@ pub fn instantiate_with_cw4_groups_governance( initial_members: initial_weights, query_auth: None, }, - dao_code_hash: core_info.code_hash.clone(), }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -450,8 +438,6 @@ pub fn instantiate_with_cw4_groups_governance( query_auth_code_id: query_auth.code_id, query_auth_code_hash: query_auth.code_hash, prng_seed: "seed".into(), - snip20_code_hash: snip20_info.code_hash, - snip721_code_hash: snip721_info.code_hash, }; let addr = app diff --git a/packages/dao-utils/Cargo.toml b/packages/dao-utils/Cargo.toml index e353634..2ee0e7a 100644 --- a/packages/dao-utils/Cargo.toml +++ b/packages/dao-utils/Cargo.toml @@ -24,6 +24,7 @@ dao-voting ={ workspace = true } cw4 ={ workspace = true } dao-snip721-extensions = { workspace = true } snip721-roles-impl ={ workspace = true } +snip20-base ={ workspace = true } anybuf = { workspace = true } dao-interface ={ workspace = true} diff --git a/packages/dao-utils/src/msg.rs b/packages/dao-utils/src/msg.rs index 6458d36..93402ea 100644 --- a/packages/dao-utils/src/msg.rs +++ b/packages/dao-utils/src/msg.rs @@ -26,7 +26,6 @@ pub struct UncheckedConfig { pub voting_period: Duration, pub min_voting_period: Option, pub close_proposals_on_execution_failure: bool, - pub dao_code_hash: String, } impl InitCallback for ProposalCondorcetInstantiateMsg { @@ -70,10 +69,6 @@ pub struct ProposalMultipleInstantiateMsg { /// During this period an oversight account (`veto.vetoer`) can /// veto the proposal. pub veto: Option, - - // dao code hash - pub dao_code_hash: String, - pub query_auth: Option, } @@ -120,9 +115,6 @@ pub struct ProposalSingleInstantiateMsg { /// During this period an oversight account (`veto.vetoer`) can /// veto the proposal. pub veto: Option, - /// Code hash of dao - pub dao_code_hash: String, - pub query_auth: Option, } @@ -134,7 +126,6 @@ impl InitCallback for ProposalSingleInstantiateMsg { pub enum GroupContract { Existing { address: String, - code_hash: String, }, New { cw4_group_code_id: u64, @@ -147,7 +138,6 @@ pub enum GroupContract { #[cw_serde] pub struct VotingCW4nstantiateMsg { pub group_contract: GroupContract, - pub dao_code_hash: String, } impl InitCallback for VotingCW4nstantiateMsg { @@ -161,8 +151,6 @@ pub enum StakingInfo { Existing { /// Address of an already instantiated staking contract. staking_contract_address: String, - /// code hash of an already instantiated staking contract. - staking_contract_code_hash: String, }, New { /// Code ID for staking contract to instantiate. @@ -184,8 +172,6 @@ pub enum Snip20TokenInfo { Existing { /// Address of an already instantiated cw20 token contract. address: String, - /// Code hash of an already instantiated cw20 token contract. - code_hash: String, /// Information about the staking contract to use. staking_contract: StakingInfo, }, @@ -212,7 +198,6 @@ pub struct Snip20StakedInstantiateMsg { /// The number or percentage of tokens that must be staked /// for the DAO to be active pub active_threshold: Option, - pub dao_code_hash: String, pub query_auth: Option, } @@ -240,8 +225,6 @@ pub enum NftRolesContract { Existing { /// Address of an already instantiated snip721-weighted-roles token contract. address: String, - /// code hash of an already instantiated snip721-weighted-roles token contract. - code_hash: String, }, New { /// Code ID for snip721 roles contract. @@ -277,7 +260,6 @@ pub enum NftRolesContract { pub struct Snip721RolesInstantiateMsg { /// Info about the associated NFT contract pub nft_contract: NftRolesContract, - pub dao_code_hash: String, } impl InitCallback for Snip721RolesInstantiateMsg { @@ -291,8 +273,6 @@ pub enum NftContract { Existing { /// Address of an already instantiated snip721 or sg721 token contract. address: String, - /// code hash of an already instantiated snip721 or sg721 token contract. - code_hash: String, }, /// Creates a new NFT collection used for staking and governance. New { @@ -325,9 +305,6 @@ pub struct Snip721StakedInstantiateMsg { /// The number or percentage of tokens that must be staked /// for the DAO to be active pub active_threshold: Option, - - pub dao_code_hash: String, - pub query_auth: Option, } @@ -344,7 +321,6 @@ pub struct TokenStakedInstantiateMsg { /// The number or percentage of tokens that must be staked /// for the DAO to be active pub active_threshold: Option, - pub dao_code_hash: String, pub query_auth: Option, } @@ -362,3 +338,31 @@ pub enum TokenInfo { impl InitCallback for TokenStakedInstantiateMsg { const BLOCK_SIZE: usize = 256; } + + +#[cw_serde] +pub enum DaoVotingSnip20BalanceTokenInfo { + Existing { + address: String, + code_hash: String, + }, + New { + code_id: u64, + code_hash: String, + label: String, + name: String, + symbol: String, + decimals: u8, + initial_balances: Vec, + }, +} + +#[cw_serde] +pub struct DaoVotingSnip20BalanceInstantiateMsg { + pub token_info: DaoVotingSnip20BalanceTokenInfo, + pub dao_code_hash: String, +} + +impl InitCallback for DaoVotingSnip20BalanceInstantiateMsg { + const BLOCK_SIZE: usize = 256; +} \ No newline at end of file