diff --git a/Cargo.lock b/Cargo.lock index 29caf23d6..b81b4604b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -30,19 +30,13 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "anyhow" version = "1.0.75" @@ -74,7 +68,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -85,7 +79,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -152,9 +146,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -171,6 +165,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -179,9 +179,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "base64ct" @@ -220,7 +220,7 @@ checksum = "b30ed1d6f8437a487a266c8293aeb95b61a23261273e3e02912cdb8b68bf798b" dependencies = [ "bs58", "hmac", - "k256", + "k256 0.11.6", "once_cell", "pbkdf2", "rand_core 0.6.4", @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" +checksum = "128a44527fc0d6abf05f9eda748b9027536e12dff93f5acc8449f51583309350" [[package]] name = "bootstrap-env" @@ -312,9 +312,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] @@ -345,11 +345,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ - "android-tzdata", "num-traits", ] @@ -504,10 +503,10 @@ checksum = "3903590099dcf1ea580d9353034c9ba1dbf55d1389a5bd2ade98535c3445d1f9" dependencies = [ "bip32", "cosmos-sdk-proto 0.14.0", - "ecdsa", + "ecdsa 0.14.8", "eyre", "getrandom", - "k256", + "k256 0.11.6", "rand_core 0.6.4", "serde", "serde_json", @@ -525,10 +524,10 @@ checksum = "6fa07096219b1817432b8f1e47c22e928c64bbfd231fc08f0a98f0e7ddd602b7" dependencies = [ "bip32", "cosmos-sdk-proto 0.15.0", - "ecdsa", + "ecdsa 0.14.8", "eyre", "getrandom", - "k256", + "k256 0.11.6", "rand_core 0.6.4", "serde", "serde_json", @@ -540,31 +539,31 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dd316b3061747d6f57c1c4a131a5ba2f9446601a9276d05a4d25ab2ce0a7e0" +checksum = "1ca101fbf2f76723711a30ea3771ef312ec3ec254ad021b237871ed802f9f175" dependencies = [ "digest 0.10.7", "ed25519-zebra", - "k256", + "k256 0.13.1", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b14230c6942a301afb96f601af97ae09966601bd1007067a2c7fe8ffcfe303" +checksum = "c73d2dd292f60e42849d2b07c03d809cf31e128a4299a805abd6d24553bcaaf5" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027bdd5941b7d4b45bd773b6d88818dcc043e8db68916bfbd5caf971024dbea" +checksum = "6ce34a08020433989af5cc470104f6bd22134320fe0221bd8aeb919fd5ec92d5" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -575,9 +574,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e069f6e65a9a1f55f8d7423703bed35e9311d029d91b357b17a07010d95cd7" +checksum = "96694ec781a7dd6dea1f968a2529ade009c21ad999c88b5f53d6cc495b3b96f7" dependencies = [ "proc-macro2", "quote", @@ -586,11 +585,11 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27a06f0f6c35b178563c6b1044245b3f750c4a66d9f6d2b942a6b29ad77d3ae" +checksum = "2a44d3f9c25b2f864737c6605a98f2e4675d53fd8bbc7cf4d7c02475661a793d" dependencies = [ - "base64 0.13.1", + "base64 0.21.3", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -606,9 +605,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81854e8f4cb8d6d0ff956de34af56ed70c5a09cb61431dbc854982d10f8886b7" +checksum = "ab544dfcad7c9e971933d522d99ec75cc8ddfa338854bb992b092e11bcd7e818" dependencies = [ "cosmwasm-std", "serde", @@ -635,6 +634,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -848,15 +859,15 @@ dependencies = [ [[package]] name = "cw-multi-test" version = "0.16.5" -source = "git+https://github.com/JakeHartnell/cw-multi-test.git?branch=bank-supply-support#8c618c8dcc014dacf2cc10627805b36bbc0bfc6e" +source = "git+https://github.com/CosmWasm/cw-multi-test.git?rev=d38db7752b9f054c395d6108453f8b321e4cab02#d38db7752b9f054c395d6108453f8b321e4cab02" dependencies = [ "anyhow", "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "derivative", - "itertools 0.10.5", - "k256", + "itertools", + "k256 0.11.6", "prost 0.9.0", "schemars", "serde", @@ -1032,16 +1043,17 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", + "cw-ownable", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "osmosis-std 0.16.2", + "osmosis-std", "osmosis-test-tube", "prost 0.11.9", "schemars", "serde", "serde_json", "thiserror", - "token-bindings 0.11.0", + "token-bindings", ] [[package]] @@ -1298,6 +1310,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-controllers 1.1.0", + "cw-hooks", "cw-multi-test", "cw-ownable", "cw-paginate-storage 2.2.0", @@ -1308,6 +1321,8 @@ dependencies = [ "cw20 1.1.0", "cw20-base 1.1.0", "cw20-stake 0.2.6", + "dao-hooks", + "dao-voting 2.2.0", "thiserror", ] @@ -1329,6 +1344,7 @@ dependencies = [ "cw20 1.1.0", "cw20-base 1.1.0", "cw20-stake 2.2.0", + "dao-hooks", "stake-cw20-external-rewards", "thiserror", ] @@ -1632,6 +1648,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dao-hooks" +version = "2.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-hooks", + "cw4 1.1.0", + "dao-voting 2.2.0", +] + [[package]] name = "dao-interface" version = "2.2.0" @@ -1695,9 +1722,9 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", @@ -1721,10 +1748,10 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-approval-single", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", @@ -1744,8 +1771,8 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw2 1.1.0", + "dao-hooks", "dao-interface", - "dao-proposal-hooks", "dao-voting 2.2.0", "serde", "thiserror", @@ -1765,9 +1792,9 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-multiple", "dao-testing", "dao-voting 2.2.0", @@ -1790,9 +1817,9 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", @@ -1836,25 +1863,14 @@ dependencies = [ "cw20 1.1.0", "cw20-base 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", - "dao-proposal-hooks", "dao-proposal-single", - "dao-vote-hooks", "dao-voting 2.2.0", "dao-voting-cw20-balance", "thiserror", ] -[[package]] -name = "dao-proposal-hooks" -version = "2.2.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-hooks", - "dao-voting 2.2.0", -] - [[package]] name = "dao-proposal-multiple" version = "2.2.0" @@ -1876,19 +1892,18 @@ dependencies = [ "cw4-group 1.1.0", "cw721-base 0.18.0", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-pre-propose-base", "dao-pre-propose-multiple", - "dao-proposal-hooks", "dao-testing", - "dao-vote-hooks", "dao-voting 0.1.0", "dao-voting 2.2.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", "dao-voting-cw4", "dao-voting-cw721-staked", - "dao-voting-native-staked", + "dao-voting-token-staked", "rand", "thiserror", ] @@ -1918,19 +1933,18 @@ dependencies = [ "cw721-base 0.18.0", "dao-dao-core", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-pre-propose-base", "dao-pre-propose-single", - "dao-proposal-hooks", "dao-testing", - "dao-vote-hooks", "dao-voting 0.1.0", "dao-voting 2.2.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", "dao-voting-cw4", "dao-voting-cw721-staked", - "dao-voting-native-staked", + "dao-voting-token-staked", "thiserror", ] @@ -1983,25 +1997,14 @@ dependencies = [ "dao-voting-cw4", "dao-voting-cw721-roles", "dao-voting-cw721-staked", - "dao-voting-native-staked", - "dao-voting-token-factory-staked", - "osmosis-std 0.16.2", + "dao-voting-token-staked", + "osmosis-std", "osmosis-test-tube", "rand", "serde", "serde_json", "stake-cw20", - "token-bindings 0.10.3", -] - -[[package]] -name = "dao-vote-hooks" -version = "2.2.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-hooks", - "dao-voting 2.2.0", + "token-bindings", ] [[package]] @@ -2119,6 +2122,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.0", + "cw-hooks", "cw-multi-test", "cw-paginate-storage 2.2.0", "cw-storage-plus 1.1.0", @@ -2128,6 +2132,7 @@ dependencies = [ "cw721-base 0.18.0", "cw721-controllers", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-testing", "dao-voting 2.2.0", @@ -2139,28 +2144,7 @@ dependencies = [ ] [[package]] -name = "dao-voting-native-staked" -version = "2.2.0" -dependencies = [ - "anyhow", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-controllers 1.1.0", - "cw-hooks", - "cw-multi-test", - "cw-paginate-storage 2.2.0", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", - "cw2 1.1.0", - "dao-dao-macros", - "dao-interface", - "dao-voting 2.2.0", - "thiserror", -] - -[[package]] -name = "dao-voting-token-factory-staked" +name = "dao-voting-token-staked" version = "2.2.0" dependencies = [ "anyhow", @@ -2170,21 +2154,23 @@ dependencies = [ "cw-controllers 1.1.0", "cw-hooks", "cw-multi-test", + "cw-ownable", "cw-paginate-storage 2.2.0", "cw-storage-plus 1.1.0", "cw-tokenfactory-issuer", "cw-utils 1.0.1", "cw2 1.1.0", "dao-dao-macros", + "dao-hooks", "dao-interface", + "dao-proposal-hook-counter", + "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", - "osmosis-std 0.17.0-rc0", + "osmosis-std", "osmosis-test-tube", "serde", "thiserror", - "token-bindings 0.11.0", - "token-bindings-test", ] [[package]] @@ -2197,6 +2183,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -2224,6 +2220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -2246,10 +2243,24 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der 0.7.8", + "digest 0.10.7", + "elliptic-curve 0.13.5", + "rfc6979 0.4.0", + "signature 2.1.0", + "spki 0.7.2", ] [[package]] @@ -2258,7 +2269,7 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", ] [[package]] @@ -2300,16 +2311,35 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", - "der", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", "digest 0.10.7", - "ff", + "ff 0.12.1", "generic-array", - "group", - "pkcs8", + "group 0.12.1", + "pkcs8 0.9.0", "rand_core 0.6.4", - "sec1", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.3", + "digest 0.10.7", + "ff 0.13.0", + "generic-array", + "group 0.13.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -2348,18 +2378,18 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" dependencies = [ "serde", ] [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -2396,6 +2426,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "flex-error" version = "0.4.4" @@ -2483,7 +2523,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -2524,6 +2564,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2541,9 +2582,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "glob" @@ -2557,16 +2598,27 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -2598,12 +2650,11 @@ checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "headers" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", + "base64 0.21.3", "bytes", "headers-core", "http", @@ -2651,6 +2702,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -2866,15 +2926,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" @@ -2908,12 +2959,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", "sha2 0.10.7", "sha3", ] +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa 0.16.8", + "elliptic-curve 0.13.5", + "once_cell", + "sha2 0.10.7", + "signature 2.1.0", +] + [[package]] name = "keccak" version = "0.1.4" @@ -2977,9 +3042,9 @@ checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "mime" @@ -3064,9 +3129,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -3107,28 +3172,13 @@ checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "osmosis-std" -version = "0.16.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=autobuild-v17.0.0-rc0#36ef03c6d61b860c6bd25cac56bc52144c4e6b9c" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive 0.16.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=autobuild-v17.0.0-rc0)", - "prost 0.11.9", - "prost-types", - "schemars", - "serde", - "serde-cw-value", -] - -[[package]] -name = "osmosis-std" -version = "0.17.0-rc0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b022b748710ecdf1adc6a124c3bef29f17ef05e7fa1260a08889d1d53f9cc5" +checksum = "088997c4da871db9edb9e090a4e164d1cb12498781491ccb8030246287400300" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", + "osmosis-std-derive", "prost 0.11.9", "prost-types", "schemars", @@ -3142,19 +3192,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f47f0b2f22adb341bb59e5a3a1b464dde033181954bd055b9ae86d6511ba465b" dependencies = [ - "itertools 0.10.5", - "proc-macro2", - "prost-types", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "osmosis-std-derive" -version = "0.16.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=autobuild-v17.0.0-rc0#36ef03c6d61b860c6bd25cac56bc52144c4e6b9c" -dependencies = [ - "itertools 0.10.5", + "itertools", "proc-macro2", "prost-types", "quote", @@ -3163,15 +3201,15 @@ dependencies = [ [[package]] name = "osmosis-test-tube" -version = "17.0.0-rc0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977a2b4f088dd704a47e96b5914e28465cfcdb1cb1145a1f9b45c219a9b145c5" +checksum = "d176b76c0142f7a047d4c4f12a553b10de557aa60304077ca88532249924da0f" dependencies = [ "base64 0.13.1", "bindgen", "cosmrs 0.9.0", "cosmwasm-std", - "osmosis-std 0.17.0-rc0", + "osmosis-std", "prost 0.11.9", "serde", "serde_json", @@ -3241,19 +3279,20 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" dependencies = [ "pest", "pest_generator", @@ -3261,22 +3300,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" dependencies = [ "once_cell", "pest", @@ -3300,14 +3339,14 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -3321,8 +3360,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.2", ] [[package]] @@ -3379,7 +3428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2", "quote", "syn 1.0.109", @@ -3392,7 +3441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2", "quote", "syn 1.0.109", @@ -3454,9 +3503,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", @@ -3466,9 +3515,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", @@ -3477,9 +3526,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rfc6979" @@ -3487,11 +3536,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hmac", "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -3562,9 +3621,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" dependencies = [ "bitflags 2.4.0", "errno", @@ -3630,9 +3689,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" dependencies = [ "dyn-clone", "schemars_derive", @@ -3642,9 +3701,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" dependencies = [ "proc-macro2", "quote", @@ -3668,10 +3727,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", - "der", + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.8", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -3707,9 +3780,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.185" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -3743,13 +3816,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.185" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -3782,7 +3855,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -3814,9 +3887,9 @@ dependencies = [ [[package]] name = "sg-std" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171f97d3032b7d713dd16decaed06479e7ce5585147f387860ad2fb3f7b9ed94" +checksum = "4db53aebc2b4f981dc20a51213544adde8beaace6880345627f4babe2e1bc3ab" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -3910,9 +3983,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signature" @@ -3924,11 +3997,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -3966,7 +4049,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.8", ] [[package]] @@ -4061,9 +4154,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -4088,7 +4181,7 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", + "k256 0.11.6", "num-traits", "once_cell", "prost 0.11.9", @@ -4099,7 +4192,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.9.9", - "signature", + "signature 1.6.4", "subtle", "subtle-encoding", "tendermint-proto 0.23.9", @@ -4119,7 +4212,7 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", + "k256 0.11.6", "num-traits", "once_cell", "prost 0.11.9", @@ -4130,7 +4223,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.9.9", - "signature", + "signature 1.6.4", "subtle", "subtle-encoding", "tendermint-proto 0.26.0", @@ -4319,14 +4412,14 @@ dependencies = [ [[package]] name = "test-tube" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b1f7cafdf7738331999fb1465d2d3032f08ac61940e1ef4601dbbef21d6a5e" +checksum = "04de0d85f2438f0b64a5c135a1524564f2b89263cfbce011542446b6681d006f" dependencies = [ "base64 0.13.1", "cosmrs 0.9.0", "cosmwasm-std", - "osmosis-std 0.17.0-rc0", + "osmosis-std", "prost 0.11.9", "serde", "serde_json", @@ -4341,22 +4434,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -4391,22 +4484,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "token-bindings" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752a7997ebaa191cf3d8436261e449732e65268e552e8bea6133c3b21b48fe36" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "token-bindings" version = "0.11.0" -source = "git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954#0cd084b68172ffc9af29eb37fb915392ce351954" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be1c893c90d2993320d9722516ece705460f464616313a62edadb9e71df4502" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4414,22 +4496,6 @@ dependencies = [ "serde", ] -[[package]] -name = "token-bindings-test" -version = "0.11.0" -source = "git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954#0cd084b68172ffc9af29eb37fb915392ce351954" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus 0.16.0", - "itertools 0.11.0", - "schemars", - "serde", - "thiserror", - "token-bindings 0.11.0", -] - [[package]] name = "tokio" version = "1.32.0" @@ -4465,7 +4531,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -4553,7 +4619,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.2", + "base64 0.21.3", "bytes", "futures-core", "futures-util", @@ -4625,7 +4691,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -4700,9 +4766,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -4735,9 +4801,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -4779,7 +4845,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", "wasm-bindgen-shared", ] @@ -4801,7 +4867,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4843,13 +4909,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -4987,5 +5054,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] diff --git a/Cargo.toml b/Cargo.toml index 2d5bff497..0d55ba587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,8 +54,8 @@ cw721 = "0.18" cw721-base = "0.18" env_logger = "0.10" once_cell = "1.18" -osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", branch = "autobuild-v17.0.0-rc0" } -osmosis-test-tube = "17.0.0-rc0" +osmosis-std = "0.19.0" +osmosis-test-tube = "19.0.0" proc-macro2 = "1.0" prost = "0.11" quote = "1.0" @@ -71,9 +71,7 @@ sg-multi-test = "3.1.0" syn = { version = "1.0", features = ["derive"] } test-context = "0.1" thiserror = { version = "1.0" } -# TODO use upstream when PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 -token-bindings = { git = "https://github.com/CosmosContracts/token-bindings.git", rev = "0cd084b68172ffc9af29eb37fb915392ce351954" } -token-bindings-test = { git = "https://github.com/CosmosContracts/token-bindings.git", rev = "0cd084b68172ffc9af29eb37fb915392ce351954" } +token-bindings = "0.11.0" wynd-utils = "0.4" # One commit ahead of version 0.3.0. Allows initialization with an @@ -94,28 +92,27 @@ cw721-controllers = { path = "./packages/cw721-controllers", version = "2.2.0" } cw721-roles = { path = "./contracts/external/cw721-roles", version = "*" } dao-cw721-extensions = { path = "./packages/dao-cw721-extensions", version = "*" } dao-dao-core = { path = "./contracts/dao-dao-core", version = "2.2.0" } -dao-interface = { path = "./packages/dao-interface", version = "2.2.0" } dao-dao-macros = { path = "./packages/dao-dao-macros", version = "2.2.0" } +dao-hooks = { path = "./packages/dao-hooks", version = "*" } +dao-interface = { path = "./packages/dao-interface", version = "2.2.0" } dao-pre-propose-approval-single = { path = "./contracts/pre-propose/dao-pre-propose-approval-single", version = "2.2.0" } dao-pre-propose-approver = { path = "./contracts/pre-propose/dao-pre-propose-approver", version = "2.2.0" } dao-pre-propose-base = { path = "./packages/dao-pre-propose-base", version = "2.2.0" } dao-pre-propose-multiple = { path = "./contracts/pre-propose/dao-pre-propose-multiple", version = "2.2.0" } dao-pre-propose-single = { path = "./contracts/pre-propose/dao-pre-propose-single", version = "2.2.0" } dao-proposal-condorcet = { path = "./contracts/proposal/dao-proposal-condorcet", version = "2.2.0" } -dao-proposal-hooks = { path = "./packages/dao-proposal-hooks", version = "2.2.0" } dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", version = "2.2.0" } dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.2.0" } dao-proposal-sudo = { path = "./test-contracts/dao-proposal-sudo", version = "2.2.0" } +dao-proposal-hook-counter = { path = "./test-contracts/dao-proposal-hook-counter", version = "2.2.0" } dao-testing = { path = "./packages/dao-testing", version = "2.2.0" } -dao-vote-hooks = { path = "./packages/dao-vote-hooks", version = "2.2.0" } dao-voting = { path = "./packages/dao-voting", version = "2.2.0" } dao-voting-cw20-balance = { path = "./test-contracts/dao-voting-cw20-balance", version = "2.2.0" } dao-voting-cw20-staked = { path = "./contracts/voting/dao-voting-cw20-staked", version = "2.2.0" } dao-voting-cw4 = { path = "./contracts/voting/dao-voting-cw4", version = "2.2.0" } dao-voting-cw721-roles = { path = "./contracts/voting/dao-voting-cw721-roles", version = "*" } dao-voting-cw721-staked = { path = "./contracts/voting/dao-voting-cw721-staked", version = "2.2.0" } -dao-voting-native-staked = { path = "./contracts/voting/dao-voting-native-staked", version = "2.2.0" } -dao-voting-token-factory-staked = { path = "./contracts/voting/dao-voting-token-factory-staked", version = "2.2.0" } +dao-voting-token-staked = { path = "./contracts/voting/dao-voting-token-staked", version = "2.2.0" } # v1 dependencies. used for state migrations. cw-core-v1 = { package = "cw-core", version = "0.1.0" } @@ -129,6 +126,6 @@ cw4-voting-v1 = { package = "cw4-voting", version = "0.1.0" } voting-v1 = { package = "dao-voting", version = "0.1.0" } stake-cw20-v03 = { package = "stake-cw20", version = "0.2.6" } -# TODO remove when upstream PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 +# TODO remove when new release is tagged upstream [patch.crates-io] -cw-multi-test = { git = "https://github.com/JakeHartnell/cw-multi-test.git", branch = "bank-supply-support" } +cw-multi-test = { git = "https://github.com/CosmWasm/cw-multi-test.git", rev = "d38db7752b9f054c395d6108453f8b321e4cab02" } diff --git a/ci/integration-tests/src/helpers/helper.rs b/ci/integration-tests/src/helpers/helper.rs index 3c685c22d..4d30c1670 100644 --- a/ci/integration-tests/src/helpers/helper.rs +++ b/ci/integration-tests/src/helpers/helper.rs @@ -118,15 +118,16 @@ pub fn create_dao( .unwrap(); let ProposalCreationPolicy::Module { addr: pre_propose } = chain - .orc - .query( - "dao_proposal_single", - &dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {} - ).unwrap() - .data() - .unwrap() + .orc + .query( + "dao_proposal_single", + &dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {}, + ) + .unwrap() + .data() + .unwrap() else { - panic!("expected pre-propose module") + panic!("expected pre-propose module") }; chain .orc diff --git a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs index e08d81fa9..dd13c47fb 100644 --- a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs +++ b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs @@ -1,7 +1,6 @@ use cosm_orc::orchestrator::{ExecReq, SigningKey}; use cosmwasm_std::{Binary, Empty, Uint128}; use cw_utils::Duration; -use dao_interface::state::Admin; use test_context::test_context; use dao_voting_cw721_staked as module; @@ -38,7 +37,6 @@ pub fn instantiate_cw721_base(chain: &mut Chain, key: &SigningKey, minter: &str) fn setup_test( chain: &mut Chain, - owner: Option, unstaking_duration: Option, key: &SigningKey, minter: &str, @@ -50,7 +48,6 @@ fn setup_test( CONTRACT_NAME, "instantiate_dao_voting_cw721_staked", &module::msg::InstantiateMsg { - owner, nft_contract: module::msg::NftContract::Existing { address: cw721.clone(), }, @@ -169,7 +166,7 @@ fn cw721_stake_tokens(chain: &mut Chain) { let user_addr = chain.users["user1"].account.address.clone(); let user_key = chain.users["user1"].key.clone(); - let CommonTest { module, .. } = setup_test(chain, None, None, &user_key, &user_addr); + let CommonTest { module, .. } = setup_test(chain, None, &user_key, &user_addr); mint_and_stake_nft(chain, &user_key, &user_addr, &module, "a"); @@ -202,13 +199,8 @@ fn cw721_stake_max_claims_works(chain: &mut Chain) { let user_addr = chain.users["user1"].account.address.clone(); let user_key = chain.users["user1"].key.clone(); - let CommonTest { module, .. } = setup_test( - chain, - None, - Some(Duration::Height(1)), - &user_key, - &user_addr, - ); + let CommonTest { module, .. } = + setup_test(chain, Some(Duration::Height(1)), &user_key, &user_addr); // Create `MAX_CLAIMS` claims. diff --git a/contracts/dao-dao-core/schema/dao-dao-core.json b/contracts/dao-dao-core/schema/dao-dao-core.json index fec05f159..c2e9cf422 100644 --- a/contracts/dao-dao-core/schema/dao-dao-core.json +++ b/contracts/dao-dao-core/schema/dao-dao-core.json @@ -1029,7 +1029,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1147,7 +1147,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1398,7 +1398,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/external/cw-tokenfactory-issuer/Cargo.toml b/contracts/external/cw-tokenfactory-issuer/Cargo.toml index 10161d182..66fc81ada 100644 --- a/contracts/external/cw-tokenfactory-issuer/Cargo.toml +++ b/contracts/external/cw-tokenfactory-issuer/Cargo.toml @@ -37,8 +37,9 @@ test-tube = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-storage = { workspace = true } -cw-storage-plus = { workspace = true } cw2 = { workspace = true } +cw-ownable = { workspace = true } +cw-storage-plus = { workspace = true } osmosis-std = { workspace = true } prost = { workspace = true } schemars = { workspace = true } diff --git a/contracts/external/cw-tokenfactory-issuer/README.md b/contracts/external/cw-tokenfactory-issuer/README.md index fb615da62..1cfdcf352 100644 --- a/contracts/external/cw-tokenfactory-issuer/README.md +++ b/contracts/external/cw-tokenfactory-issuer/README.md @@ -6,27 +6,28 @@ This repo contains a set of contracts that when used in conjunction with the x/t - Creating a new Token Factory token or using an existing one - Granting and revoking allowances for the minting and burning of tokens - Updating token metadata -- Freezing and unfreezing transfers, with a whitelist to allow some address to continue to transfer -- Force transfering tokens via the contract owner - Updating the contract owner or Token Factory admin +- And more! (see [Advanced Features](#advanced-features)) It is intended to work on multiple chains supporting Token Factory, and has been tested on Juno Network and Osmosis. -The contract has an owner (which can be removed or updated via `ExecuteMsg::ChangeContractOwner {}`), but it can delegate capabilities to other acccounts. For example, the owner of a contract can delegate minting allowance of 1000 tokens to a new address. +The contract has an owner (which can be removed or updated via `ExecuteMsg::UpdateOwnership {}`), but it can delegate capabilities to other acccounts. For example, the owner of a contract can delegate minting allowance of 1000 tokens to a new address. -The contract is also the admin of the newly created Token Factory denom. For minting and burning, users then interact with the contract using its own ExecuteMsgs which trigger the contract's access control logic, and the contract then dispatches tokenfactory sdk.Msgs from its own contract account. +Ownership functionality for this contract is implemented using the `cw-ownable` library. -NOTE: this contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the blacklisting of specific accounts as well as the freezing of all transfers if necessary. This feature is not enabled on every chain using Token Factory, and so blacklisting and freezing features are disabled if `MsgBeforeSendHook` is not supported. DAOs wishing to leverage these features on chains after support is added can call `ExecuteMsg::SetBeforeSendHook {}`. +The `cw_tokenfactory_issuer` contract is also the admin of newly created Token Factory denoms. For minting and burning, users then interact with the contract using its own ExecuteMsgs which trigger the contract's access control logic, and the contract then dispatches tokenfactory sdk.Msgs from its own contract account. ## Instantiation -When instantiating `cw-tokenfactory-issuer`, you can either create a `new_token` or an `existing_token`. + +When instantiating `cw-tokenfactory-issuer`, you can either create a `new` or an `existing`. ### Creating a new Token Factory token + To create a new Token Factory token, simply instantiate the contract with a `subdenom`, this will create a new contract as well as a token with a denom formatted as `factory/{contract_address}/{subdenom}`. Example instantiate message: -``` json +```json { "new_token": { "subdenom": "test" @@ -37,13 +38,67 @@ Example instantiate message: All other updates can be preformed afterwards via this contract's `ExecuteMsg` enum. See `src/msg.rs` for available methods. ### Using an Existing Token + You can also instantiate this contract with an existing token, however most features will not be available until the previous Token Factory admin transfers admin rights to the instantiated contract and optionally calls `ExecuteMsg::SetBeforeSendHook {}` to enable dependent features. Example instantiate message: -``` json + +```json { "existing_token": { "denom": "factory/{contract_address}/{subdenom}" } } ``` + +## Renouncing Token Factory Admin +Some DAOs or protocols after the initial setup phase may wish to render their tokens immutable, permanently disabling features of this contract. + +To do so, they must execute a `ExcuteMessage::UpdateTokenFactoryAdmin {}` method, setting the Admin to a null address or the bank module for your respective chain. + +For example, on Juno this could be: + +``` json +{ + "update_token_factory_admin": { + "new_admin": "juno1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + } +} +``` + +The Token Factory standard requires a Token Factory admin per token, by setting to a null address the Token is rendered immutable and the `cw-tokenfactory-issuer` will be unable to make future updates. This is secure as the cryptography that underlies the chain enforces that even with the largest super computers in the world it would take an astonomically large amount of time to compute the private key for this address. + +### Advanced Features + +This contract supports a number of advanced features which DAOs or token issuers may wist to leverage: +- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to allow transfer to or from +- Denylist to prevent certain addresses from transferring +- Force transfering tokens via the contract owner + +**By default, these features are disabled**, and must be explictly enabled by the contract owner (for example via a DAO governance prop). + +Moreover, for these features to work, your chain must support the `MsgBeforeSendHook` bank module hook. This is not yet available on every chain using Token Factory, and so denylisting and freezing features are not available if `MsgBeforeSendHook` is not supported. + +On chains where `MsgBeforeSendHook` is supported, DAOs or issuers wishing to leverage these features must set the before send hook with `ExecuteMsg::SetBeforeSendHook {}`. + +This method takes a `cosmwasm_address`, which is the address of a contract implement a `SudoMsg::BlockBeforeSend` entrypoint. Normally this will be the address of the `cw_tokenfactory_issuer` contract itself, but it is possible to specify a custom contract. This contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the denylisting of specific accounts as well as the freezing of all transfers if necessary. + +Example message to set before send hook: +``` json +{ + "set_before_send_hook": { + "cosmwasm_address": "
" + } +} +``` + +DAOs or issuers wishing to leverage these features on chains without support can call `ExecuteMsg::SetBeforeSendHook {}` when support is added. + +If a DAO or issuer wishes to disable and removed before send hook related functionality, they simply need to call `ExecuteMsg::SetBeforeSendHook {}` with an empty string for the `cosmwasm_address` like so: +``` json +{ + "set_before_send_hook": { + "cosmwasm_address": "" + } +} +``` diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json index 42ade5b4f..ca5ea62fb 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -5,6 +5,7 @@ "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", + "description": "The message used to create a new instance of this smart contract.", "oneOf": [ { "description": "`NewToken` will create a new token when instantiate the contract. Newly created token will have full denom as `factory//`. It will be attached to the contract setup the beforesend listener automatically.", @@ -56,22 +57,27 @@ "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", + "description": "State changing methods available to this smart contract.", "oneOf": [ { - "description": "Change the admin of the Token Factory denom itself.", + "description": "Allow adds the target address to the allowlist to be able to send or recieve tokens even if the token is frozen. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature to work.\n\nThis functionality is intedended for DAOs who do not wish to have a their tokens liquid while bootstrapping their DAO. For example, a DAO may wish to white list a Token Staking contract (to allow users to stake their tokens in the DAO) or a Merkle Drop contract (to allow users to claim their tokens).", "type": "object", "required": [ - "change_token_factory_admin" + "allow" ], "properties": { - "change_token_factory_admin": { + "allow": { "type": "object", "required": [ - "new_admin" + "address", + "status" ], "properties": { - "new_admin": { + "address": { "type": "string" + }, + "status": { + "type": "boolean" } }, "additionalProperties": false @@ -80,19 +86,23 @@ "additionalProperties": false }, { - "description": "Change the owner of this contract who is allowed to call privileged methods.", + "description": "Burn token to address. Burn allowance is required and wiil be deducted after successful burn.", "type": "object", "required": [ - "change_contract_owner" + "burn" ], "properties": { - "change_contract_owner": { + "burn": { "type": "object", "required": [ - "new_owner" + "amount", + "from_address" ], "properties": { - "new_owner": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from_address": { "type": "string" } }, @@ -102,46 +112,24 @@ "additionalProperties": false }, { - "description": "Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata.", - "type": "object", - "required": [ - "set_denom_metadata" - ], - "properties": { - "set_denom_metadata": { - "type": "object", - "required": [ - "metadata" - ], - "properties": { - "metadata": { - "$ref": "#/definitions/Metadata" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Grant/revoke mint allowance.", + "description": "Mint token to address. Mint allowance is required and wiil be deducted after successful mint.", "type": "object", "required": [ - "set_minter_allowance" + "mint" ], "properties": { - "set_minter_allowance": { + "mint": { "type": "object", "required": [ - "address", - "allowance" + "amount", + "to_address" ], "properties": { - "address": { - "type": "string" - }, - "allowance": { + "amount": { "$ref": "#/definitions/Uint128" + }, + "to_address": { + "type": "string" } }, "additionalProperties": false @@ -150,24 +138,24 @@ "additionalProperties": false }, { - "description": "Grant/revoke burn allowance.", + "description": "Deny adds the target address to the denylist, whis prevents them from sending/receiving the token attached to this contract tokenfactory's BeforeSendHook listener must be set to this contract in order for this feature to work as intended.", "type": "object", "required": [ - "set_burner_allowance" + "deny" ], "properties": { - "set_burner_allowance": { + "deny": { "type": "object", "required": [ "address", - "allowance" + "status" ], "properties": { "address": { "type": "string" }, - "allowance": { - "$ref": "#/definitions/Uint128" + "status": { + "type": "boolean" } }, "additionalProperties": false @@ -176,22 +164,18 @@ "additionalProperties": false }, { - "description": "Grant/revoke permission to blacklist addresses", + "description": "Block every token transfers of the token attached to this contract. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature to work as intended.", "type": "object", "required": [ - "set_blacklister" + "freeze" ], "properties": { - "set_blacklister": { + "freeze": { "type": "object", "required": [ - "address", "status" ], "properties": { - "address": { - "type": "string" - }, "status": { "type": "boolean" } @@ -202,24 +186,28 @@ "additionalProperties": false }, { - "description": "Grant/revoke permission to blacklist addresses", + "description": "Force transfer token from one address to another.", "type": "object", "required": [ - "set_whitelister" + "force_transfer" ], "properties": { - "set_whitelister": { + "force_transfer": { "type": "object", "required": [ - "address", - "status" + "amount", + "from_address", + "to_address" ], "properties": { - "address": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from_address": { "type": "string" }, - "status": { - "type": "boolean" + "to_address": { + "type": "string" } }, "additionalProperties": false @@ -228,38 +216,20 @@ "additionalProperties": false }, { - "description": "Attempt to SetBeforeSendHook on the token attached to this contract. This will fail if the token already has a SetBeforeSendHook or the chain still does not support it.", + "description": "Attempt to SetBeforeSendHook on the token attached to this contract. This will fail if the chain does not support bank module hooks (many Token Factory implementations do not yet support).\n\nThis takes a cosmwasm_address as an argument, which is the address of the contract that will be called before every token transfer. Normally, this will be the issuer contract itself, though it can be a custom contract for greater flexibility.\n\nSetting the address to an empty string will remove the SetBeforeSendHook.\n\nThis method can only be called by the contract owner.", "type": "object", "required": [ "set_before_send_hook" ], "properties": { "set_before_send_hook": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Grant/revoke permission to freeze the token", - "type": "object", - "required": [ - "set_freezer" - ], - "properties": { - "set_freezer": { "type": "object", "required": [ - "address", - "status" + "cosmwasm_address" ], "properties": { - "address": { + "cosmwasm_address": { "type": "string" - }, - "status": { - "type": "boolean" } }, "additionalProperties": false @@ -268,24 +238,24 @@ "additionalProperties": false }, { - "description": "Mint token to address. Mint allowance is required and wiil be deducted after successful mint.", + "description": "Grant/revoke burn allowance.", "type": "object", "required": [ - "mint" + "set_burner_allowance" ], "properties": { - "mint": { + "set_burner_allowance": { "type": "object", "required": [ - "amount", - "to_address" + "address", + "allowance" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "to_address": { + "address": { "type": "string" + }, + "allowance": { + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false @@ -294,24 +264,20 @@ "additionalProperties": false }, { - "description": "Burn token to address. Burn allowance is required and wiil be deducted after successful burn.", + "description": "Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata.", "type": "object", "required": [ - "burn" + "set_denom_metadata" ], "properties": { - "burn": { + "set_denom_metadata": { "type": "object", "required": [ - "amount", - "from_address" + "metadata" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "from_address": { - "type": "string" + "metadata": { + "$ref": "#/definitions/Metadata" } }, "additionalProperties": false @@ -320,24 +286,24 @@ "additionalProperties": false }, { - "description": "Block target address from sending/receiving token attached to this contract tokenfactory's beforesend listener must be set to this contract in order for it to work as intended.", + "description": "Grant/revoke mint allowance.", "type": "object", "required": [ - "blacklist" + "set_minter_allowance" ], "properties": { - "blacklist": { + "set_minter_allowance": { "type": "object", "required": [ "address", - "status" + "allowance" ], "properties": { "address": { "type": "string" }, - "status": { - "type": "boolean" + "allowance": { + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false @@ -346,24 +312,20 @@ "additionalProperties": false }, { - "description": "Whitelist target address to be able to send tokens even if the token is frozen.", + "description": "Updates the admin of the Token Factory token. Normally this is the cw-tokenfactory-issuer contract itself. This is intended to be used only if you seek to transfer ownership of the Token somewhere else (i.e. to another management contract).", "type": "object", "required": [ - "whitelist" + "update_token_factory_admin" ], "properties": { - "whitelist": { + "update_token_factory_admin": { "type": "object", "required": [ - "address", - "status" + "new_admin" ], "properties": { - "address": { + "new_admin": { "type": "string" - }, - "status": { - "type": "boolean" } }, "additionalProperties": false @@ -372,59 +334,71 @@ "additionalProperties": false }, { - "description": "Block every token transfers of the token attached to this contract tokenfactory's beforesend listener must be set to this contract in order for it to work as intended.", + "description": "Updates the owner of this contract who is allowed to call privileged methods. NOTE: this is separate from the Token Factory token admin, for this contract to work at all, it needs to the be the Token Factory token admin.\n\nNormally, the contract owner will be a DAO.\n\nThe `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", "type": "object", "required": [ - "freeze" + "update_ownership" ], "properties": { - "freeze": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false + "update_ownership": { + "$ref": "#/definitions/Action" } }, "additionalProperties": false - }, - { - "description": "Force transfer token from one address to another.", - "type": "object", - "required": [ - "force_transfer" - ], - "properties": { - "force_transfer": { + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", "type": "object", "required": [ - "amount", - "from_address", - "to_address" + "transfer_ownership" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "from_address": { - "type": "string" - }, - "to_address": { - "type": "string" + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] } - }, - "additionalProperties": false - } - ], - "definitions": { + ] + }, "DenomUnit": { "description": "DenomUnit represents a struct that describes a given denomination unit of the basic token.", "type": "object", @@ -453,6 +427,53 @@ } } }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Metadata": { "description": "Metadata represents a struct that describes a basic token.", "type": "object", @@ -493,18 +514,31 @@ } } }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" } } }, "query": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", + "description": "Queries supported by this smart contract.", "oneOf": [ { - "description": "IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse", + "description": "Returns if token transfer is disabled. Response: IsFrozenResponse", "type": "object", "required": [ "is_frozen" @@ -518,7 +552,7 @@ "additionalProperties": false }, { - "description": "Denom returns the token denom that this contract is the admin for. Response: DenomResponse", + "description": "Returns the token denom that this contract is the admin for. Response: DenomResponse", "type": "object", "required": [ "denom" @@ -532,13 +566,12 @@ "additionalProperties": false }, { - "description": "Owner returns the owner of the contract. Response: OwnerResponse", "type": "object", "required": [ - "owner" + "ownership" ], "properties": { - "owner": { + "ownership": { "type": "object", "additionalProperties": false } @@ -546,7 +579,7 @@ "additionalProperties": false }, { - "description": "Allowance returns the allowance of the specified address. Response: AllowanceResponse", + "description": "Returns the burn allowance of the specified address. Response: AllowanceResponse", "type": "object", "required": [ "burn_allowance" @@ -568,7 +601,7 @@ "additionalProperties": false }, { - "description": "Allowances Enumerates over all allownances. Response: Vec", + "description": "Enumerates over all burn allownances. Response: AllowancesResponse", "type": "object", "required": [ "burn_allowances" @@ -598,7 +631,7 @@ "additionalProperties": false }, { - "description": "Allowance returns the allowance of the specified user. Response: AllowanceResponse", + "description": "Returns the mint allowance of the specified user. Response: AllowanceResponse", "type": "object", "required": [ "mint_allowance" @@ -620,7 +653,7 @@ "additionalProperties": false }, { - "description": "Allowances Enumerates over all allownances. Response: AllowancesResponse", + "description": "Enumerates over all mint allownances. Response: AllowancesResponse", "type": "object", "required": [ "mint_allowances" @@ -650,13 +683,13 @@ "additionalProperties": false }, { - "description": "IsBlacklisted returns wether the user is blacklisted or not. Response: StatusResponse", + "description": "Returns wether the user is on denylist or not. Response: StatusResponse", "type": "object", "required": [ - "is_blacklisted" + "is_denied" ], "properties": { - "is_blacklisted": { + "is_denied": { "type": "object", "required": [ "address" @@ -672,13 +705,13 @@ "additionalProperties": false }, { - "description": "Blacklistees enumerates over all addresses on the blacklist. Response: BlacklisteesResponse", + "description": "Enumerates over all addresses on the denylist. Response: DenylistResponse", "type": "object", "required": [ - "blacklistees" + "denylist" ], "properties": { - "blacklistees": { + "denylist": { "type": "object", "properties": { "limit": { @@ -702,13 +735,13 @@ "additionalProperties": false }, { - "description": "IsBlacklister returns if the addres has blacklister privileges. Response: StatusResponse", + "description": "Returns wether the user is on the allowlist or not. Response: StatusResponse", "type": "object", "required": [ - "is_blacklister" + "is_allowed" ], "properties": { - "is_blacklister": { + "is_allowed": { "type": "object", "required": [ "address" @@ -724,13 +757,13 @@ "additionalProperties": false }, { - "description": "Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklisterAllowancesResponse", + "description": "Enumerates over all addresses on the allowlist. Response: AllowlistResponse", "type": "object", "required": [ - "blacklister_allowances" + "allowlist" ], "properties": { - "blacklister_allowances": { + "allowlist": { "type": "object", "properties": { "limit": { @@ -754,156 +787,14 @@ "additionalProperties": false }, { - "description": "IsWhitelisted returns wether the user is whitelisted or not. Response: StatusResponse", + "description": "Returns information about the BeforeSendHook for the token. Note: many Token Factory chains do not yet support this feature.\n\nThe information returned is: - Whether features in this contract that require MsgBeforeSendHook are enabled. - The address of the BeforeSendHook contract if configured.\n\nResponse: BeforeSendHookInfo", "type": "object", "required": [ - "is_whitelisted" + "before_send_hook_info" ], "properties": { - "is_whitelisted": { + "before_send_hook_info": { "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Whitelistees enumerates over all addresses on the whitelist. Response: WhitelisteesResponse", - "type": "object", - "required": [ - "whitelistees" - ], - "properties": { - "whitelistees": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "IsWhitelister returns if the addres has whitelister privileges. Response: StatusResponse", - "type": "object", - "required": [ - "is_whitelister" - ], - "properties": { - "is_whitelister": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelisterAllowancesResponse", - "type": "object", - "required": [ - "whitelister_allowances" - ], - "properties": { - "whitelister_allowances": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "IsFreezer returns whether the address has freezer status. Response: StatusResponse", - "type": "object", - "required": [ - "is_freezer" - ], - "properties": { - "is_freezer": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "FreezerAllowances enumerates over all freezer addresses. Response: FreezerAllowancesResponse", - "type": "object", - "required": [ - "freezer_allowances" - ], - "properties": { - "freezer_allowances": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, "additionalProperties": false } }, @@ -970,15 +861,16 @@ } }, "responses": { - "blacklistees": { + "allowlist": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BlacklisteesResponse", + "title": "AllowlistResponse", + "description": "Returns a list of addresses currently on the allowlist", "type": "object", "required": [ - "blacklistees" + "allowlist" ], "properties": { - "blacklistees": { + "allowlist": { "type": "array", "items": { "$ref": "#/definitions/StatusInfo" @@ -988,6 +880,7 @@ "additionalProperties": false, "definitions": { "StatusInfo": { + "description": "Account info for list queries related to allowlist and denylist", "type": "object", "required": [ "address", @@ -1005,44 +898,33 @@ } } }, - "blacklister_allowances": { + "before_send_hook_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BlacklisterAllowancesResponse", + "title": "BeforeSendHookInfo", + "description": "Whether or not features that require MsgBeforeSendHook are enabled Many Token Factory chains do not yet support MsgBeforeSendHook", "type": "object", "required": [ - "blacklisters" + "advanced_features_enabled" ], "properties": { - "blacklisters": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } + "advanced_features_enabled": { + "description": "Whether or not features in this contract that require MsgBeforeSendHook are enabled.", + "type": "boolean" + }, + "hook_contract_address": { + "description": "The address of the contract that implements the BeforeSendHook interface. Most often this will be the cw_tokenfactory_issuer contract itself.", + "type": [ + "string", + "null" + ] } }, - "additionalProperties": false, - "definitions": { - "StatusInfo": { - "type": "object", - "required": [ - "address", - "status" - ], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } + "additionalProperties": false }, "burn_allowance": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowanceResponse", + "description": "Returns a mint or burn allowance for a particular address, representing the amount of tokens the account is allowed to mint or burn", "type": "object", "required": [ "allowance" @@ -1063,6 +945,7 @@ "burn_allowances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowancesResponse", + "description": "Returns a list of all mint or burn allowances", "type": "object", "required": [ "allowances" @@ -1078,6 +961,7 @@ "additionalProperties": false, "definitions": { "AllowanceInfo": { + "description": "Information about a particular account and its mint / burn allowances. Used in list queries.", "type": "object", "required": [ "address", @@ -1102,6 +986,7 @@ "denom": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "DenomResponse", + "description": "Returns the full denomination for the Token Factory token. For example: `factory/{contract address}/{subdenom}`", "type": "object", "required": [ "denom" @@ -1113,15 +998,16 @@ }, "additionalProperties": false }, - "freezer_allowances": { + "denylist": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "FreezerAllowancesResponse", + "title": "DenylistResponse", + "description": "Returns a list of addresses currently on the denylist.", "type": "object", "required": [ - "freezers" + "denylist" ], "properties": { - "freezers": { + "denylist": { "type": "array", "items": { "$ref": "#/definitions/StatusInfo" @@ -1131,6 +1017,7 @@ "additionalProperties": false, "definitions": { "StatusInfo": { + "description": "Account info for list queries related to allowlist and denylist", "type": "object", "required": [ "address", @@ -1148,9 +1035,10 @@ } } }, - "is_blacklisted": { + "is_allowed": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", + "description": "Whether a particular account is allowed or denied", "type": "object", "required": [ "status" @@ -1162,23 +1050,10 @@ }, "additionalProperties": false }, - "is_blacklister": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "is_freezer": { + "is_denied": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", + "description": "Whether a particular account is allowed or denied", "type": "object", "required": [ "status" @@ -1193,6 +1068,7 @@ "is_frozen": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "IsFrozenResponse", + "description": "Returns whether or not the Token Factory token is frozen and transfers are disabled.", "type": "object", "required": [ "is_frozen" @@ -1204,37 +1080,10 @@ }, "additionalProperties": false }, - "is_whitelisted": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "is_whitelister": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, "mint_allowance": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowanceResponse", + "description": "Returns a mint or burn allowance for a particular address, representing the amount of tokens the account is allowed to mint or burn", "type": "object", "required": [ "allowance" @@ -1255,6 +1104,7 @@ "mint_allowances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowancesResponse", + "description": "Returns a list of all mint or burn allowances", "type": "object", "required": [ "allowances" @@ -1270,6 +1120,7 @@ "additionalProperties": false, "definitions": { "AllowanceInfo": { + "description": "Information about a particular account and its mint / burn allowances. Used in list queries.", "type": "object", "required": [ "address", @@ -1291,87 +1142,110 @@ } } }, - "owner": { + "ownership": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerResponse", + "title": "Ownership_for_Addr", + "description": "The contract's ownership info", "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - }, - "whitelistees": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WhitelisteesResponse", - "type": "object", - "required": [ - "whitelistees" - ], "properties": { - "whitelistees": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "StatusInfo": { - "type": "object", - "required": [ - "address", - "status" - ], - "properties": { - "address": { - "type": "string" + "owner": { + "description": "The contract's current owner. `None` if the ownership has been renounced.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" }, - "status": { - "type": "boolean" + { + "type": "null" } - }, - "additionalProperties": false - } - } - }, - "whitelister_allowances": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WhitelisterAllowancesResponse", - "type": "object", - "required": [ - "whitelisters" - ], - "properties": { - "whitelisters": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } + ] + }, + "pending_expiry": { + "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pending_owner": { + "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, "definitions": { - "StatusInfo": { - "type": "object", - "required": [ - "address", - "status" - ], - "properties": { - "address": { - "type": "string" + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false }, - "status": { - "type": "boolean" + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" } } } diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 144486cb2..8d550284e 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -3,39 +3,46 @@ use std::convert::TryInto; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, SubMsgResult, + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, }; use cosmwasm_std::{CosmosMsg, Reply}; -use cw2::set_contract_version; -use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ - MsgCreateDenom, MsgCreateDenomResponse, MsgSetBeforeSendHook, -}; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; +use osmosis_std::types::osmosis::tokenfactory::v1beta1::{MsgCreateDenom, MsgCreateDenomResponse}; +use token_bindings::TokenFactoryMsg; use crate::error::ContractError; use crate::execute; use crate::hooks; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; use crate::queries; -use crate::state::{BEFORE_SEND_HOOK_FEATURES_ENABLED, DENOM, IS_FROZEN, OWNER}; +use crate::state::{BeforeSendHookInfo, BEFORE_SEND_HOOK_INFO, DENOM, IS_FROZEN}; -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:cw-tokenfactory-issuer"; +// Version info for migration +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const CREATE_DENOM_REPLY_ID: u64 = 1; -const BEFORE_SEND_HOOK_REPLY_ID: u64 = 2; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: InstantiateMsg, ) -> Result, ContractError> { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - OWNER.save(deps.storage, &info.sender)?; + // Owner is the sender of the initial InstantiateMsg + cw_ownable::initialize_owner(deps.storage, deps.api, Some(info.sender.as_str()))?; + + // BeforeSendHook features are disabled by default. + BEFORE_SEND_HOOK_INFO.save( + deps.storage, + &BeforeSendHookInfo { + advanced_features_enabled: false, + hook_contract_address: None, + }, + )?; IS_FROZEN.save(deps.storage, &false)?; match msg { @@ -45,8 +52,7 @@ pub fn instantiate( .add_attribute("owner", info.sender) .add_attribute("subdenom", subdenom.clone()) .add_submessage( - // create new denom, if denom is created successfully, - // set beforesend listener to this contract on reply + // Create new denom, denom info is saved in the reply SubMsg::reply_on_success( >::from(MsgCreateDenom { sender: env.contract.address.to_string(), @@ -59,10 +65,6 @@ pub fn instantiate( InstantiateMsg::ExistingToken { denom } => { DENOM.save(deps.storage, &denom)?; - // BeforeSendHook cannot be set with existing tokens - // features that rely on it are disabled - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &false)?; - Ok(Response::new() .add_attribute("action", "instantiate") .add_attribute("owner", info.sender) @@ -73,7 +75,7 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, @@ -87,12 +89,8 @@ pub fn execute( amount, from_address: address, } => execute::burn(deps, env, info, amount, address), - ExecuteMsg::Blacklist { address, status } => { - execute::blacklist(deps, info, address, status) - } - ExecuteMsg::Whitelist { address, status } => { - execute::whitelist(deps, info, address, status) - } + ExecuteMsg::Deny { address, status } => execute::deny(deps, env, info, address, status), + ExecuteMsg::Allow { address, status } => execute::allow(deps, info, address, status), ExecuteMsg::Freeze { status } => execute::freeze(deps, info, status), ExecuteMsg::ForceTransfer { amount, @@ -101,11 +99,11 @@ pub fn execute( } => execute::force_transfer(deps, env, info, amount, from_address, to_address), // Admin functions - ExecuteMsg::ChangeTokenFactoryAdmin { new_admin } => { - execute::change_tokenfactory_admin(deps, info, new_admin) + ExecuteMsg::UpdateTokenFactoryAdmin { new_admin } => { + execute::update_tokenfactory_admin(deps, info, new_admin) } - ExecuteMsg::ChangeContractOwner { new_owner } => { - execute::change_contract_owner(deps, info, new_owner) + ExecuteMsg::UpdateOwnership(action) => { + execute::update_contract_owner(deps, env, info, action) } ExecuteMsg::SetMinterAllowance { address, allowance } => { execute::set_minter(deps, info, address, allowance) @@ -113,15 +111,8 @@ pub fn execute( ExecuteMsg::SetBurnerAllowance { address, allowance } => { execute::set_burner(deps, info, address, allowance) } - ExecuteMsg::SetBeforeSendHook {} => execute::set_before_send_hook(deps, env, info), - ExecuteMsg::SetBlacklister { address, status } => { - execute::set_blacklister(deps, info, address, status) - } - ExecuteMsg::SetWhitelister { address, status } => { - execute::set_whitelister(deps, info, address, status) - } - ExecuteMsg::SetFreezer { address, status } => { - execute::set_freezer(deps, info, address, status) + ExecuteMsg::SetBeforeSendHook { cosmwasm_address } => { + execute::set_before_send_hook(deps, env, info, cosmwasm_address) } ExecuteMsg::SetDenomMetadata { metadata } => { execute::set_denom_metadata(deps, env, info, metadata) @@ -130,11 +121,7 @@ pub fn execute( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn sudo( - deps: DepsMut, - _env: Env, - msg: SudoMsg, -) -> Result { +pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result { match msg { SudoMsg::BlockBeforeSend { from, to, amount } => { hooks::beforesend_hook(deps, from, to, amount) @@ -143,69 +130,54 @@ pub fn sudo( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::IsFrozen {} => to_binary(&queries::query_is_frozen(deps)?), - QueryMsg::Denom {} => to_binary(&queries::query_denom(deps)?), - QueryMsg::Owner {} => to_binary(&queries::query_owner(deps)?), + QueryMsg::Allowlist { start_after, limit } => { + to_binary(&queries::query_allowlist(deps, start_after, limit)?) + } + QueryMsg::BeforeSendHookInfo {} => { + to_binary(&queries::query_before_send_hook_features(deps)?) + } QueryMsg::BurnAllowance { address } => { to_binary(&queries::query_burn_allowance(deps, address)?) } QueryMsg::BurnAllowances { start_after, limit } => { to_binary(&queries::query_burn_allowances(deps, start_after, limit)?) } + QueryMsg::Denom {} => to_binary(&queries::query_denom(deps)?), + QueryMsg::Denylist { start_after, limit } => { + to_binary(&queries::query_denylist(deps, start_after, limit)?) + } + QueryMsg::IsAllowed { address } => to_binary(&queries::query_is_allowed(deps, address)?), + QueryMsg::IsDenied { address } => to_binary(&queries::query_is_denied(deps, address)?), + QueryMsg::IsFrozen {} => to_binary(&queries::query_is_frozen(deps)?), + QueryMsg::Ownership {} => to_binary(&queries::query_owner(deps)?), QueryMsg::MintAllowance { address } => { to_binary(&queries::query_mint_allowance(deps, address)?) } QueryMsg::MintAllowances { start_after, limit } => { to_binary(&queries::query_mint_allowances(deps, start_after, limit)?) } - QueryMsg::IsBlacklisted { address } => { - to_binary(&queries::query_is_blacklisted(deps, address)?) - } - QueryMsg::Blacklistees { start_after, limit } => { - to_binary(&queries::query_blacklistees(deps, start_after, limit)?) - } - QueryMsg::IsBlacklister { address } => { - to_binary(&queries::query_is_blacklister(deps, address)?) - } - QueryMsg::BlacklisterAllowances { start_after, limit } => to_binary( - &queries::query_blacklister_allowances(deps, start_after, limit)?, - ), - QueryMsg::IsWhitelisted { address } => { - to_binary(&queries::query_is_whitelisted(deps, address)?) - } - QueryMsg::Whitelistees { start_after, limit } => { - to_binary(&queries::query_whitelistees(deps, start_after, limit)?) - } - QueryMsg::IsWhitelister { address } => { - to_binary(&queries::query_is_whitelister(deps, address)?) - } - QueryMsg::WhitelisterAllowances { start_after, limit } => to_binary( - &queries::query_whitelister_allowances(deps, start_after, limit)?, - ), - QueryMsg::IsFreezer { address } => to_binary(&queries::query_is_freezer(deps, address)?), - QueryMsg::FreezerAllowances { start_after, limit } => to_binary( - &queries::query_freezer_allowances(deps, start_after, limit)?, - ), } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate( - deps: DepsMut, - _env: Env, - _msg: MigrateMsg, -) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::new().add_attribute("action", "migrate")) +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version.as_str() < CONTRACT_VERSION { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn reply( - deps: DepsMut, - env: Env, + deps: DepsMut, + _env: Env, msg: Reply, ) -> Result, ContractError> { match msg.id { @@ -213,37 +185,8 @@ pub fn reply( let MsgCreateDenomResponse { new_token_denom } = msg.result.try_into()?; DENOM.save(deps.storage, &new_token_denom)?; - // SetBeforeSendHook to this contract - // this will trigger sudo endpoint before any bank send - // which makes blacklisting / freezing possible - let msg_set_beforesend_hook: CosmosMsg = MsgSetBeforeSendHook { - sender: env.contract.address.to_string(), - denom: new_token_denom.clone(), - cosmwasm_address: env.contract.address.to_string(), - } - .into(); - - Ok(Response::new() - .add_attribute("denom", new_token_denom) - .add_submessage(SubMsg::reply_always( - msg_set_beforesend_hook, - BEFORE_SEND_HOOK_REPLY_ID, - ))) - } - BEFORE_SEND_HOOK_REPLY_ID => match msg.result { - SubMsgResult::Ok(_) => { - // Enable features with BeforeSendHook requirement - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; - - Ok(Response::new().add_attribute("extra_features", "enabled")) - } - SubMsgResult::Err(_) => { - // MsgSetBeforeSendHook failed, disable extra features that require it - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &false)?; - - Ok(Response::new().add_attribute("extra_features", "disabled")) - } - }, + Ok(Response::new().add_attribute("denom", new_token_denom)) + } _ => Err(ContractError::UnknownReplyId { id: msg.id }), } } diff --git a/contracts/external/cw-tokenfactory-issuer/src/error.rs b/contracts/external/cw-tokenfactory-issuer/src/error.rs index 221b3fd0a..69dc28154 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/error.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/error.rs @@ -6,16 +6,19 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), - #[error("The chain you are using does not support MsgBeforeSendHook at this time. Features requiring it are disabled.")] + #[error(transparent)] + Ownership(#[from] cw_ownable::OwnershipError), + + #[error("BeforeSendHook not set. Features requiring it are disabled.")] BeforeSendHookFeaturesDisabled {}, - #[error("MsgBeforeSendHook is already configured. Features requiring it are already enabled.")] - BeforeSendHookAlreadyEnabled {}, + #[error("The address '{address}' is denied transfer abilities")] + Denied { address: String }, - #[error("The address '{address}' is blacklisted")] - Blacklisted { address: String }, + #[error("Cannot denylist the issuer contract itself")] + CannotDenylistSelf {}, - #[error("The contract is frozen for denom {denom:?}")] + #[error("The contract is frozen for denom {denom:?}. Addresses need to be added to the allowlist to enable transfers to or from an account.")] ContractFrozen { denom: String }, #[error("Invalid subdenom: {subdenom:?}")] diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 916a11d9b..c8ff45f4a 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -3,67 +3,66 @@ use osmosis_std::types::cosmos::bank::v1beta1::Metadata; use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ MsgBurn, MsgForceTransfer, MsgSetBeforeSendHook, MsgSetDenomMetadata, }; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; +use token_bindings::TokenFactoryMsg; use crate::error::ContractError; -use crate::helpers::{ - check_before_send_hook_features_enabled, check_bool_allowance, check_is_contract_owner, -}; +use crate::helpers::check_before_send_hook_features_enabled; use crate::state::{ - BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTER_ALLOWANCES, - BURNER_ALLOWANCES, DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, - WHITELISTED_ADDRESSES, WHITELISTER_ALLOWANCES, + BeforeSendHookInfo, ALLOWLIST, BEFORE_SEND_HOOK_INFO, BURNER_ALLOWANCES, DENOM, DENYLIST, + IS_FROZEN, MINTER_ALLOWANCES, }; +/// Mints new tokens. To mint new tokens, the address calling this method must +/// have an allowance of tokens to mint. This allowance is set by the contract through +/// the `ExecuteMsg::SetMinter { .. }` message. pub fn mint( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, to_address: String, amount: Uint128, ) -> Result, ContractError> { - // validate that to_address is a valid address + // Validate that to_address is a valid address deps.api.addr_validate(&to_address)?; - // don't allow minting of 0 coins + // Don't allow minting of 0 coins if amount.is_zero() { return Err(ContractError::ZeroAmount {}); } - // decrease minter allowance + // Decrease minter allowance let allowance = MINTER_ALLOWANCES .may_load(deps.storage, &info.sender)? .unwrap_or_else(Uint128::zero); - // if minter allowance goes negative, throw error + // If minter allowance goes negative, throw error let updated_allowance = allowance .checked_sub(amount) .map_err(|_| ContractError::not_enough_mint_allowance(amount, allowance))?; - // if minter allowance goes 0, remove from storage + // If minter allowance goes 0, remove from storage if updated_allowance.is_zero() { MINTER_ALLOWANCES.remove(deps.storage, &info.sender); } else { MINTER_ALLOWANCES.save(deps.storage, &info.sender, &updated_allowance)?; } - // get token denom from contract + // Get token denom from contract let denom = DENOM.load(deps.storage)?; - // create tokenfactory MsgMint which mints coins to the contract address + // Create tokenfactory MsgMint which mints coins to the contract address let mint_tokens_msg = TokenFactoryMsg::mint_contract_tokens( denom.clone(), amount, env.contract.address.into_string(), ); - // send newly minted coins from contract to designated recipient + // Send newly minted coins from contract to designated recipient let send_tokens_msg = BankMsg::Send { to_address: to_address.clone(), amount: coins(amount.u128(), denom), }; - // dispatch msgs Ok(Response::new() .add_message(mint_tokens_msg) .add_message(send_tokens_msg) @@ -72,39 +71,44 @@ pub fn mint( .add_attribute("amount", amount)) } +/// Burns tokens. To burn tokens, the address calling this method must +/// have an allowance of tokens to burn and the tokens to burn must belong +/// to the `cw_tokenfactory_issuer` contract itself. The allowance is set by +/// the contract through the `ExecuteMsg::SetBurner { .. }` message, and funds +/// to be burnt must be sent to this contract prior to burning. pub fn burn( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128, address: String, ) -> Result, ContractError> { - // don't allow burning of 0 coins + // Don't allow burning of 0 coins if amount.is_zero() { return Err(ContractError::ZeroAmount {}); } - // decrease burner allowance + // Decrease burner allowance let allowance = BURNER_ALLOWANCES .may_load(deps.storage, &info.sender)? .unwrap_or_else(Uint128::zero); - // if burner allowance goes negative, throw error + // If burner allowance goes negative, throw error let updated_allowance = allowance .checked_sub(amount) .map_err(|_| ContractError::not_enough_burn_allowance(amount, allowance))?; - // if burner allowance goes 0, remove from storage + // If burner allowance goes 0, remove from storage if updated_allowance.is_zero() { BURNER_ALLOWANCES.remove(deps.storage, &info.sender); } else { BURNER_ALLOWANCES.save(deps.storage, &info.sender, &updated_allowance)?; } - // get token denom from contract config + // Get token denom from contract config let denom = DENOM.load(deps.storage)?; - // create tokenfactory MsgBurn which burns coins from the contract address + // Create tokenfactory MsgBurn which burns coins from the contract address // NOTE: this requires the contract to own the tokens already let burn_from_address = deps.api.addr_validate(&address)?; let burn_tokens_msg: cosmwasm_std::CosmosMsg = MsgBurn { @@ -114,66 +118,66 @@ pub fn burn( } .into(); - // dispatch msg Ok(Response::new() .add_message(burn_tokens_msg) .add_attribute("action", "burn") - .add_attribute("from", info.sender) + .add_attribute("burner", info.sender) + .add_attribute("burn_from_address", burn_from_address.to_string()) .add_attribute("amount", amount)) } -pub fn change_contract_owner( - deps: DepsMut, +/// Updates the contract owner, must be the current contract owner to call +/// this method. +pub fn update_contract_owner( + deps: DepsMut, + env: Env, info: MessageInfo, - new_owner: String, + action: cw_ownable::Action, ) -> Result, ContractError> { - // Only allow current contract owner to change owner - check_is_contract_owner(deps.as_ref(), info.sender)?; - - // validate that new owner is a valid address - let new_owner_addr = deps.api.addr_validate(&new_owner)?; - - // update the contract owner in the contract config - OWNER.save(deps.storage, &new_owner_addr)?; - - // return OK - Ok(Response::new() - .add_attribute("action", "change_contract_owner") - .add_attribute("new_owner", new_owner)) + // cw-ownable performs all validation and ownership checks for us + let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; + Ok(Response::default().add_attributes(ownership.into_attributes())) } -pub fn change_tokenfactory_admin( - deps: DepsMut, +/// Updates the Token Factory token admin. To set no admin, specify the `new_admin` +/// argument to be either a null address or the address of the Cosmos SDK bank module +/// for the chain. +/// +/// Must be the contract owner to call this method. +pub fn update_tokenfactory_admin( + deps: DepsMut, info: MessageInfo, new_admin: String, ) -> Result, ContractError> { // Only allow current contract owner to change tokenfactory admin - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; - // validate that the new admin is a valid address + // Validate that the new admin is a valid address let new_admin_addr = deps.api.addr_validate(&new_admin)?; - // construct tokenfactory change admin msg - let change_admin_msg = TokenFactoryMsg::ChangeAdmin { + // Construct tokenfactory change admin msg + let update_admin_msg = TokenFactoryMsg::ChangeAdmin { denom: DENOM.load(deps.storage)?, new_admin_address: new_admin_addr.into(), }; - // dispatch change admin msg Ok(Response::new() - .add_message(change_admin_msg) - .add_attribute("action", "change_tokenfactory_admin") + .add_message(update_admin_msg) + .add_attribute("action", "update_tokenfactory_admin") .add_attribute("new_admin", new_admin)) } +/// Sets metadata related to the Token Factory denom. +/// +/// Must be the contract owner to call this method. pub fn set_denom_metadata( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, metadata: Metadata, ) -> Result, ContractError> { - // only allow current contract owner to set denom metadata - check_is_contract_owner(deps.as_ref(), info.sender)?; + // Only allow current contract owner to set denom metadata + cw_ownable::assert_owner(deps.storage, &info.sender)?; Ok(Response::new() .add_attribute("action", "set_denom_metadata") @@ -183,263 +187,245 @@ pub fn set_denom_metadata( })) } -pub fn set_blacklister( - deps: DepsMut, - info: MessageInfo, - address: String, - status: bool, -) -> Result, ContractError> { - check_before_send_hook_features_enabled(deps.as_ref())?; - - // Only allow current contract owner to set blacklister permission - check_is_contract_owner(deps.as_ref(), info.sender)?; - - let address = deps.api.addr_validate(&address)?; - - // set blacklister status - // NOTE: Does not check if new status is same as old status - // but if status is false, remove if exist to reduce space usage - if status { - BLACKLISTER_ALLOWANCES.save(deps.storage, &address, &status)?; - } else { - BLACKLISTER_ALLOWANCES.remove(deps.storage, &address); - } - - // Return OK - Ok(Response::new() - .add_attribute("action", "set_blacklister") - .add_attribute("blacklister", address) - .add_attribute("status", status.to_string())) -} - -pub fn set_whitelister( - deps: DepsMut, - info: MessageInfo, - address: String, - status: bool, -) -> Result, ContractError> { - check_before_send_hook_features_enabled(deps.as_ref())?; - - // Only allow current contract owner to set blacklister permission - check_is_contract_owner(deps.as_ref(), info.sender)?; - - let address = deps.api.addr_validate(&address)?; - - // set blacklister status - // NOTE: Does not check if new status is same as old status - // but if status is false, remove if exist to reduce space usage - if status { - WHITELISTER_ALLOWANCES.save(deps.storage, &address, &status)?; - } else { - WHITELISTER_ALLOWANCES.remove(deps.storage, &address); - } - - // Return OK - Ok(Response::new() - .add_attribute("action", "set_blacklister") - .add_attribute("blacklister", address) - .add_attribute("status", status.to_string())) -} - -pub fn set_freezer( - deps: DepsMut, - info: MessageInfo, - address: String, - status: bool, -) -> Result, ContractError> { - check_before_send_hook_features_enabled(deps.as_ref())?; - - // Only allow current contract owner to set freezer permission - check_is_contract_owner(deps.as_ref(), info.sender)?; - - let address = deps.api.addr_validate(&address)?; - - // set freezer status - // NOTE: Does not check if new status is same as old status - // but if status is false, remove if exist to reduce space usage - if status { - FREEZER_ALLOWANCES.save(deps.storage, &address, &status)?; - } else { - FREEZER_ALLOWANCES.remove(deps.storage, &address); - } - - // return OK - Ok(Response::new() - .add_attribute("action", "set_freezer") - .add_attribute("freezer", address) - .add_attribute("status", status.to_string())) -} - +/// Calls `MsgSetBeforeSendHook` and enables BeforeSendHook related features. +/// Takes a `cosmwasm_address` argument which is the address of the contract enforcing +/// the hook. Normally this will be the cw_tokenfactory_issuer contract address, but could +/// be a 3rd party address for more advanced use cases. +/// +/// As not all chains support the `BeforeSendHook` in the bank module, this +/// is intended to be called should chains add this feature at a later date. +/// +/// Must be the contract owner to call this method. pub fn set_before_send_hook( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, + cosmwasm_address: String, ) -> Result, ContractError> { // Only allow current contract owner - check_is_contract_owner(deps.as_ref(), info.sender)?; - - // Return error if BeforeSendHook already enabled - if BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage)? { - return Err(ContractError::BeforeSendHookAlreadyEnabled {}); + cw_ownable::assert_owner(deps.storage, &info.sender)?; + + // The `cosmwasm_address` can be an empty string if setting the value to nil to + // disable the hook. If an empty string, we disable before send hook features. + // Otherwise, we validate the `cosmwasm_address` enable before send hook features. + if cosmwasm_address.is_empty() { + // Disable BeforeSendHook features + BEFORE_SEND_HOOK_INFO.save( + deps.storage, + &BeforeSendHookInfo { + advanced_features_enabled: false, + hook_contract_address: None, + }, + )?; + } else { + // Validate that address is a valid address + deps.api.addr_validate(&cosmwasm_address)?; + + // If the `cosmwasm_address` is not the same as the cw_tokenfactory_issuer contract + // BeforeSendHook features are disabled. + let mut advanced_features_enabled = true; + if cosmwasm_address != env.contract.address { + advanced_features_enabled = false; + } + + // Save the BeforeSendHookInfo + BEFORE_SEND_HOOK_INFO.save( + deps.storage, + &BeforeSendHookInfo { + advanced_features_enabled, + hook_contract_address: Some(cosmwasm_address.clone()), + }, + )?; } // Load the Token Factory denom let denom = DENOM.load(deps.storage)?; - // SetBeforeSendHook to this contract - // this will trigger sudo endpoint before any bank send - // which makes blacklisting / freezing possible + // SetBeforeSendHook to this contract. + // This will trigger sudo endpoint before any bank send, + // which makes denylisting / freezing possible. let msg_set_beforesend_hook: CosmosMsg = MsgSetBeforeSendHook { sender: env.contract.address.to_string(), - denom: denom.clone(), - cosmwasm_address: env.contract.address.to_string(), + denom, + cosmwasm_address, } .into(); - // Enable BeforeSendHook features - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; - Ok(Response::new() .add_attribute("action", "set_before_send_hook") .add_message(msg_set_beforesend_hook)) } +/// Specifies and sets a burn allowance to allow for the burning of tokens. +/// To remove previously granted burn allowances, set this to zero. +/// +/// Must be the contract owner to call this method. pub fn set_burner( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, address: String, allowance: Uint128, ) -> Result, ContractError> { // Only allow current contract owner to set burner allowance - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; - // validate that burner is a valid address + // Validate that burner is a valid address let address = deps.api.addr_validate(&address)?; - // update allowance of burner - // remove key from state if set to 0 + // Update allowance of burner, remove key from state if set to 0 if allowance.is_zero() { BURNER_ALLOWANCES.remove(deps.storage, &address); } else { BURNER_ALLOWANCES.save(deps.storage, &address, &allowance)?; } - // return OK Ok(Response::new() .add_attribute("action", "set_burner") .add_attribute("burner", address) .add_attribute("allowance", allowance)) } +/// Specifies and sets a burn allowance to allow for the minting of tokens. +/// To remove previously granted mint allowances, set this to zero. +/// +/// Must be the contract owner to call this method. pub fn set_minter( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, address: String, allowance: Uint128, ) -> Result, ContractError> { // Only allow current contract owner to set minter allowance - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; - // validate that minter is a valid address + // Validate that minter is a valid address let address = deps.api.addr_validate(&address)?; - // update allowance of minter - // remove key from state if set to 0 + // Update allowance of minter, remove key from state if set to 0 if allowance.is_zero() { MINTER_ALLOWANCES.remove(deps.storage, &address); } else { MINTER_ALLOWANCES.save(deps.storage, &address, &allowance)?; } - // return OK Ok(Response::new() .add_attribute("action", "set_minter") .add_attribute("minter", address) - .add_attribute("amount", allowance)) + .add_attribute("allowance", allowance)) } +/// Freezes / unfreezes token transfers, meaning that address will not be +/// able to send tokens until the token is unfrozen. This feature is dependent +/// on the BeforeSendHook. +/// +/// This feature works in conjunction with this contract's allowlist. For example, +/// a DAO may wish to prevent its token from being liquid during its bootstrapping +/// phase. It may wish to add its staking contract to the allowlist to allow users +/// to stake their tokens (thus users would be able to transfer to the staking +/// contract), or add an airdrop contract to the allowlist so users can claim +/// their tokens (but not yet trade them). +/// +/// Must be the contract owner to call this method. pub fn freeze( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, status: bool, ) -> Result, ContractError> { check_before_send_hook_features_enabled(deps.as_ref())?; - // check to make sure that the sender has freezer permissions - check_bool_allowance(deps.as_ref(), info, FREEZER_ALLOWANCES)?; + // Only allow current contract owner to call this method + cw_ownable::assert_owner(deps.storage, &info.sender)?; // Update config frozen status // NOTE: Does not check if new status is same as old status IS_FROZEN.save(deps.storage, &status)?; - // return OK Ok(Response::new() .add_attribute("action", "freeze") .add_attribute("status", status.to_string())) } -pub fn blacklist( - deps: DepsMut, +/// Adds or removes an address from the denylist, meaning they will not +/// be able to transfer their tokens. This feature is dependent on +/// the BeforeSendHook. +/// +/// Must be the contract owner to call this method. +pub fn deny( + deps: DepsMut, + env: Env, info: MessageInfo, address: String, status: bool, ) -> Result, ContractError> { check_before_send_hook_features_enabled(deps.as_ref())?; - // check to make sure that the sender has blacklister permissions - check_bool_allowance(deps.as_ref(), info, BLACKLISTER_ALLOWANCES)?; + // Only allow current contract owner to call this method + cw_ownable::assert_owner(deps.storage, &info.sender)?; let address = deps.api.addr_validate(&address)?; - // update blacklisted status - // validate that blacklisteed is a valid address + // Check this issuer contract is not denylisting itself + if address == env.contract.address { + return Err(ContractError::CannotDenylistSelf {}); + } + + // Update denylist status and validate that denylistee is a valid address // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { - BLACKLISTED_ADDRESSES.save(deps.storage, &address, &status)?; + DENYLIST.save(deps.storage, &address, &status)?; } else { - BLACKLISTED_ADDRESSES.remove(deps.storage, &address); + DENYLIST.remove(deps.storage, &address); } - // return OK Ok(Response::new() - .add_attribute("action", "blacklist") + .add_attribute("action", "denylist") .add_attribute("address", address) .add_attribute("status", status.to_string())) } -pub fn whitelist( - deps: DepsMut, +/// Relevant only when the token is frozen. Addresses on the allowlist can +/// transfer tokens as well as have tokens sent to them. This feature is +/// dependent on the BeforeSendHook. +/// +/// See the `freeze` method for more information. +/// +/// Must be the contract owner to call this method. +pub fn allow( + deps: DepsMut, info: MessageInfo, address: String, status: bool, ) -> Result, ContractError> { check_before_send_hook_features_enabled(deps.as_ref())?; - // check to make sure that the sender has blacklister permissions - check_bool_allowance(deps.as_ref(), info, WHITELISTER_ALLOWANCES)?; + // Only allow current contract owner to call this method + cw_ownable::assert_owner(deps.storage, &info.sender)?; let address = deps.api.addr_validate(&address)?; - // update blacklisted status - // validate that blacklisteed is a valid address + // Update allowlist status and validate that allowlistee is a valid address // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { - WHITELISTED_ADDRESSES.save(deps.storage, &address, &status)?; + ALLOWLIST.save(deps.storage, &address, &status)?; } else { - WHITELISTED_ADDRESSES.remove(deps.storage, &address); + ALLOWLIST.remove(deps.storage, &address); } - // return OK Ok(Response::new() - .add_attribute("action", "blacklist") + .add_attribute("action", "allowlist") .add_attribute("address", address) .add_attribute("status", status.to_string())) } +/// Force transfers tokens from one account to another. To disable this, +/// DAOs will need to renounce Token Factory admin by setting the token +/// admin to be a null address or the address of the bank module. +/// +/// Must be the contract owner to call this method. pub fn force_transfer( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128, @@ -447,7 +433,7 @@ pub fn force_transfer( to_address: String, ) -> Result, ContractError> { // Only allow current contract owner to change owner - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; // Load TF denom for this contract let denom = DENOM.load(deps.storage)?; @@ -456,7 +442,7 @@ pub fn force_transfer( let force_transfer_msg: CosmosMsg = MsgForceTransfer { transfer_from_address: from_address.clone(), transfer_to_address: to_address.clone(), - amount: Some(Coin::new(amount.u128(), denom.clone()).into()), + amount: Some(Coin::new(amount.u128(), denom).into()), sender: env.contract.address.to_string(), } .into(); diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index 5262f9a79..2328035a4 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -1,119 +1,62 @@ -use crate::state::{ - BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, DENOM, IS_FROZEN, OWNER, - WHITELISTED_ADDRESSES, -}; +use crate::state::{ALLOWLIST, BEFORE_SEND_HOOK_INFO, DENOM, DENYLIST, IS_FROZEN}; use crate::ContractError; -use cosmwasm_std::{Addr, Coin, Deps, MessageInfo, Uint128}; -use cw_storage_plus::Map; -use token_bindings::TokenFactoryQuery; +use cosmwasm_std::Deps; -pub fn check_contract_has_funds( - denom: String, - funds: &[Coin], - amount: Uint128, -) -> Result<(), ContractError> { - if let Some(c) = funds.iter().find(|c| c.denom == denom) { - if c.amount < amount { - Err(ContractError::NotEnoughFunds { - denom, - funds: c.amount.u128(), - needed: amount.u128(), - }) - } else { - Ok(()) - } - } else { - Err(ContractError::NotEnoughFunds { - denom, - funds: 0u128, - needed: amount.u128(), - }) - } -} - -pub fn check_is_contract_owner( - deps: Deps, - sender: Addr, -) -> Result<(), ContractError> { - let owner = OWNER.load(deps.storage)?; - if owner != sender { - Err(ContractError::Unauthorized {}) - } else { - Ok(()) - } -} - -pub fn check_before_send_hook_features_enabled( - deps: Deps, -) -> Result<(), ContractError> { - let enabled = BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage)?; - if !enabled { +/// Checks wether the BeforeSendHookFeatures gated features are enabled +pub fn check_before_send_hook_features_enabled(deps: Deps) -> Result<(), ContractError> { + let info = BEFORE_SEND_HOOK_INFO.load(deps.storage)?; + if !info.advanced_features_enabled { Err(ContractError::BeforeSendHookFeaturesDisabled {}) } else { Ok(()) } } -pub fn check_bool_allowance( - deps: Deps, - info: MessageInfo, - allowances: Map<&Addr, bool>, -) -> Result<(), ContractError> { - let res = allowances.load(deps.storage, &info.sender); - match res { - Ok(authorized) => { - if !authorized { - return Err(ContractError::Unauthorized {}); - } - } - Err(error) => { - if let cosmwasm_std::StdError::NotFound { .. } = error { - return Err(ContractError::Unauthorized {}); - } else { - return Err(ContractError::Std(error)); - } - } - } - Ok(()) -} - -pub fn check_is_not_blacklisted( - deps: Deps, - address: String, -) -> Result<(), ContractError> { +/// Checks wether the given address is on the denylist +pub fn check_is_not_denied(deps: Deps, address: String) -> Result<(), ContractError> { let addr = deps.api.addr_validate(&address)?; - if let Some(is_blacklisted) = BLACKLISTED_ADDRESSES.may_load(deps.storage, &addr)? { - if is_blacklisted { - return Err(ContractError::Blacklisted { address }); + if let Some(is_denied) = DENYLIST.may_load(deps.storage, &addr)? { + if is_denied { + return Err(ContractError::Denied { address }); } }; Ok(()) } +/// Checks wether the contract is frozen for the given denom, in which case +/// token transfers will not be allowed unless the to or from address is on +/// the allowlist pub fn check_is_not_frozen( - deps: Deps, - address: &str, + deps: Deps, + from_address: &str, + to_address: &str, denom: &str, ) -> Result<(), ContractError> { let is_frozen = IS_FROZEN.load(deps.storage)?; let contract_denom = DENOM.load(deps.storage)?; - // check if issuer is configured to be frozen and the arriving denom is the same + // Check if issuer is configured to be frozen and the arriving denom is the same // as this contract denom. + // // Denom can be different since setting beforesend listener doesn't check // contract's denom. let is_denom_frozen = is_frozen && denom == contract_denom; if is_denom_frozen { - let addr = deps.api.addr_validate(address)?; - if let Some(is_whitelisted) = WHITELISTED_ADDRESSES.may_load(deps.storage, &addr)? { - if is_whitelisted { - return Ok(()); + let from = deps.api.addr_validate(from_address)?; + let to = deps.api.addr_validate(to_address)?; + + // If either the from address or the to_address is allowed, then transaction proceeds + let is_from_allowed = ALLOWLIST.may_load(deps.storage, &from)?; + let is_to_allowed = ALLOWLIST.may_load(deps.storage, &to)?; + match (is_from_allowed, is_to_allowed) { + (Some(true), _) => return Ok(()), + (_, Some(true)) => return Ok(()), + _ => { + return Err(ContractError::ContractFrozen { + denom: contract_denom, + }) } - }; - - return Err(ContractError::ContractFrozen { - denom: contract_denom, - }); + } } Ok(()) diff --git a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs index af4166d35..2b344f3e0 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs @@ -1,21 +1,25 @@ use cosmwasm_std::{Coin, DepsMut, Response}; -use token_bindings::TokenFactoryQuery; use crate::error::ContractError; -use crate::helpers::{check_is_not_blacklisted, check_is_not_frozen}; +use crate::helpers::{check_is_not_denied, check_is_not_frozen}; +/// The before send hook is called before every token transfer on chains that +/// support MsgSetBeforeSendHook. +/// +/// It is called by the bank module. pub fn beforesend_hook( - deps: DepsMut, + deps: DepsMut, from: String, to: String, coin: Coin, ) -> Result { - // assert that denom of this contract is not frozen - check_is_not_frozen(deps.as_ref(), &from, &coin.denom)?; + // Assert that denom of this contract is not frozen + // If it is frozen, check whether either 'from' or 'to' address is allowed + check_is_not_frozen(deps.as_ref(), &from, &to, &coin.denom)?; - // assert that neither 'from' or 'to' address is blacklisted - check_is_not_blacklisted(deps.as_ref(), from)?; - check_is_not_blacklisted(deps.as_ref(), to)?; + // Assert that neither 'from' or 'to' address is denylist + check_is_not_denied(deps.as_ref(), from)?; + check_is_not_denied(deps.as_ref(), to)?; Ok(Response::new().add_attribute("action", "before_send")) } diff --git a/contracts/external/cw-tokenfactory-issuer/src/lib.rs b/contracts/external/cw-tokenfactory-issuer/src/lib.rs index f10d4f9af..73e94be76 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/lib.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/lib.rs @@ -1,10 +1,23 @@ +#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] + +/// The smart contract itself, including the execute, instantiate, query, migrate +/// and reply entry points pub mod contract; +/// Private error module, ContractError is re-exported in the public interface mod error; +/// Contract methods that can be executed and alter state pub mod execute; +/// Helper functions used for validation and checks pub mod helpers; +/// Contract hooks pub mod hooks; +/// Contract messages describing the API of the contract as well as responses +/// from contract queries pub mod msg; +/// Contract queries pub mod queries; +/// The contract state pub mod state; +/// Error messages used in this contract pub use crate::error::ContractError; diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 6d0bc0eea..3c874e584 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -2,6 +2,9 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Uint128}; pub use osmosis_std::types::cosmos::bank::v1beta1::{DenomUnit, Metadata}; +use crate::state::BeforeSendHookInfo; + +/// The message used to create a new instance of this smart contract. #[cw_serde] pub enum InstantiateMsg { /// `NewToken` will create a new token when instantiate the contract. @@ -17,42 +20,17 @@ pub enum InstantiateMsg { ExistingToken { denom: String }, } -#[cw_serde] -pub struct MigrateMsg {} - +/// State changing methods available to this smart contract. #[cw_serde] pub enum ExecuteMsg { - /// Change the admin of the Token Factory denom itself. - ChangeTokenFactoryAdmin { new_admin: String }, - - /// Change the owner of this contract who is allowed to call privileged methods. - ChangeContractOwner { new_owner: String }, - - /// Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata. - SetDenomMetadata { metadata: Metadata }, - - /// Grant/revoke mint allowance. - SetMinterAllowance { address: String, allowance: Uint128 }, - - /// Grant/revoke burn allowance. - SetBurnerAllowance { address: String, allowance: Uint128 }, - - /// Grant/revoke permission to blacklist addresses - SetBlacklister { address: String, status: bool }, - - /// Grant/revoke permission to blacklist addresses - SetWhitelister { address: String, status: bool }, - - /// Attempt to SetBeforeSendHook on the token attached to this contract. - /// This will fail if the token already has a SetBeforeSendHook or the chain - /// still does not support it. - SetBeforeSendHook {}, - - /// Grant/revoke permission to freeze the token - SetFreezer { address: String, status: bool }, - - /// Mint token to address. Mint allowance is required and wiil be deducted after successful mint. - Mint { to_address: String, amount: Uint128 }, + /// Allow adds the target address to the allowlist to be able to send or recieve tokens even if the token + /// is frozen. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature + /// to work. + /// + /// This functionality is intedended for DAOs who do not wish to have a their tokens liquid while bootstrapping + /// their DAO. For example, a DAO may wish to white list a Token Staking contract (to allow users to stake their + /// tokens in the DAO) or a Merkle Drop contract (to allow users to claim their tokens). + Allow { address: String, status: bool }, /// Burn token to address. Burn allowance is required and wiil be deducted after successful burn. Burn { @@ -60,15 +38,17 @@ pub enum ExecuteMsg { amount: Uint128, }, - /// Block target address from sending/receiving token attached to this contract - /// tokenfactory's beforesend listener must be set to this contract in order for it to work as intended. - Blacklist { address: String, status: bool }, + /// Mint token to address. Mint allowance is required and wiil be deducted after successful mint. + Mint { to_address: String, amount: Uint128 }, - /// Whitelist target address to be able to send tokens even if the token is frozen. - Whitelist { address: String, status: bool }, + /// Deny adds the target address to the denylist, whis prevents them from sending/receiving the token attached + /// to this contract tokenfactory's BeforeSendHook listener must be set to this contract in order for this + /// feature to work as intended. + Deny { address: String, status: bool }, - /// Block every token transfers of the token attached to this contract - /// tokenfactory's beforesend listener must be set to this contract in order for it to work as intended. + /// Block every token transfers of the token attached to this contract. + /// Token Factory's BeforeSendHook listener must be set to this contract in order for this + /// feature to work as intended. Freeze { status: bool }, /// Force transfer token from one address to another. @@ -77,162 +57,197 @@ pub enum ExecuteMsg { from_address: String, to_address: String, }, + + /// Attempt to SetBeforeSendHook on the token attached to this contract. + /// This will fail if the chain does not support bank module hooks (many Token + /// Factory implementations do not yet support). + /// + /// This takes a cosmwasm_address as an argument, which is the address of the + /// contract that will be called before every token transfer. Normally, this + /// will be the issuer contract itself, though it can be a custom contract for + /// greater flexibility. + /// + /// Setting the address to an empty string will remove the SetBeforeSendHook. + /// + /// This method can only be called by the contract owner. + SetBeforeSendHook { cosmwasm_address: String }, + + /// Grant/revoke burn allowance. + SetBurnerAllowance { address: String, allowance: Uint128 }, + + /// Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata. + SetDenomMetadata { metadata: Metadata }, + + /// Grant/revoke mint allowance. + SetMinterAllowance { address: String, allowance: Uint128 }, + + /// Updates the admin of the Token Factory token. + /// Normally this is the cw-tokenfactory-issuer contract itself. + /// This is intended to be used only if you seek to transfer ownership + /// of the Token somewhere else (i.e. to another management contract). + UpdateTokenFactoryAdmin { new_admin: String }, + + /// Updates the owner of this contract who is allowed to call privileged methods. + /// NOTE: this is separate from the Token Factory token admin, for this contract to work + /// at all, it needs to the be the Token Factory token admin. + /// + /// Normally, the contract owner will be a DAO. + /// + /// The `action` to be provided can be either to propose transferring ownership to an + /// account, accept a pending ownership transfer, or renounce the ownership permanently. + UpdateOwnership(cw_ownable::Action), } -/// SudoMsg is only exposed for internal Cosmos SDK modules to call. -/// This is showing how we can expose "admin" functionality than can not be called by -/// external users or contracts, but only trusted (native/Go) code in the blockchain +/// Used for smart contract migration. #[cw_serde] -pub enum SudoMsg { - BlockBeforeSend { - from: String, - to: String, - amount: Coin, - }, -} +pub struct MigrateMsg {} +/// Queries supported by this smart contract. #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse + /// Returns if token transfer is disabled. Response: IsFrozenResponse #[returns(IsFrozenResponse)] IsFrozen {}, - /// Denom returns the token denom that this contract is the admin for. Response: DenomResponse + + /// Returns the token denom that this contract is the admin for. Response: DenomResponse #[returns(DenomResponse)] Denom {}, - /// Owner returns the owner of the contract. Response: OwnerResponse - #[returns(OwnerResponse)] - Owner {}, - /// Allowance returns the allowance of the specified address. Response: AllowanceResponse + + #[returns(::cw_ownable::Ownership<::cosmwasm_std::Addr>)] + Ownership {}, + + /// Returns the burn allowance of the specified address. Response: AllowanceResponse #[returns(AllowanceResponse)] BurnAllowance { address: String }, - /// Allowances Enumerates over all allownances. Response: Vec + + /// Enumerates over all burn allownances. Response: AllowancesResponse #[returns(AllowancesResponse)] BurnAllowances { start_after: Option, limit: Option, }, - /// Allowance returns the allowance of the specified user. Response: AllowanceResponse + + /// Returns the mint allowance of the specified user. Response: AllowanceResponse #[returns(AllowanceResponse)] MintAllowance { address: String }, - /// Allowances Enumerates over all allownances. Response: AllowancesResponse + + /// Enumerates over all mint allownances. Response: AllowancesResponse #[returns(AllowancesResponse)] MintAllowances { start_after: Option, limit: Option, }, - /// IsBlacklisted returns wether the user is blacklisted or not. Response: StatusResponse - #[returns(StatusResponse)] - IsBlacklisted { address: String }, - /// Blacklistees enumerates over all addresses on the blacklist. Response: BlacklisteesResponse - #[returns(BlacklisteesResponse)] - Blacklistees { - start_after: Option, - limit: Option, - }, - /// IsBlacklister returns if the addres has blacklister privileges. Response: StatusResponse - #[returns(StatusResponse)] - IsBlacklister { address: String }, - /// Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklisterAllowancesResponse - #[returns(BlacklisterAllowancesResponse)] - BlacklisterAllowances { - start_after: Option, - limit: Option, - }, - /// IsWhitelisted returns wether the user is whitelisted or not. Response: StatusResponse + + /// Returns wether the user is on denylist or not. Response: StatusResponse #[returns(StatusResponse)] - IsWhitelisted { address: String }, - /// Whitelistees enumerates over all addresses on the whitelist. Response: WhitelisteesResponse - #[returns(WhitelisteesResponse)] - Whitelistees { + IsDenied { address: String }, + + /// Enumerates over all addresses on the denylist. Response: DenylistResponse + #[returns(DenylistResponse)] + Denylist { start_after: Option, limit: Option, }, - /// IsWhitelister returns if the addres has whitelister privileges. Response: StatusResponse + + /// Returns wether the user is on the allowlist or not. Response: StatusResponse #[returns(StatusResponse)] - IsWhitelister { address: String }, - /// Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelisterAllowancesResponse - #[returns(WhitelisterAllowancesResponse)] - WhitelisterAllowances { + IsAllowed { address: String }, + + /// Enumerates over all addresses on the allowlist. Response: AllowlistResponse + #[returns(AllowlistResponse)] + Allowlist { start_after: Option, limit: Option, }, - /// IsFreezer returns whether the address has freezer status. Response: StatusResponse - #[returns(StatusResponse)] - IsFreezer { address: String }, - /// FreezerAllowances enumerates over all freezer addresses. Response: FreezerAllowancesResponse - #[returns(FreezerAllowancesResponse)] - FreezerAllowances { - start_after: Option, - limit: Option, + + /// Returns information about the BeforeSendHook for the token. Note: many Token + /// Factory chains do not yet support this feature. + /// + /// The information returned is: + /// - Whether features in this contract that require MsgBeforeSendHook are enabled. + /// - The address of the BeforeSendHook contract if configured. + /// + /// Response: BeforeSendHookInfo + #[returns(BeforeSendHookInfo)] + BeforeSendHookInfo {}, +} + +/// SudoMsg is only exposed for internal Cosmos SDK modules to call. +/// This is showing how we can expose "admin" functionality than can not be called by +/// external users or contracts, but only trusted (native/Go) code in the blockchain +#[cw_serde] +pub enum SudoMsg { + BlockBeforeSend { + from: String, + to: String, + amount: Coin, }, } -// We define a custom struct for each query response +/// Returns whether or not the Token Factory token is frozen and transfers +/// are disabled. #[cw_serde] pub struct IsFrozenResponse { pub is_frozen: bool, } -// We define a custom struct for each query response +/// Returns the full denomination for the Token Factory token. For example: +/// `factory/{contract address}/{subdenom}` #[cw_serde] pub struct DenomResponse { pub denom: String, } +/// Returns the current owner of this issuer contract who is allowed to +/// call priviledged methods. #[cw_serde] pub struct OwnerResponse { pub address: String, } +/// Returns a mint or burn allowance for a particular address, representing +/// the amount of tokens the account is allowed to mint or burn #[cw_serde] pub struct AllowanceResponse { pub allowance: Uint128, } +/// Information about a particular account and its mint / burn allowances. +/// Used in list queries. #[cw_serde] pub struct AllowanceInfo { pub address: String, pub allowance: Uint128, } +/// Returns a list of all mint or burn allowances #[cw_serde] pub struct AllowancesResponse { pub allowances: Vec, } +/// Whether a particular account is allowed or denied #[cw_serde] pub struct StatusResponse { pub status: bool, } +/// Account info for list queries related to allowlist and denylist #[cw_serde] pub struct StatusInfo { pub address: String, pub status: bool, } +/// Returns a list of addresses currently on the denylist. #[cw_serde] -pub struct BlacklisteesResponse { - pub blacklistees: Vec, -} - -#[cw_serde] -pub struct BlacklisterAllowancesResponse { - pub blacklisters: Vec, -} - -#[cw_serde] -pub struct WhitelisteesResponse { - pub whitelistees: Vec, -} - -#[cw_serde] -pub struct WhitelisterAllowancesResponse { - pub whitelisters: Vec, +pub struct DenylistResponse { + pub denylist: Vec, } +/// Returns a list of addresses currently on the allowlist #[cw_serde] -pub struct FreezerAllowancesResponse { - pub freezers: Vec, +pub struct AllowlistResponse { + pub allowlist: Vec, } diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index be3d4f14f..a6b7f3b5d 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -1,60 +1,55 @@ use cosmwasm_std::{Addr, Deps, Order, StdResult, Uint128}; use cw_storage_plus::{Bound, Map}; -use token_bindings::TokenFactoryQuery; use crate::msg::{ - AllowanceInfo, AllowanceResponse, AllowancesResponse, BlacklisteesResponse, - BlacklisterAllowancesResponse, DenomResponse, FreezerAllowancesResponse, IsFrozenResponse, - OwnerResponse, StatusInfo, StatusResponse, WhitelisteesResponse, WhitelisterAllowancesResponse, + AllowanceInfo, AllowanceResponse, AllowancesResponse, AllowlistResponse, DenomResponse, + DenylistResponse, IsFrozenResponse, StatusInfo, StatusResponse, }; use crate::state::{ - BLACKLISTED_ADDRESSES, BLACKLISTER_ALLOWANCES, BURNER_ALLOWANCES, DENOM, FREEZER_ALLOWANCES, - IS_FROZEN, MINTER_ALLOWANCES, OWNER, WHITELISTED_ADDRESSES, WHITELISTER_ALLOWANCES, + BeforeSendHookInfo, ALLOWLIST, BEFORE_SEND_HOOK_INFO, BURNER_ALLOWANCES, DENOM, DENYLIST, + IS_FROZEN, MINTER_ALLOWANCES, }; // Default settings for pagination const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; -pub fn query_denom(deps: Deps) -> StdResult { +/// Returns the token denom that this contract is the admin for. Response: DenomResponse +pub fn query_denom(deps: Deps) -> StdResult { let denom = DENOM.load(deps.storage)?; Ok(DenomResponse { denom }) } -pub fn query_is_frozen(deps: Deps) -> StdResult { +/// Returns if token transfer is disabled. Response: IsFrozenResponse +pub fn query_is_frozen(deps: Deps) -> StdResult { let is_frozen = IS_FROZEN.load(deps.storage)?; Ok(IsFrozenResponse { is_frozen }) } -pub fn query_owner(deps: Deps) -> StdResult { - let owner = OWNER.load(deps.storage)?; - Ok(OwnerResponse { - address: owner.into_string(), - }) +/// Returns the owner of the contract. Response: Ownership +pub fn query_owner(deps: Deps) -> StdResult> { + cw_ownable::get_ownership(deps.storage) } -pub fn query_mint_allowance( - deps: Deps, - address: String, -) -> StdResult { +/// Returns the mint allowance of the specified user. Response: AllowanceResponse +pub fn query_mint_allowance(deps: Deps, address: String) -> StdResult { let allowance = MINTER_ALLOWANCES .may_load(deps.storage, &deps.api.addr_validate(&address)?)? .unwrap_or_else(Uint128::zero); Ok(AllowanceResponse { allowance }) } -pub fn query_burn_allowance( - deps: Deps, - address: String, -) -> StdResult { +/// Returns the allowance of the specified address. Response: AllowanceResponse +pub fn query_burn_allowance(deps: Deps, address: String) -> StdResult { let allowance = BURNER_ALLOWANCES .may_load(deps.storage, &deps.api.addr_validate(&address)?)? .unwrap_or_else(Uint128::zero); Ok(AllowanceResponse { allowance }) } +/// Helper function used in allowance list queries. pub fn query_allowances( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, allowances: Map<&Addr, Uint128>, @@ -84,8 +79,9 @@ pub fn query_allowances( .collect() } +/// Enumerates over all allownances. Response: AllowancesResponse pub fn query_mint_allowances( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { @@ -94,8 +90,9 @@ pub fn query_mint_allowances( }) } +/// Enumerates over all burn allownances. Response: AllowancesResponse pub fn query_burn_allowances( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { @@ -104,28 +101,31 @@ pub fn query_burn_allowances( }) } -pub fn query_is_blacklisted( - deps: Deps, - address: String, -) -> StdResult { - let status = BLACKLISTED_ADDRESSES +/// Returns wether the user is on denylist or not. Response: StatusResponse +pub fn query_is_denied(deps: Deps, address: String) -> StdResult { + let status = DENYLIST .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } -pub fn query_is_whitelisted( - deps: Deps, - address: String, -) -> StdResult { - let status = WHITELISTED_ADDRESSES +/// Returns wether the user is on the allowlist or not. Response: StatusResponse +pub fn query_is_allowed(deps: Deps, address: String) -> StdResult { + let status = ALLOWLIST .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } +/// Returns whether features that require MsgBeforeSendHook are enabled. +/// Most Cosmos chains do not support this feature yet. +pub fn query_before_send_hook_features(deps: Deps) -> StdResult { + BEFORE_SEND_HOOK_INFO.load(deps.storage) +} + +/// A helper function used in list queries pub fn query_status_map( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, map: Map<&Addr, bool>, @@ -152,84 +152,24 @@ pub fn query_status_map( .collect() } -pub fn query_blacklistees( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - Ok(BlacklisteesResponse { - blacklistees: query_status_map(deps, start_after, limit, BLACKLISTED_ADDRESSES)?, - }) -} - -pub fn query_is_blacklister( - deps: Deps, - address: String, -) -> StdResult { - let status = BLACKLISTER_ALLOWANCES - .load(deps.storage, &deps.api.addr_validate(&address)?) - .unwrap_or(false); - Ok(StatusResponse { status }) -} - -pub fn query_blacklister_allowances( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - Ok(BlacklisterAllowancesResponse { - blacklisters: query_status_map(deps, start_after, limit, BLACKLISTER_ALLOWANCES)?, - }) -} - -pub fn query_whitelistees( - deps: Deps, +/// Enumerates over all addresses on the allowlist. Response: AllowlistResponse +pub fn query_allowlist( + deps: Deps, start_after: Option, limit: Option, -) -> StdResult { - Ok(WhitelisteesResponse { - whitelistees: query_status_map(deps, start_after, limit, WHITELISTED_ADDRESSES)?, +) -> StdResult { + Ok(AllowlistResponse { + allowlist: query_status_map(deps, start_after, limit, ALLOWLIST)?, }) } -pub fn query_is_whitelister( - deps: Deps, - address: String, -) -> StdResult { - let status = WHITELISTER_ALLOWANCES - .load(deps.storage, &deps.api.addr_validate(&address)?) - .unwrap_or(false); - Ok(StatusResponse { status }) -} - -pub fn query_whitelister_allowances( - deps: Deps, +/// Enumerates over all addresses on the denylist. Response: DenylistResponse +pub fn query_denylist( + deps: Deps, start_after: Option, limit: Option, -) -> StdResult { - Ok(WhitelisterAllowancesResponse { - whitelisters: query_status_map(deps, start_after, limit, WHITELISTER_ALLOWANCES)?, +) -> StdResult { + Ok(DenylistResponse { + denylist: query_status_map(deps, start_after, limit, DENYLIST)?, }) } - -pub fn query_freezer_allowances( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - Ok(FreezerAllowancesResponse { - freezers: query_status_map(deps, start_after, limit, FREEZER_ALLOWANCES)?, - }) -} - -pub fn query_is_freezer( - deps: Deps, - address: String, -) -> StdResult { - let status = FREEZER_ALLOWANCES - .load(deps.storage, &deps.api.addr_validate(&address)?) - .unwrap_or(false); - Ok(StatusResponse { status }) -} - -// query inspiration see https://github.com/mars-protocol/fields-of-mars/blob/v1.0.0/packages/fields-of-mars/src/martian_field.rs#L465-L473 diff --git a/contracts/external/cw-tokenfactory-issuer/src/state.rs b/contracts/external/cw-tokenfactory-issuer/src/state.rs index 1f71f4437..3c694c8c4 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/state.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/state.rs @@ -1,25 +1,33 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; -pub const OWNER: Item = Item::new("owner"); +/// Holds the Token Factory denom managed by this contract pub const DENOM: Item = Item::new("denom"); -/// Blacklisted addresses prevented from transferring tokens -pub const BLACKLISTED_ADDRESSES: Map<&Addr, bool> = Map::new("blacklisted_addresses"); +/// Denylist addresses prevented from transferring tokens +pub const DENYLIST: Map<&Addr, bool> = Map::new("denylist"); /// Addresses allowed to transfer tokens even if the token is frozen -pub const WHITELISTED_ADDRESSES: Map<&Addr, bool> = Map::new("whitelisted_addresses"); +pub const ALLOWLIST: Map<&Addr, bool> = Map::new("allowlist"); /// Whether or not features that require MsgBeforeSendHook are enabled /// Many Token Factory chains do not yet support MsgBeforeSendHook -pub const BEFORE_SEND_HOOK_FEATURES_ENABLED: Item = Item::new("hook_features_enabled"); +#[cw_serde] +pub struct BeforeSendHookInfo { + /// Whether or not features in this contract that require MsgBeforeSendHook are enabled. + pub advanced_features_enabled: bool, + /// The address of the contract that implements the BeforeSendHook interface. + /// Most often this will be the cw_tokenfactory_issuer contract itself. + pub hook_contract_address: Option, +} +pub const BEFORE_SEND_HOOK_INFO: Item = Item::new("hook_features_enabled"); /// Whether or not token transfers are frozen pub const IS_FROZEN: Item = Item::new("is_frozen"); -/// Allowances -pub const BLACKLISTER_ALLOWANCES: Map<&Addr, bool> = Map::new("blacklister_allowances"); -pub const WHITELISTER_ALLOWANCES: Map<&Addr, bool> = Map::new("whitelister_allowances"); +/// Allowances for burning pub const BURNER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("burner_allowances"); -pub const FREEZER_ALLOWANCES: Map<&Addr, bool> = Map::new("freezer_allowances"); + +/// Allowances for minting pub const MINTER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("minter_allowances"); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs new file mode 100644 index 000000000..9d1da91d8 --- /dev/null +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs @@ -0,0 +1,134 @@ +use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; +use osmosis_test_tube::Account; + +use crate::test_env::{ + test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, +}; + +#[test] +fn allowlist_by_owner_should_pass() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let allowlistee = &env.test_accs[2]; + + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + env.cw_tokenfactory_issuer + .allow(&allowlistee.address(), true, owner) + .unwrap(); + + // Should be allowlist after set true + assert!( + env.cw_tokenfactory_issuer + .query_is_allowed(&allowlistee.address()) + .unwrap() + .status + ); + + env.cw_tokenfactory_issuer + .allow(&allowlistee.address(), false, owner) + .unwrap(); + + // Should be unallowlist after set false + assert!( + !env.cw_tokenfactory_issuer + .query_is_allowed(&allowlistee.address()) + .unwrap() + .status + ); +} + +#[test] +fn allowlist_by_non_owern_should_fail() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; + let allowlistee = &env.test_accs[2]; + + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Non-owner cannot add address to allowlist + let err = env + .cw_tokenfactory_issuer + .allow(&allowlistee.address(), true, non_owner) + .unwrap_err(); + + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) + ); +} + +#[test] +fn query_allowlist_within_default_limit() { + test_query_within_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Allowlist the address + env.cw_tokenfactory_issuer + .allow(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_allowlist(start_after, limit) + .unwrap() + .allowlist + } + }, + ); +} + +#[test] +fn query_allowlist_over_default_limit() { + test_query_over_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Allowlist the address + env.cw_tokenfactory_issuer + .allow(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_allowlist(start_after, limit) + .unwrap() + .allowlist + } + }, + ); +} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs index 9464d8035..49320bed8 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs @@ -9,7 +9,7 @@ fn before_send_should_not_block_anything_by_default() { let owner = &env.test_accs[0]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // mint to self + // Mint to self env.cw_tokenfactory_issuer .set_minter(&owner.address(), 10000, owner) .unwrap(); @@ -17,7 +17,7 @@ fn before_send_should_not_block_anything_by_default() { .mint(&owner.address(), 10000, owner) .unwrap(); - // bank send should pass + // Bank send should pass env.send_tokens(env.test_accs[1].address(), coins(10000, denom), owner) .unwrap(); } @@ -28,14 +28,15 @@ fn before_send_should_block_on_frozen() { let owner = &env.test_accs[0]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // freeze + // Owner sets before send hook to enable advanced features env.cw_tokenfactory_issuer - .set_freezer(&owner.address(), true, owner) + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) .unwrap(); + // Freeze env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - // bank send should fail + // Bank send should fail let err = env .send_tokens( env.test_accs[1].address(), @@ -44,91 +45,141 @@ fn before_send_should_block_on_frozen() { ) .unwrap_err(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\". Addresses need to be added to the allowlist to enable transfers to or from an account.: execute wasm contract failed") }); } #[test] -fn white_listed_addresses_can_transfer_when_token_frozen() { +fn allowlisted_addresses_can_transfer_when_token_frozen() { let env = TestEnv::default(); let owner = &env.test_accs[0]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - let whitelistee = &env.test_accs[1]; + let allowlistee = &env.test_accs[1]; let other = &env.test_accs[2]; - // freeze + // Owner sets before send hook to enable advanced features env.cw_tokenfactory_issuer - .set_freezer(&owner.address(), true, owner) + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) .unwrap(); - env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - - // bank send should fail - let err = env - .send_tokens(whitelistee.address(), coins(10000, denom.clone()), owner) - .unwrap_err(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); - // Whitelist address + // Mint to owner and allowlistee + env.cw_tokenfactory_issuer + .set_minter(&owner.address(), 100000, owner) + .unwrap(); env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) + .mint(&owner.address(), 10000, owner) .unwrap(); env.cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), true, owner) + .mint(&allowlistee.address(), 10000, owner) .unwrap(); - // bank send should pass - env.send_tokens(other.address(), coins(10000, denom.clone()), whitelistee) - .unwrap_err(); - // Non whitelisted address can't transfer, bank send should fail + // Freeze + env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); + + // Allowlist address + env.cw_tokenfactory_issuer + .allow(&allowlistee.address(), true, owner) + .unwrap(); + + // Bank send should pass + env.send_tokens(other.address(), coins(10000, denom.clone()), allowlistee) + .unwrap(); + + // Non allowlist address can't transfer, bank send should fail let err = env .send_tokens(other.address(), coins(10000, denom.clone()), owner) .unwrap_err(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\". Addresses need to be added to the allowlist to enable transfers to or from an account.: execute wasm contract failed") }); + + // Other assets are not affected + env.send_tokens(other.address(), coins(10000, "uosmo"), owner) + .unwrap(); } #[test] -fn before_send_should_block_sending_from_blacklisted_address() { +fn non_allowlisted_accounts_can_transfer_to_allowlisted_address_frozen() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let blacklistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + let allowlistee = &env.test_accs[1]; + let other = &env.test_accs[2]; - // mint to blacklistee + // Owner sets before send hook to enable advanced features env.cw_tokenfactory_issuer - .set_minter(&owner.address(), 20000, owner) + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Mint to other + env.cw_tokenfactory_issuer + .set_minter(&owner.address(), 100000, owner) .unwrap(); env.cw_tokenfactory_issuer - .mint(&blacklistee.address(), 20000, owner) + .mint(&other.address(), 10000, owner) .unwrap(); - // blacklist + // Freeze + env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); + + // Allowlist address env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) + .allow(&allowlistee.address(), true, owner) .unwrap(); + + // Bank send to allow listed address should pass + env.send_tokens(allowlistee.address(), coins(10000, denom.clone()), other) + .unwrap(); +} + +#[test] +fn before_send_should_block_sending_from_denylist_address() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let denylistee = &env.test_accs[1]; + let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + + // Owner sets before send hook to enable advanced features env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, owner) + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) .unwrap(); - // bank send should fail + // Mint to denylistee + env.cw_tokenfactory_issuer + .set_minter(&owner.address(), 20000, owner) + .unwrap(); + env.cw_tokenfactory_issuer + .mint(&denylistee.address(), 20000, owner) + .unwrap(); + + // Denylist + env.cw_tokenfactory_issuer + .deny(&denylistee.address(), true, owner) + .unwrap(); + + // Bank send should fail let err = env .send_tokens( env.test_accs[2].address(), coins(10000, denom.clone()), - blacklistee, + denylistee, ) .unwrap_err(); - let blacklistee_addr = blacklistee.address(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{blacklistee_addr}' is blacklisted: execute wasm contract failed") }); + let denylistee_addr = denylistee.address(); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denied transfer abilities: execute wasm contract failed") }); } #[test] -fn before_send_should_block_sending_to_blacklisted_address() { +fn before_send_should_block_sending_to_denylist_address() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let blacklistee = &env.test_accs[1]; + let denylistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // mint to self + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Mint to self env.cw_tokenfactory_issuer .set_minter(&owner.address(), 10000, owner) .unwrap(); @@ -136,19 +187,16 @@ fn before_send_should_block_sending_to_blacklisted_address() { .mint(&owner.address(), 10000, owner) .unwrap(); - // blacklist - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); + // Denylist env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, owner) + .deny(&denylistee.address(), true, owner) .unwrap(); - // bank send should fail + // Bank send should fail let err = env - .send_tokens(blacklistee.address(), coins(10000, denom.clone()), owner) + .send_tokens(denylistee.address(), coins(10000, denom.clone()), owner) .unwrap_err(); - let blacklistee_addr = blacklistee.address(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{blacklistee_addr}' is blacklisted: execute wasm contract failed") }); + let denylistee_addr = denylistee.address(); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denied transfer abilities: execute wasm contract failed") }); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs deleted file mode 100644 index ceb738ba8..000000000 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs +++ /dev/null @@ -1,339 +0,0 @@ -use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; -use osmosis_test_tube::Account; - -use crate::test_env::{ - test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, -}; - -#[test] -fn set_blacklister_performed_by_contract_owner_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), true, owner) - .unwrap(); - - let is_blacklister = env - .cw_tokenfactory_issuer - .query_is_blacklister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(is_blacklister); - - env.cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), false, owner) - .unwrap(); - - let is_blacklister = env - .cw_tokenfactory_issuer - .query_is_blacklister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(!is_blacklister); -} - -#[test] -fn set_blacklister_performed_by_non_contract_owner_should_fail() { - let env = TestEnv::default(); - let non_owner = &env.test_accs[1]; - - let err = env - .cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), true, non_owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_blacklister_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_blacklister(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .set_blacklister(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklister_allowances(None, None) - .unwrap() - .blacklisters, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .set_blacklister(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklister_allowances(None, None) - .unwrap() - .blacklisters, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_blacklister(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -#[test] -fn blacklist_by_blacklister_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - let blacklistee = &env.test_accs[2]; - - env.cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, non_owner) - .unwrap(); - - // should be blacklisted after set true - assert!( - env.cw_tokenfactory_issuer - .query_is_blacklisted(&blacklistee.address()) - .unwrap() - .status - ); - - env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), false, non_owner) - .unwrap(); - - // should be unblacklisted after set false - assert!( - !env.cw_tokenfactory_issuer - .query_is_blacklisted(&blacklistee.address()) - .unwrap() - .status - ); -} - -#[test] -fn blacklist_by_non_blacklister_should_fail() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let blacklistee = &env.test_accs[2]; - let err = env - .cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_blacklist_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .blacklist(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .blacklist(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklistees(None, None) - .unwrap() - .blacklistees, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .blacklist(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklistees(None, None) - .unwrap() - .blacklistees, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_blacklisted(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -// query blacklisters -#[test] -fn query_blacklister_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklister_allowances(start_after, limit) - .unwrap() - .blacklisters - } - }, - ); -} - -#[test] -fn query_blacklister_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklister_allowances(start_after, limit) - .unwrap() - .blacklisters - } - }, - ); -} -// query blacklistees -#[test] -fn query_blacklistee_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .blacklist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklistees(start_after, limit) - .unwrap() - .blacklistees - } - }, - ); -} - -#[test] -fn query_blacklistee_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .blacklist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklistees(start_after, limit) - .unwrap() - .blacklistees - } - }, - ); -} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs index 66977a1e9..6c63e1e38 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs @@ -42,7 +42,9 @@ fn set_burner_performed_by_non_contract_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } @@ -52,18 +54,18 @@ fn set_allowance_to_0_should_remove_it_from_storage() { let owner = &env.test_accs[0]; let burner = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_burner(&burner.address(), allowance, owner) .unwrap(); - // set allowance to 0 + // Set allowance to 0 env.cw_tokenfactory_issuer .set_burner(&burner.address(), 0, owner) .unwrap(); - // check if key for the minter address is removed + // Check if key for the minter address is removed assert_eq!( env.cw_tokenfactory_issuer .query_burn_allowances(None, None) @@ -79,13 +81,13 @@ fn used_up_allowance_should_be_removed_from_storage() { let owner = &env.test_accs[0]; let burner = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_minter(&burner.address(), allowance, owner) .unwrap(); - // mint the whole allowance to be burned the same amount later + // Mint the whole allowance to be burned the same amount later env.cw_tokenfactory_issuer .mint(&burner.address(), allowance, burner) .unwrap(); @@ -94,12 +96,12 @@ fn used_up_allowance_should_be_removed_from_storage() { .set_burner(&burner.address(), allowance, owner) .unwrap(); - // use all allowance + // Use all allowance env.cw_tokenfactory_issuer .burn(&burner.address(), allowance, burner) .unwrap(); - // check if key for the burner address is removed + // Check if key for the burner address is removed assert_eq!( env.cw_tokenfactory_issuer .query_burn_allowances(None, None) @@ -127,7 +129,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow let burner = &env.test_accs[1]; let burn_to = &env.test_accs[2]; - // mint + // Mint env.cw_tokenfactory_issuer .set_minter(&burner.address(), allowance, owner) .unwrap(); @@ -136,7 +138,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow .mint(&burn_to.address(), burn_amount, burner) .unwrap(); - // burn + // Burn env.cw_tokenfactory_issuer .set_burner(&burner.address(), allowance, owner) .unwrap(); @@ -145,7 +147,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow .burn(&burn_to.address(), burn_amount, burner) .unwrap(); - // check if allowance is deducted properly + // Check if allowance is deducted properly let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) @@ -155,7 +157,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow assert_eq!(resulted_allowance, allowance - burn_amount); - // check if resulted balance is 0 + // Check if resulted balance is 0 let amount = env .bank() .query_balance(&QueryBalanceRequest { @@ -185,7 +187,7 @@ fn burn_more_than_balance_should_fail_and_not_deduct_allowance() { let allowance = burn_amount; - // mint + // Mint env.cw_tokenfactory_issuer .set_minter(&burner.address(), balance, owner) .unwrap(); @@ -194,7 +196,7 @@ fn burn_more_than_balance_should_fail_and_not_deduct_allowance() { .mint(&burn_from.address(), balance, burner) .unwrap(); - // burn + // Burn env.cw_tokenfactory_issuer .set_burner(&burner.address(), allowance, owner) .unwrap(); @@ -211,7 +213,7 @@ fn burn_more_than_balance_should_fail_and_not_deduct_allowance() { } ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) @@ -251,7 +253,7 @@ fn burn_over_allowance_should_fail_and_not_deduct_allowance() { )) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) @@ -288,7 +290,7 @@ fn burn_0_should_fail_and_not_deduct_allowance() { TokenfactoryIssuer::execute_error(ContractError::ZeroAmount {}) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs index 3140fac06..f7cc5491f 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs @@ -1,4 +1,5 @@ -use cw_tokenfactory_issuer::ContractError; +use cosmwasm_std::{Addr, Uint128}; +use cw_tokenfactory_issuer::{msg::ExecuteMsg, ContractError}; use osmosis_test_tube::Account; use crate::test_env::{TestEnv, TokenfactoryIssuer}; @@ -10,25 +11,27 @@ fn change_owner_by_owner_should_work() { let new_owner = &env.test_accs[1]; assert_eq!( - prev_owner.address(), - env.cw_tokenfactory_issuer.query_owner().unwrap().address + Some(Addr::unchecked(prev_owner.address())), + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, ); env.cw_tokenfactory_issuer - .change_contract_owner(&new_owner.address(), prev_owner) + .update_contract_owner(new_owner, prev_owner) .unwrap(); assert_eq!( - new_owner.address(), - env.cw_tokenfactory_issuer.query_owner().unwrap().address + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, + Some(Addr::unchecked(new_owner.address())), ); - // previous owner should not be able to execute owner action + // Previous owner should not be able to execute owner action assert_eq!( env.cw_tokenfactory_issuer - .change_contract_owner(&prev_owner.address(), prev_owner) + .update_contract_owner(prev_owner, prev_owner) .unwrap_err(), - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } @@ -39,11 +42,79 @@ fn change_owner_by_non_owner_should_fail() { let err = env .cw_tokenfactory_issuer - .change_contract_owner(&new_owner.address(), new_owner) + .update_contract_owner(new_owner, new_owner) .unwrap_err(); assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) + ); +} + +#[test] +fn renounce_ownership() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; + let hook = &env.test_accs[1]; + + assert_eq!( + Some(Addr::unchecked(owner.address())), + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, + ); + + // Renounce ownership + env.cw_tokenfactory_issuer + .execute( + &ExecuteMsg::UpdateOwnership(cw_ownable::Action::RenounceOwnership), + &[], + owner, + ) + .unwrap(); + + assert_eq!( + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, + None, + ); + + // Cannot perform actions that require ownership + assert_eq!( + env.cw_tokenfactory_issuer + .set_minter(&non_owner.address(), 10000, owner) + .unwrap_err(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) + ); + assert_eq!( + env.cw_tokenfactory_issuer + .set_burner(&non_owner.address(), 10000, owner) + .unwrap_err(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) + ); + assert_eq!( + env.cw_tokenfactory_issuer + .force_transfer( + non_owner, + Uint128::new(10000), + owner.address(), + non_owner.address(), + ) + .unwrap_err(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) + ); + assert_eq!( + env.cw_tokenfactory_issuer + .set_before_send_hook(hook.address(), owner) + .unwrap_err(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs index 6076d200b..cfcced250 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs @@ -1,7 +1,4 @@ use cw_tokenfactory_issuer::{msg::InstantiateMsg, ContractError}; -// use osmosis_test_tube::osmosis_std::types::cosmos::bank::v1beta1::{ -// DenomUnit, Metadata, QueryDenomMetadataRequest, -// }; use crate::test_env::{TestEnv, TokenfactoryIssuer}; @@ -9,7 +6,7 @@ use crate::test_env::{TestEnv, TokenfactoryIssuer}; fn set_denom_metadata_by_contract_owner_should_work() { let subdenom = "usthb".to_string(); - // set no metadata + // Set no metadata let env = TestEnv::new(InstantiateMsg::NewToken { subdenom }, 0).unwrap(); let owner = &env.test_accs[0]; @@ -42,7 +39,7 @@ fn set_denom_metadata_by_contract_owner_should_work() { fn set_denom_metadata_by_contract_non_owner_should_fail() { let subdenom = "usthb".to_string(); - // set no metadata + // Set no metadata let env = TestEnv::new(InstantiateMsg::NewToken { subdenom }, 0).unwrap(); let non_owner = &env.test_accs[1]; @@ -67,7 +64,7 @@ fn set_denom_metadata_by_contract_non_owner_should_fail() { symbol: "STHB".to_string(), }; - // set denom metadata + // Set denom metadata let err = env .cw_tokenfactory_issuer .set_denom_metadata(metadata, non_owner) @@ -75,7 +72,9 @@ fn set_denom_metadata_by_contract_non_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ) } @@ -83,7 +82,7 @@ fn set_denom_metadata_by_contract_non_owner_should_fail() { fn set_denom_metadata_with_base_denom_unit_should_overides_default_base_denom_unit() { let subdenom = "usthb".to_string(); - // set no metadata + // Set no metadata let env = TestEnv::new(InstantiateMsg::NewToken { subdenom }, 0).unwrap(); let owner = &env.test_accs[0]; @@ -108,36 +107,8 @@ fn set_denom_metadata_with_base_denom_unit_should_overides_default_base_denom_un symbol: "STHB".to_string(), }; - // set denom metadata + // Set denom metadata env.cw_tokenfactory_issuer .set_denom_metadata(metadata.clone(), owner) .unwrap(); - - // should update metadata - - // assert_eq!( - // env.bank() - // .query_denom_metadata(&QueryDenomMetadataRequest { - // denom: denom.clone() - // }) - // .unwrap() - // .metadata - // .unwrap(), - // Metadata { - // description: metadata.description, - // denom_units: metadata - // .denom_units - // .into_iter() - // .map(|d| DenomUnit { - // denom: d.denom, - // exponent: d.exponent, - // aliases: d.aliases, - // }) - // .collect(), - // base: denom, - // display: metadata.display, - // name: metadata.name, - // symbol: metadata.symbol, - // } - // ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs new file mode 100644 index 000000000..b15de2f22 --- /dev/null +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs @@ -0,0 +1,156 @@ +use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; +use osmosis_test_tube::Account; + +use crate::test_env::{ + test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, +}; + +#[test] +fn denylist_by_owner_should_pass() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let denylistee = &env.test_accs[2]; + + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + env.cw_tokenfactory_issuer + .deny(&denylistee.address(), true, owner) + .unwrap(); + + // Should be denylist after set true + assert!( + env.cw_tokenfactory_issuer + .query_is_denied(&denylistee.address()) + .unwrap() + .status + ); + + env.cw_tokenfactory_issuer + .deny(&denylistee.address(), false, owner) + .unwrap(); + + // Should be undenylist after set false + assert!( + !env.cw_tokenfactory_issuer + .query_is_denied(&denylistee.address()) + .unwrap() + .status + ); +} + +#[test] +fn denylist_by_non_owner_should_fail() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; + let denylistee = &env.test_accs[2]; + + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Non-owner cannot add address to denylist + let err = env + .cw_tokenfactory_issuer + .deny(&denylistee.address(), true, non_owner) + .unwrap_err(); + + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) + ); +} + +#[test] +fn set_denylist_to_issuer_itself_fails() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Owner cannot deny issuer itself + let err = env + .cw_tokenfactory_issuer + .deny(&env.cw_tokenfactory_issuer.contract_addr, true, owner) + .unwrap_err(); + + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::CannotDenylistSelf {}) + ); +} + +#[test] +fn query_denylist_within_default_limit() { + test_query_within_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Deny address + env.cw_tokenfactory_issuer + .deny(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_denylist(start_after, limit) + .unwrap() + .denylist + } + }, + ); +} + +#[test] +fn query_denylist_over_default_limit() { + test_query_over_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Deny address + env.cw_tokenfactory_issuer + .deny(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_denylist(start_after, limit) + .unwrap() + .denylist + } + }, + ); +} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs index db8c4c5a4..a02bafce9 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs @@ -37,7 +37,9 @@ fn test_force_transfer() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); // Owner can force transfer tokens diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs index c72b9cd46..8a9963c73 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs @@ -1,128 +1,20 @@ -use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; -use osmosis_test_tube::Account; +use cw_tokenfactory_issuer::ContractError; -use crate::test_env::{ - test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, -}; +use crate::test_env::{TestEnv, TokenfactoryIssuer}; #[test] -fn set_freezer_performed_by_contract_owner_should_pass() { +fn freeze_by_owener_should_pass() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), true, owner) - .unwrap(); - - let is_freezer = env - .cw_tokenfactory_issuer - .query_is_freezer(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(is_freezer); - - env.cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), false, owner) - .unwrap(); - - let is_freezer = env - .cw_tokenfactory_issuer - .query_is_freezer(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(!is_freezer); -} - -#[test] -fn set_freezer_performed_by_non_contract_owner_should_fail() { - let env = TestEnv::default(); - let non_owner = &env.test_accs[1]; - - let err = env - .cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), true, non_owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_freezer_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_freezer(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .set_freezer(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_freezer_allowances(None, None) - .unwrap() - .freezers, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); + // Owner sets before send hook to enable advanced features env.cw_tokenfactory_issuer - .set_freezer(&sorted_addrs[1], false, owner) + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) .unwrap(); - assert_eq!( - env.cw_tokenfactory_issuer - .query_freezer_allowances(None, None) - .unwrap() - .freezers, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_freezer(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -#[test] -fn freeze_by_freezer_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer.freeze(true, non_owner).unwrap(); + env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - // should be frozen after set true + // Should be frozen after set true assert!( env.cw_tokenfactory_issuer .query_is_frozen() @@ -130,9 +22,9 @@ fn freeze_by_freezer_should_pass() { .is_frozen ); - env.cw_tokenfactory_issuer.freeze(false, non_owner).unwrap(); + env.cw_tokenfactory_issuer.freeze(false, owner).unwrap(); - // should be unfrozen after set false + // Should be unfrozen after set false assert!( !env.cw_tokenfactory_issuer .query_is_frozen() @@ -142,65 +34,26 @@ fn freeze_by_freezer_should_pass() { } #[test] -fn freeze_by_non_freezer_should_fail() { +fn freeze_by_non_owner_should_fail() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let err = env.cw_tokenfactory_issuer.freeze(true, owner).unwrap_err(); + let non_owner = &env.test_accs[1]; - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); -#[test] -fn query_freezer_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_freezer(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_freezer_allowances(start_after, limit) - .unwrap() - .freezers - } - }, - ); -} + // Non-owner cannot freeze + let err = env + .cw_tokenfactory_issuer + .freeze(true, non_owner) + .unwrap_err(); -#[test] -fn query_freezer_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_freezer(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_freezer_allowances(start_after, limit) - .unwrap() - .freezers - } - }, + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs index 786653642..32d176f1e 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs @@ -1,11 +1,14 @@ -use cosmwasm_std::coins; -use cw_tokenfactory_issuer::msg::InstantiateMsg; -use osmosis_test_tube::{Account, OsmosisTestApp, RunnerError}; +use cosmwasm_std::Addr; +use cw_tokenfactory_issuer::{ + msg::{InstantiateMsg, QueryMsg}, + state::BeforeSendHookInfo, +}; +use osmosis_test_tube::{Account, OsmosisTestApp}; use crate::test_env::{TestEnv, TokenfactoryIssuer}; #[test] -fn instantiate_with_new_token_shoud_set_initial_state_correctly() { +fn instantiate_with_new_token_should_set_initial_state_correctly() { let subdenom = "uthb".to_string(); let env = TestEnv::new( InstantiateMsg::NewToken { @@ -17,7 +20,7 @@ fn instantiate_with_new_token_shoud_set_initial_state_correctly() { let owner = &env.test_accs[0]; - // check tokenfactory's token admin + // Check tokenfactory's token admin let denom = format!( "factory/{}/{}", env.cw_tokenfactory_issuer.contract_addr, subdenom @@ -29,13 +32,14 @@ fn instantiate_with_new_token_shoud_set_initial_state_correctly() { "token admin must be tokenfactory-issuer contract" ); - // check initial contract state + // Check initial contract state let contract_denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; assert_eq!( denom, contract_denom, "denom stored in contract must be `factory//`" ); + // Contract is not frozen let is_frozen = env .cw_tokenfactory_issuer .query_is_frozen() @@ -43,51 +47,21 @@ fn instantiate_with_new_token_shoud_set_initial_state_correctly() { .is_frozen; assert!(!is_frozen, "newly instantiated contract must not be frozen"); - let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().address; + // Advanced features requiring BeforeSendHook are disabled + let info: BeforeSendHookInfo = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookInfo {}) + .unwrap(); + assert!(!info.advanced_features_enabled); + + let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().owner; assert_eq!( owner_addr, - owner.address(), + Some(Addr::unchecked(owner.address())), "owner must be contract instantiate tx signer" ); } -#[test] -fn instantiate_with_new_token_shoud_set_hook_correctly() { - let subdenom = "uthb".to_string(); - let env = TestEnv::new( - InstantiateMsg::NewToken { - subdenom: subdenom.clone(), - }, - 0, - ) - .unwrap(); - - let owner = &env.test_accs[0]; - - let denom = format!( - "factory/{}/{}", - env.cw_tokenfactory_issuer.contract_addr, subdenom - ); - - // freeze - env.cw_tokenfactory_issuer - .set_freezer(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - - // bank send should fail - let err = env - .send_tokens( - env.test_accs[1].address(), - coins(10000, denom.clone()), - owner, - ) - .unwrap_err(); - - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); -} - #[test] fn instantiate_with_existing_token_should_set_initial_state_correctly() { let app = OsmosisTestApp::new(); @@ -123,10 +97,10 @@ fn instantiate_with_existing_token_should_set_initial_state_correctly() { .is_frozen; assert!(!is_frozen, "newly instantiated contract must not be frozen"); - let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().address; + let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().owner; assert_eq!( owner_addr, - owner.address(), + Some(Addr::unchecked(owner.address())), "owner must be contract instantiate tx signer" ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs index cc632bc38..4b68bce12 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs @@ -40,7 +40,9 @@ fn set_minter_performed_by_non_contract_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } @@ -50,18 +52,18 @@ fn set_allowance_to_0_should_remove_it_from_storage() { let owner = &env.test_accs[0]; let minter = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_minter(&minter.address(), allowance, owner) .unwrap(); - // set allowance to 0 + // Set allowance to 0 env.cw_tokenfactory_issuer .set_minter(&minter.address(), 0, owner) .unwrap(); - // check if key for the minter address is removed + // Check if key for the minter address is removed assert_eq!( env.cw_tokenfactory_issuer .query_mint_allowances(None, None) @@ -77,18 +79,18 @@ fn used_up_allowance_should_be_removed_from_storage() { let owner = &env.test_accs[0]; let minter = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_minter(&minter.address(), allowance, owner) .unwrap(); - // use all allowance + // Use all allowance env.cw_tokenfactory_issuer .mint(&minter.address(), allowance, minter) .unwrap(); - // check if key for the minter address is removed + // Check if key for the minter address is removed assert_eq!( env.cw_tokenfactory_issuer .query_mint_allowances(None, None) @@ -124,7 +126,7 @@ fn mint_less_than_or_eq_allowance_should_pass_and_deduct_allowance() { .mint(&mint_to.address(), mint_amount, minter) .unwrap(); - // check if allowance is deducted properly + // Check if allowance is deducted properly let resulted_allowance = env .cw_tokenfactory_issuer .query_mint_allowance(&minter.address()) @@ -177,7 +179,7 @@ fn mint_over_allowance_should_fail_and_not_deduct_allowance() { )) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_mint_allowance(&minter.address()) @@ -214,7 +216,7 @@ fn mint_0_should_fail_and_not_deduct_allowance() { TokenfactoryIssuer::execute_error(ContractError::ZeroAmount {}) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_mint_allowance(&minter.address()) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs index 3fe1f6085..056859504 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs @@ -1,12 +1,12 @@ +mod allowlist; mod beforesend; -mod blacklist; mod burn; mod contract_owner; mod denom_metadata; +mod denylist; mod force_transfer; mod freeze; mod instantiate; mod mint; -mod set_before_update_hook; +mod set_before_send_hook; mod tokenfactory_admin; -mod whitelist; diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_send_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_send_hook.rs new file mode 100644 index 000000000..74810986d --- /dev/null +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_send_hook.rs @@ -0,0 +1,105 @@ +use cosmwasm_std::coins; +use cw_tokenfactory_issuer::msg::QueryMsg; +use cw_tokenfactory_issuer::{state::BeforeSendHookInfo, ContractError}; +use osmosis_test_tube::{Account, RunnerError}; + +use crate::test_env::{TestEnv, TokenfactoryIssuer}; + +#[test] +fn test_set_before_send_hook() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; + + // Non-owner cannot set before update hook + let err = env + .cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), non_owner) + .unwrap_err(); + + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) + ); + + // Owner can set before update hook, but hook is already set + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Query before update hook + let info: BeforeSendHookInfo = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookInfo {}) + .unwrap(); + assert!(info.advanced_features_enabled); +} + +#[test] +fn test_set_before_send_hook_nil() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + + // Owner can set before update hook to nil + env.cw_tokenfactory_issuer + .set_before_send_hook("".to_string(), owner) + .unwrap(); + + // Query before update hook, should now be disabled + let info: BeforeSendHookInfo = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookInfo {}) + .unwrap(); + assert!(!info.advanced_features_enabled); +} + +#[test] +fn test_set_before_send_hook_invalid_address_fails() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + + // Invalid address fails + let err = env + .cw_tokenfactory_issuer + .set_before_send_hook("invalid".to_string(), owner) + .unwrap_err(); + + assert_eq!( + err, + RunnerError::ExecuteError { msg: "failed to execute message; message index: 0: Generic error: addr_validate errored: decoding bech32 failed: invalid bech32 string length 7: execute wasm contract failed".to_string() } + ); +} + +#[test] +fn test_set_before_send_hook_to_a_different_contract() { + let env = TestEnv::default(); + let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + let owner = &env.test_accs[0]; + let hook = &env.test_accs[1]; + + // Owner can set before update hook to nil + env.cw_tokenfactory_issuer + .set_before_send_hook(hook.address(), owner) + .unwrap(); + + // Query before update hook, should now be disabled + let info: BeforeSendHookInfo = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookInfo {}) + .unwrap(); + // Advanced features for this contract are not enabled + assert!(!info.advanced_features_enabled); + // But the hook contract address is set + assert_eq!(info.hook_contract_address.unwrap(), hook.address()); + + // Bank send should pass + env.send_tokens(hook.address(), coins(10000, "uosmo"), owner) + .unwrap(); + + // Bank send of TF denom should fail as the hook account isn't a contract + // and doesn't implement the required interface. + env.send_tokens(hook.address(), coins(10000, denom), owner) + .unwrap_err(); +} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs deleted file mode 100644 index 39299fd93..000000000 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs +++ /dev/null @@ -1,32 +0,0 @@ -use cw_tokenfactory_issuer::ContractError; - -use crate::test_env::{TestEnv, TokenfactoryIssuer}; - -#[test] -fn test_set_before_update_hook() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - // Non-owner cannot set before update hook - let err = env - .cw_tokenfactory_issuer - .set_before_send_hook(non_owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); - - // Owner can set before update hook, but hook is already set - let err = env - .cw_tokenfactory_issuer - .set_before_send_hook(owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::BeforeSendHookAlreadyEnabled {}) - ); -} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs index 65a3151e8..7edb5efc6 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs @@ -11,7 +11,7 @@ fn transfer_token_factory_admin_by_contract_owner_should_pass() { let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; env.cw_tokenfactory_issuer - .change_tokenfactory_admin(&new_admin.address(), owner) + .update_tokenfactory_admin(&new_admin.address(), owner) .unwrap(); assert_eq!(new_admin.address(), env.token_admin(&denom)); @@ -25,11 +25,13 @@ fn transfer_token_factory_admin_by_non_contract_owner_should_fail() { let err = env .cw_tokenfactory_issuer - .change_tokenfactory_admin(&someone_else.address(), non_owner) + .update_tokenfactory_admin(&someone_else.address(), non_owner) .unwrap_err(); assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ) } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs deleted file mode 100644 index a4a39f385..000000000 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs +++ /dev/null @@ -1,339 +0,0 @@ -use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; -use osmosis_test_tube::Account; - -use crate::test_env::{ - test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, -}; - -#[test] -fn set_whitelister_performed_by_contract_owner_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), true, owner) - .unwrap(); - - let is_whitelister = env - .cw_tokenfactory_issuer - .query_is_whitelister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(is_whitelister); - - env.cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), false, owner) - .unwrap(); - - let is_whitelister = env - .cw_tokenfactory_issuer - .query_is_whitelister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(!is_whitelister); -} - -#[test] -fn set_whitelister_performed_by_non_contract_owner_should_fail() { - let env = TestEnv::default(); - let non_owner = &env.test_accs[1]; - - let err = env - .cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), true, non_owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_whitelister_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_whitelister(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .set_whitelister(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelister_allowances(None, None) - .unwrap() - .whitelisters, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .set_whitelister(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelister_allowances(None, None) - .unwrap() - .whitelisters, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_whitelister(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -#[test] -fn whitelist_by_whitelister_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - let whitelistee = &env.test_accs[2]; - - env.cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), true, non_owner) - .unwrap(); - - // should be whitelisted after set true - assert!( - env.cw_tokenfactory_issuer - .query_is_whitelisted(&whitelistee.address()) - .unwrap() - .status - ); - - env.cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), false, non_owner) - .unwrap(); - - // should be unwhitelisted after set false - assert!( - !env.cw_tokenfactory_issuer - .query_is_whitelisted(&whitelistee.address()) - .unwrap() - .status - ); -} - -#[test] -fn whitelist_by_non_whitelister_should_fail() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let whitelistee = &env.test_accs[2]; - let err = env - .cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), true, owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_whitelist_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .whitelist(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .whitelist(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelistees(None, None) - .unwrap() - .whitelistees, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .whitelist(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelistees(None, None) - .unwrap() - .whitelistees, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_whitelisted(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -// query whitelisters -#[test] -fn query_whitelister_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelister_allowances(start_after, limit) - .unwrap() - .whitelisters - } - }, - ); -} - -#[test] -fn query_whitelister_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelister_allowances(start_after, limit) - .unwrap() - .whitelisters - } - }, - ); -} -// query whitelistees -#[test] -fn query_whitelistee_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .whitelist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelistees(start_after, limit) - .unwrap() - .whitelistees - } - }, - ); -} - -#[test] -fn query_whitelistee_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .whitelist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelistees(start_after, limit) - .unwrap() - .whitelistees - } - }, - ); -} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs index af9079487..f6f4b3d1e 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs @@ -2,17 +2,12 @@ // see https://github.com/rust-lang/rust/issues/46379 #![allow(dead_code)] -use cosmwasm_std::{Coin, Uint128}; - -use cw_tokenfactory_issuer::msg::{ - BlacklisteesResponse, BlacklisterAllowancesResponse, Metadata, MigrateMsg, - WhitelisteesResponse, WhitelisterAllowancesResponse, -}; +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw_tokenfactory_issuer::msg::{AllowlistResponse, DenylistResponse, Metadata, MigrateMsg}; use cw_tokenfactory_issuer::{ msg::{ - AllowanceResponse, AllowancesResponse, DenomResponse, ExecuteMsg, - FreezerAllowancesResponse, InstantiateMsg, IsFrozenResponse, OwnerResponse, QueryMsg, - StatusResponse, + AllowanceResponse, AllowancesResponse, DenomResponse, ExecuteMsg, InstantiateMsg, + IsFrozenResponse, QueryMsg, StatusResponse, }, ContractError, }; @@ -173,32 +168,39 @@ impl TokenfactoryIssuer { wasm.execute(&self.contract_addr, execute_msg, funds, signer) } - pub fn change_contract_owner( + pub fn update_contract_owner( &self, - new_owner: &str, + new_owner: &SigningAccount, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::ChangeContractOwner { - new_owner: new_owner.to_string(), - }, + &ExecuteMsg::UpdateOwnership(cw_ownable::Action::TransferOwnership { + new_owner: new_owner.address(), + expiry: None, + }), &[], signer, + )?; + self.execute( + &ExecuteMsg::UpdateOwnership(cw_ownable::Action::AcceptOwnership {}), + &[], + new_owner, ) } - pub fn change_tokenfactory_admin( + pub fn update_tokenfactory_admin( &self, new_admin: &str, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::ChangeTokenFactoryAdmin { + &ExecuteMsg::UpdateTokenFactoryAdmin { new_admin: new_admin.to_string(), }, &[], signer, ) } + pub fn set_denom_metadata( &self, metadata: Metadata, @@ -270,56 +272,13 @@ impl TokenfactoryIssuer { ) } - pub fn set_freezer( - &self, - address: &str, - status: bool, - signer: &SigningAccount, - ) -> RunnerExecuteResult { - self.execute( - &ExecuteMsg::SetFreezer { - address: address.to_string(), - status, - }, - &[], - signer, - ) - } - pub fn set_before_send_hook( &self, - signer: &SigningAccount, - ) -> RunnerExecuteResult { - self.execute(&ExecuteMsg::SetBeforeSendHook {}, &[], signer) - } - - pub fn set_blacklister( - &self, - address: &str, - status: bool, + cosmwasm_address: String, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::SetBlacklister { - address: address.to_string(), - status, - }, - &[], - signer, - ) - } - - pub fn set_whitelister( - &self, - address: &str, - status: bool, - signer: &SigningAccount, - ) -> RunnerExecuteResult { - self.execute( - &ExecuteMsg::SetWhitelister { - address: address.to_string(), - status, - }, + &ExecuteMsg::SetBeforeSendHook { cosmwasm_address }, &[], signer, ) @@ -351,14 +310,14 @@ impl TokenfactoryIssuer { self.execute(&ExecuteMsg::Freeze { status }, &[], signer) } - pub fn blacklist( + pub fn deny( &self, address: &str, status: bool, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::Blacklist { + &ExecuteMsg::Deny { address: address.to_string(), status, }, @@ -367,14 +326,14 @@ impl TokenfactoryIssuer { ) } - pub fn whitelist( + pub fn allow( &self, address: &str, status: bool, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::Whitelist { + &ExecuteMsg::Allow { address: address.to_string(), status, }, @@ -396,82 +355,24 @@ impl TokenfactoryIssuer { self.query(&QueryMsg::Denom {}) } - pub fn query_is_freezer(&self, address: &str) -> Result { - self.query(&QueryMsg::IsFreezer { - address: address.to_string(), - }) - } - - pub fn query_is_blacklister(&self, address: &str) -> Result { - self.query(&QueryMsg::IsBlacklister { - address: address.to_string(), - }) - } - - pub fn query_is_whitelister(&self, address: &str) -> Result { - self.query(&QueryMsg::IsWhitelister { - address: address.to_string(), - }) - } - - pub fn query_freezer_allowances( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::FreezerAllowances { start_after, limit }) - } - - pub fn query_blacklister_allowances( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::BlacklisterAllowances { start_after, limit }) - } - - pub fn query_blacklistees( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::Blacklistees { start_after, limit }) - } - - pub fn query_whitelister_allowances( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::WhitelisterAllowances { start_after, limit }) - } - - pub fn query_whitelistees( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::Whitelistees { start_after, limit }) - } - pub fn query_is_frozen(&self) -> Result { self.query(&QueryMsg::IsFrozen {}) } - pub fn query_is_blacklisted(&self, address: &str) -> Result { - self.query(&QueryMsg::IsBlacklisted { + pub fn query_is_denied(&self, address: &str) -> Result { + self.query(&QueryMsg::IsDenied { address: address.to_string(), }) } - pub fn query_is_whitelisted(&self, address: &str) -> Result { - self.query(&QueryMsg::IsWhitelisted { + pub fn query_is_allowed(&self, address: &str) -> Result { + self.query(&QueryMsg::IsAllowed { address: address.to_string(), }) } - pub fn query_owner(&self) -> Result { - self.query(&QueryMsg::Owner {}) + pub fn query_owner(&self) -> Result, RunnerError> { + self.query(&QueryMsg::Ownership {}) } pub fn query_mint_allowance(&self, address: &str) -> Result { @@ -502,6 +403,22 @@ impl TokenfactoryIssuer { self.query(&QueryMsg::BurnAllowances { start_after, limit }) } + pub fn query_allowlist( + &self, + start_after: Option, + limit: Option, + ) -> Result { + self.query(&QueryMsg::Allowlist { start_after, limit }) + } + + pub fn query_denylist( + &self, + start_after: Option, + limit: Option, + ) -> Result { + self.query(&QueryMsg::Denylist { start_after, limit }) + } + pub fn migrate( &self, testdata: &str, diff --git a/contracts/external/cw-vesting/src/suite_tests/tests.rs b/contracts/external/cw-vesting/src/suite_tests/tests.rs index f93656c64..2738a3539 100644 --- a/contracts/external/cw-vesting/src/suite_tests/tests.rs +++ b/contracts/external/cw-vesting/src/suite_tests/tests.rs @@ -376,7 +376,12 @@ fn test_slash_while_cancelled_counts_against_owner() { assert_eq!(balance, distributable); let vest = suite.query_vest(); - let Status::Canceled { owner_withdrawable: pre_slash } = vest.status else { panic!("should be canceled") }; + let Status::Canceled { + owner_withdrawable: pre_slash, + } = vest.status + else { + panic!("should be canceled") + }; // register the slash. even though the time of the slash was // during the vest, the contract should deduct this from @@ -390,7 +395,9 @@ fn test_slash_while_cancelled_counts_against_owner() { .unwrap(); let vest = suite.query_vest(); - let Status::Canceled { owner_withdrawable } = vest.status else { panic!("should be canceled") }; + let Status::Canceled { owner_withdrawable } = vest.status else { + panic!("should be canceled") + }; assert_eq!(pre_slash - Uint128::new(10_000_000), owner_withdrawable); } diff --git a/contracts/external/cw721-roles/src/tests.rs b/contracts/external/cw721-roles/src/tests.rs index 270a1bc0e..0783af57a 100644 --- a/contracts/external/cw721-roles/src/tests.rs +++ b/contracts/external/cw721-roles/src/tests.rs @@ -260,7 +260,6 @@ fn test_send_permissions() { dao_voting_cw721_staked_id, Addr::unchecked(DAO), &Cw721StakedInstantiateMsg { - owner: None, nft_contract: NftContract::Existing { address: cw721_addr.to_string(), }, diff --git a/contracts/external/dao-migrator/schema/dao-migrator.json b/contracts/external/dao-migrator/schema/dao-migrator.json index 3e47c1302..db060770c 100644 --- a/contracts/external/dao-migrator/schema/dao-migrator.json +++ b/contracts/external/dao-migrator/schema/dao-migrator.json @@ -1045,7 +1045,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1163,7 +1163,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1600,7 +1600,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -2899,7 +2899,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -3017,7 +3017,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -3490,7 +3490,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -4080,7 +4080,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -4198,7 +4198,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -4651,7 +4651,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -5206,7 +5206,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -5324,7 +5324,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -5797,7 +5797,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { 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 659b2d7d7..c38ce7f64 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml @@ -35,7 +35,7 @@ cw4-group = { workspace = true } cw20 = { workspace = true } cw20-base = { workspace = true } dao-dao-core = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-testing = { workspace = true } dao-voting = { workspace = true } dao-voting-cw4 = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json index c11084723..e8675b00a 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json +++ b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json @@ -816,7 +816,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -934,7 +934,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1273,7 +1273,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml b/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml index 05bc50642..1f6e594fc 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml @@ -35,7 +35,7 @@ cw4-group = { workspace = true } cw20 = { workspace = true } cw20-base = { workspace = true } dao-dao-core = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-proposal-single = { workspace = true, features = ["library"] } dao-testing = { workspace = true } dao-voting = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml b/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml index 7352139b4..670c0f281 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml @@ -37,4 +37,4 @@ dao-voting = { workspace = true } cw-denom = { workspace = true } dao-interface = { workspace = true } dao-testing = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json index d0fb4ab83..e2972c646 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json +++ b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json @@ -734,7 +734,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -852,7 +852,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1228,7 +1228,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { 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 e256a1f47..3d7352515 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs @@ -1021,7 +1021,7 @@ fn test_execute_extension_does_nothing() { assert_eq!(res.events[0].attributes.len(), 1); assert_eq!( res.events[0].attributes[0].key, - "_contract_addr".to_string() + "_contract_address".to_string() ) } diff --git a/contracts/pre-propose/dao-pre-propose-single/Cargo.toml b/contracts/pre-propose/dao-pre-propose-single/Cargo.toml index c7d1dd39a..ac1fea9d4 100644 --- a/contracts/pre-propose/dao-pre-propose-single/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-single/Cargo.toml @@ -36,6 +36,6 @@ dao-voting = { workspace = true } cw-denom = { workspace = true } dao-interface = { workspace = true } dao-testing = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-proposal-single = { workspace = true } cw-hooks = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json index 9cf71147a..815c31c6b 100644 --- a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json +++ b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json @@ -734,7 +734,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -852,7 +852,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1192,7 +1192,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { 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 6a8b48e9f..078ea2d4b 100644 --- a/contracts/pre-propose/dao-pre-propose-single/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-single/src/tests.rs @@ -957,7 +957,7 @@ fn test_execute_extension_does_nothing() { assert_eq!(res.events[0].attributes.len(), 1); assert_eq!( res.events[0].attributes[0].key, - "_contract_addr".to_string() + "_contract_address".to_string() ) } diff --git a/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json b/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json index bf32a13d6..88f46d48b 100644 --- a/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json +++ b/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json @@ -586,7 +586,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -704,7 +704,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -956,7 +956,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -1727,7 +1727,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1845,7 +1845,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -2250,7 +2250,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/proposal/dao-proposal-multiple/Cargo.toml b/contracts/proposal/dao-proposal-multiple/Cargo.toml index 35f200f8e..a7016b1ee 100644 --- a/contracts/proposal/dao-proposal-multiple/Cargo.toml +++ b/contracts/proposal/dao-proposal-multiple/Cargo.toml @@ -38,8 +38,7 @@ dao-pre-propose-base = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } cw-hooks = { workspace = true } -dao-proposal-hooks = { workspace = true } -dao-vote-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-pre-propose-multiple = { workspace = true } voting-v1 = { workspace = true } @@ -48,7 +47,7 @@ cw-multi-test = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-cw20-balance = { workspace = true } dao-voting-cw20-staked = { workspace = true } -dao-voting-native-staked = { workspace = true } +dao-voting-token-staked = { workspace = true } dao-voting-cw721-staked = { workspace = true } cw-denom = { workspace = true } dao-testing = { workspace = true } diff --git a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json index 3783a86e8..8d67f6e3c 100644 --- a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json +++ b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json @@ -1019,7 +1019,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1137,7 +1137,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1521,7 +1521,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -2785,7 +2785,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -2903,7 +2903,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -3350,7 +3350,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -3966,7 +3966,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -4084,7 +4084,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -4512,7 +4512,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -5104,7 +5104,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -5222,7 +5222,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -5669,7 +5669,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/proposal/dao-proposal-multiple/src/contract.rs b/contracts/proposal/dao-proposal-multiple/src/contract.rs index 35465ab50..073eb5511 100644 --- a/contracts/proposal/dao-proposal-multiple/src/contract.rs +++ b/contracts/proposal/dao-proposal-multiple/src/contract.rs @@ -9,10 +9,10 @@ use cw2::set_contract_version; use cw_hooks::Hooks; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; +use dao_hooks::proposal::{new_proposal_hooks, proposal_status_changed_hooks}; +use dao_hooks::vote::new_vote_hooks; use dao_interface::voting::IsActiveResponse; use dao_pre_propose_multiple::contract::ExecuteMsg as PreProposeMsg; -use dao_proposal_hooks::{new_proposal_hooks, proposal_status_changed_hooks}; -use dao_vote_hooks::new_vote_hooks; use dao_voting::{ multiple_choice::{ MultipleChoiceOptions, MultipleChoiceVote, MultipleChoiceVotes, VotingStrategy, diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs index 32eb43168..c1430c2f9 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs @@ -240,7 +240,6 @@ where match should_execute { ShouldExecute::Yes => { if res.is_err() { - println!("{:?}", res.err()); panic!() } // Check that the vote was recorded correctly. @@ -702,7 +701,7 @@ where let one_sum: u64 = one.iter().sum(); let none_sum: u64 = none.iter().sum(); - let mut sums = vec![zero_sum, one_sum, none_sum]; + let mut sums = [zero_sum, one_sum, none_sum]; sums.sort_unstable(); // If none of the above wins or there is a tie between second and first choice. diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index 45a76e9d2..2d767ae5d 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -148,7 +148,6 @@ pub fn _instantiate_with_staked_cw721_governance( voting_module_instantiate_info: ModuleInstantiateInfo { code_id: cw721_stake_id, msg: to_binary(&dao_voting_cw721_staked::msg::InstantiateMsg { - owner: Some(Admin::CoreModule {}), unstaking_duration: None, nft_contract: dao_voting_cw721_staked::msg::NftContract::Existing { address: nft_address.to_string(), @@ -265,8 +264,10 @@ pub fn _instantiate_with_native_staked_balances_governance( automatically_add_cw721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { code_id: native_stake_id, - msg: to_binary(&dao_voting_native_staked::msg::InstantiateMsg { - denom: "ujuno".to_string(), + msg: to_binary(&dao_voting_token_staked::msg::InstantiateMsg { + token_info: dao_voting_token_staked::msg::TokenInfo::Existing { + denom: "ujuno".to_string(), + }, unstaking_duration: None, active_threshold: None, }) @@ -316,7 +317,7 @@ pub fn _instantiate_with_native_staked_balances_governance( app.execute_contract( Addr::unchecked(&address), native_staking_addr.clone(), - &dao_voting_native_staked::msg::ExecuteMsg::Stake {}, + &dao_voting_token_staked::msg::ExecuteMsg::Stake {}, &[Coin { amount, denom: "ujuno".to_string(), diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index fdf6e842d..eb23de7f3 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -32,8 +32,7 @@ dao-pre-propose-base = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } cw-hooks = { workspace = true } -dao-proposal-hooks = { workspace = true } -dao-vote-hooks = { workspace = true } +dao-hooks = { workspace = true } cw-utils-v1 = { workspace = true} voting-v1 = { workspace = true } @@ -46,7 +45,7 @@ dao-dao-core = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-cw20-balance = { workspace = true } dao-voting-cw20-staked = { workspace = true } -dao-voting-native-staked = { workspace = true } +dao-voting-token-staked = { workspace = true } dao-voting-cw721-staked = { workspace = true } dao-pre-propose-single = { workspace = true } cw-denom = { workspace = true } diff --git a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json index 87f802625..44da656fc 100644 --- a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json +++ b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json @@ -1045,7 +1045,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1163,7 +1163,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1600,7 +1600,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -2899,7 +2899,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -3017,7 +3017,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -3490,7 +3490,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -4080,7 +4080,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -4198,7 +4198,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -4651,7 +4651,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -5206,7 +5206,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -5324,7 +5324,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -5797,7 +5797,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/proposal/dao-proposal-single/src/contract.rs b/contracts/proposal/dao-proposal-single/src/contract.rs index 5f30030f5..b5401786a 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -9,9 +9,9 @@ use cw_hooks::Hooks; use cw_proposal_single_v1 as v1; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; +use dao_hooks::proposal::{new_proposal_hooks, proposal_status_changed_hooks}; +use dao_hooks::vote::new_vote_hooks; use dao_interface::voting::IsActiveResponse; -use dao_proposal_hooks::{new_proposal_hooks, proposal_status_changed_hooks}; -use dao_vote_hooks::new_vote_hooks; use dao_voting::pre_propose::{PreProposeInfo, ProposalCreationPolicy}; use dao_voting::proposal::{ SingleChoiceProposeMsg as ProposeMsg, DEFAULT_LIMIT, MAX_PROPOSAL_SIZE, diff --git a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs index f27bd7e35..345cf5a3b 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs @@ -82,9 +82,9 @@ pub(crate) fn cw20_staked_balances_voting_contract() -> Box> pub(crate) fn native_staked_balances_voting_contract() -> Box> { let contract = ContractWrapper::new( - dao_voting_native_staked::contract::execute, - dao_voting_native_staked::contract::instantiate, - dao_voting_native_staked::contract::query, + dao_voting_token_staked::contract::execute, + dao_voting_token_staked::contract::instantiate, + dao_voting_token_staked::contract::query, ); Box::new(contract) } 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 ecd72e325..a97e23c23 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs @@ -1,7 +1,7 @@ -use cosmwasm_std::{coins, Addr, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Uint128}; use cw20::Cw20Coin; -use cw_multi_test::{App, BankSudo, Executor}; +use cw_multi_test::{App, BankSudo, Executor, SudoMsg}; use dao_interface::state::ProposalModule; use dao_pre_propose_single as cppbps; @@ -96,6 +96,17 @@ where { let mut app = App::default(); + // Mint some ujuno so that it exists for native staking tests + // Otherwise denom validation will fail + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: "sodenomexists".to_string(), + amount: vec![Coin { + amount: Uint128::new(10), + denom: "ujuno".to_string(), + }], + })) + .unwrap(); + let mut initial_balances = votes .iter() .map(|TestSingleChoiceVote { voter, weight, .. }| Cw20Coin { diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 8f1756edf..aafcb5abe 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -146,7 +146,6 @@ pub(crate) fn instantiate_with_staked_cw721_governance( voting_module_instantiate_info: ModuleInstantiateInfo { code_id: cw721_stake_id, msg: to_binary(&dao_voting_cw721_staked::msg::InstantiateMsg { - owner: Some(Admin::CoreModule {}), unstaking_duration: None, nft_contract: dao_voting_cw721_staked::msg::NftContract::Existing { address: nft_address.to_string(), @@ -263,8 +262,10 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( automatically_add_cw721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { code_id: native_stake_id, - msg: to_binary(&dao_voting_native_staked::msg::InstantiateMsg { - denom: "ujuno".to_string(), + msg: to_binary(&dao_voting_token_staked::msg::InstantiateMsg { + token_info: dao_voting_token_staked::msg::TokenInfo::Existing { + denom: "ujuno".to_string(), + }, unstaking_duration: None, active_threshold: None, }) @@ -313,7 +314,7 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( app.execute_contract( Addr::unchecked(&address), native_staking_addr.clone(), - &dao_voting_native_staked::msg::ExecuteMsg::Stake {}, + &dao_voting_token_staked::msg::ExecuteMsg::Stake {}, &[Coin { amount, denom: "ujuno".to_string(), diff --git a/contracts/staking/cw20-stake-external-rewards/Cargo.toml b/contracts/staking/cw20-stake-external-rewards/Cargo.toml index 1a7dd7cf1..b648436c1 100644 --- a/contracts/staking/cw20-stake-external-rewards/Cargo.toml +++ b/contracts/staking/cw20-stake-external-rewards/Cargo.toml @@ -28,6 +28,7 @@ cw2 = { workspace = true } thiserror = { workspace = true } cw20-stake = { workspace = true, features = ["library"]} cw-ownable = { workspace = true } +dao-hooks = { workspace = true } cw20-stake-external-rewards-v1 = { workspace = true } cw20-013 = { package = "cw20", version = "0.13" } diff --git a/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json b/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json index af3bc2f44..64c97cedb 100644 --- a/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json +++ b/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json @@ -286,6 +286,7 @@ ] }, "StakeChangedHookMsg": { + "description": "An enum representing staking hooks.", "oneOf": [ { "type": "object", diff --git a/contracts/staking/cw20-stake-external-rewards/src/contract.rs b/contracts/staking/cw20-stake-external-rewards/src/contract.rs index a2a50412d..b1774414f 100644 --- a/contracts/staking/cw20-stake-external-rewards/src/contract.rs +++ b/contracts/staking/cw20-stake-external-rewards/src/contract.rs @@ -19,7 +19,7 @@ use cosmwasm_std::{ }; use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw20::{Cw20ReceiveMsg, Denom}; -use cw20_stake::hooks::StakeChangedHookMsg; +use dao_hooks::stake::StakeChangedHookMsg; use cw20::Denom::Cw20; use std::cmp::min; diff --git a/contracts/staking/cw20-stake-external-rewards/src/msg.rs b/contracts/staking/cw20-stake-external-rewards/src/msg.rs index 565508e39..eb873c798 100644 --- a/contracts/staking/cw20-stake-external-rewards/src/msg.rs +++ b/contracts/staking/cw20-stake-external-rewards/src/msg.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Uint128; use cw20::{Cw20ReceiveMsg, Denom}; -use cw20_stake::hooks::StakeChangedHookMsg; +use dao_hooks::stake::StakeChangedHookMsg; use crate::state::{Config, RewardConfig}; diff --git a/contracts/staking/cw20-stake/Cargo.toml b/contracts/staking/cw20-stake/Cargo.toml index a3706eed7..20acf9375 100644 --- a/contracts/staking/cw20-stake/Cargo.toml +++ b/contracts/staking/cw20-stake/Cargo.toml @@ -21,6 +21,7 @@ cosmwasm-storage = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } +cw-hooks = { workspace = true } cw20 = { workspace = true } cw-utils = { workspace = true } cw20-base = { workspace = true, features = ["library"] } @@ -28,6 +29,8 @@ cw2 = { workspace = true } thiserror = { workspace = true } cw-paginate-storage = { workspace = true } cw-ownable = { workspace = true } +dao-hooks = { workspace = true } +dao-voting = { workspace = true } cw20-stake-v1 = { workspace = true, features = ["library"] } cw-utils-v1 = { workspace = true } diff --git a/contracts/staking/cw20-stake/src/contract.rs b/contracts/staking/cw20-stake/src/contract.rs index d25417f87..f200b3737 100644 --- a/contracts/staking/cw20-stake/src/contract.rs +++ b/contracts/staking/cw20-stake/src/contract.rs @@ -5,21 +5,8 @@ use cosmwasm_std::{ from_binary, to_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, StdResult, Uint128, }; - -use cw20::{Cw20ReceiveMsg, TokenInfoResponse}; - -use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; -use crate::math; -use crate::msg::{ - ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, - ReceiveMsg, StakedBalanceAtHeightResponse, StakedValueResponse, StakerBalanceResponse, - TotalStakedAtHeightResponse, TotalValueResponse, -}; -use crate::state::{ - Config, BALANCE, CLAIMS, CONFIG, HOOKS, MAX_CLAIMS, STAKED_BALANCES, STAKED_TOTAL, -}; -use crate::ContractError; use cw2::{get_contract_version, set_contract_version, ContractVersion}; +use cw20::{Cw20ReceiveMsg, TokenInfoResponse}; pub use cw20_base::allowances::{ execute_burn_from, execute_decrease_allowance, execute_increase_allowance, execute_send_from, execute_transfer_from, query_allowance, @@ -32,28 +19,23 @@ pub use cw20_base::contract::{ pub use cw20_base::enumerable::{query_all_accounts, query_owner_allowances}; use cw_controllers::ClaimsResponse; use cw_utils::Duration; +use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; +use dao_voting::duration::validate_duration; + +use crate::math; +use crate::msg::{ + ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, + ReceiveMsg, StakedBalanceAtHeightResponse, StakedValueResponse, StakerBalanceResponse, + TotalStakedAtHeightResponse, TotalValueResponse, +}; +use crate::state::{ + Config, BALANCE, CLAIMS, CONFIG, HOOKS, MAX_CLAIMS, STAKED_BALANCES, STAKED_TOTAL, +}; +use crate::ContractError; pub(crate) const CONTRACT_NAME: &str = "crates.io:cw20-stake"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -fn validate_duration(duration: Option) -> Result<(), ContractError> { - if let Some(unstaking_duration) = duration { - match unstaking_duration { - Duration::Height(height) => { - if height == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - Duration::Time(time) => { - if time == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - } - } - Ok(()) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -182,7 +164,7 @@ pub fn execute_stake( deps.storage, &balance.checked_add(amount).map_err(StdError::overflow)?, )?; - let hook_msgs = stake_hook_msgs(deps.storage, sender.clone(), amount_to_stake)?; + let hook_msgs = stake_hook_msgs(HOOKS, deps.storage, sender.clone(), amount_to_stake)?; Ok(Response::new() .add_submessages(hook_msgs) .add_attribute("action", "stake") @@ -230,7 +212,7 @@ pub fn execute_unstake( .checked_sub(amount_to_claim) .map_err(StdError::overflow)?, )?; - let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + let hook_msgs = unstake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; match config.unstaking_duration { None => { let cw_send_msg = cw20::Cw20ExecuteMsg::Transfer { diff --git a/contracts/staking/cw20-stake/src/error.rs b/contracts/staking/cw20-stake/src/error.rs index cd7140a21..394cd0e04 100644 --- a/contracts/staking/cw20-stake/src/error.rs +++ b/contracts/staking/cw20-stake/src/error.rs @@ -5,29 +5,40 @@ use thiserror::Error; pub enum ContractError { #[error(transparent)] Std(#[from] StdError), + #[error(transparent)] Cw20Error(#[from] cw20_base::ContractError), + #[error(transparent)] Ownership(#[from] cw_ownable::OwnershipError), + #[error(transparent)] - HookError(#[from] cw_controllers::HookError), + HookError(#[from] cw_hooks::HookError), + + #[error(transparent)] + UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + + #[error("can not migrate. current version is up to date")] + AlreadyMigrated {}, - #[error("Provided cw20 errored in response to TokenInfo query")] - InvalidCw20 {}, - #[error("Nothing to claim")] - NothingToClaim {}, - #[error("Nothing to unstake")] - NothingStaked {}, #[error("Unstaking this amount violates the invariant: (cw20 total_supply <= 2^128)")] Cw20InvaraintViolation {}, + #[error("Can not unstake more than has been staked")] ImpossibleUnstake {}, + + #[error("Provided cw20 errored in response to TokenInfo query")] + InvalidCw20 {}, + #[error("Invalid token")] InvalidToken { received: Addr, expected: Addr }, + + #[error("Nothing to claim")] + NothingToClaim {}, + + #[error("Nothing to unstake")] + NothingStaked {}, + #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] TooManyClaims {}, - #[error("Invalid unstaking duration, unstaking duration cannot be 0")] - InvalidUnstakingDuration {}, - #[error("can not migrate. current version is up to date")] - AlreadyMigrated {}, } diff --git a/contracts/staking/cw20-stake/src/hooks.rs b/contracts/staking/cw20-stake/src/hooks.rs deleted file mode 100644 index 5867d8ff4..000000000 --- a/contracts/staking/cw20-stake/src/hooks.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::state::HOOKS; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; - -// This is just a helper to properly serialize the above message -#[cw_serde] -pub enum StakeChangedHookMsg { - Stake { addr: Addr, amount: Uint128 }, - Unstake { addr: Addr, amount: Uint128 }, -} - -pub fn stake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Stake { addr, amount }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -pub fn unstake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Unstake { addr, amount }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum StakeChangedExecuteMsg { - StakeChangeHook(StakeChangedHookMsg), -} diff --git a/contracts/staking/cw20-stake/src/lib.rs b/contracts/staking/cw20-stake/src/lib.rs index 6688af2cd..e8bfc90e0 100644 --- a/contracts/staking/cw20-stake/src/lib.rs +++ b/contracts/staking/cw20-stake/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; mod error; -pub mod hooks; mod math; pub mod msg; pub mod state; diff --git a/contracts/staking/cw20-stake/src/state.rs b/contracts/staking/cw20-stake/src/state.rs index 2ebb1c887..5323d9008 100644 --- a/contracts/staking/cw20-stake/src/state.rs +++ b/contracts/staking/cw20-stake/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_controllers::Claims; -use cw_controllers::Hooks; +use cw_hooks::Hooks; use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; use cw_utils::Duration; diff --git a/contracts/staking/cw20-stake/src/tests.rs b/contracts/staking/cw20-stake/src/tests.rs index 81ae03b1a..1838265a5 100644 --- a/contracts/staking/cw20-stake/src/tests.rs +++ b/contracts/staking/cw20-stake/src/tests.rs @@ -1,3 +1,13 @@ +use anyhow::Result as AnyResult; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use cosmwasm_std::{to_binary, Addr, Empty, MessageInfo, Uint128, WasmMsg}; +use cw20::Cw20Coin; +use cw_controllers::{Claim, ClaimsResponse}; +use cw_multi_test::{next_block, App, AppResponse, Contract, ContractWrapper, Executor}; +use cw_ownable::{Action, Ownership, OwnershipError}; +use cw_utils::Duration; +use cw_utils::Expiration::AtHeight; +use dao_voting::duration::UnstakingDurationError; use std::borrow::BorrowMut; use crate::msg::{ @@ -7,20 +17,9 @@ use crate::msg::{ }; use crate::state::{Config, MAX_CLAIMS}; use crate::ContractError; -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{to_binary, Addr, Empty, MessageInfo, Uint128, WasmMsg}; -use cw20::Cw20Coin; -use cw_ownable::{Action, Ownership, OwnershipError}; -use cw_utils::Duration; - -use cw_multi_test::{next_block, App, AppResponse, Contract, ContractWrapper, Executor}; -use anyhow::Result as AnyResult; use cw20_stake_v1 as v1; -use cw_controllers::{Claim, ClaimsResponse}; -use cw_utils::Expiration::AtHeight; - const ADDR1: &str = "addr0001"; const ADDR2: &str = "addr0002"; const ADDR3: &str = "addr0003"; @@ -273,14 +272,20 @@ fn test_update_config() { .unwrap_err() .downcast() .unwrap(); - assert_eq!(err, ContractError::InvalidUnstakingDuration {}); + assert_eq!( + err, + ContractError::UnstakingDurationError(UnstakingDurationError::InvalidUnstakingDuration {}) + ); let info = mock_info(OWNER, &[]); let err: ContractError = update_config(&mut app, &staking_addr, info, Some(Duration::Time(0))) .unwrap_err() .downcast() .unwrap(); - assert_eq!(err, ContractError::InvalidUnstakingDuration {}); + assert_eq!( + err, + ContractError::UnstakingDurationError(UnstakingDurationError::InvalidUnstakingDuration {}) + ); } #[test] diff --git a/contracts/voting/dao-voting-cw20-staked/src/contract.rs b/contracts/voting/dao-voting-cw20-staked/src/contract.rs index 4033d5247..28ed26836 100644 --- a/contracts/voting/dao-voting-cw20-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw20-staked/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ to_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw20::{Cw20Coin, TokenInfoResponse}; use cw_utils::parse_reply_instantiate_data; use dao_interface::voting::IsActiveResponse; @@ -372,9 +372,15 @@ pub fn query_active_threshold(deps: Deps) -> StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version.as_str() < CONTRACT_VERSION { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/voting/dao-voting-cw20-staked/src/tests.rs b/contracts/voting/dao-voting-cw20-staked/src/tests.rs index b6d664604..bab1b65a4 100644 --- a/contracts/voting/dao-voting-cw20-staked/src/tests.rs +++ b/contracts/voting/dao-voting-cw20-staked/src/tests.rs @@ -1391,7 +1391,7 @@ fn test_migrate() { #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index efa311007..cf6b1e249 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg, Uint128, WasmMsg, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw4::{MemberListResponse, MemberResponse, TotalWeightResponse}; use cw_utils::parse_reply_instantiate_data; @@ -61,12 +61,14 @@ pub fn instantiate( return Err(ContractError::ZeroTotalWeight {}); } - // We need to set ourself as the CW4 admin it is then transferred to the DAO in the reply + // Instantiate group contract, set DAO as admin. + // Voting module contracts are instantiated by the main dao-dao-core + // contract, so the Admin is set to info.sender. let msg = WasmMsg::Instantiate { admin: Some(info.sender.to_string()), code_id: cw4_group_code_id, msg: to_binary(&cw4_group::msg::InstantiateMsg { - admin: Some(env.contract.address.to_string()), + admin: Some(info.sender.to_string()), members: initial_members, })?, funds: vec![], @@ -99,7 +101,7 @@ pub fn instantiate( Ok(Response::new() .add_attribute("action", "instantiate") - .add_attribute("group_contract", "address")) + .add_attribute("group_contract", group_contract.to_string())) } } } @@ -168,9 +170,15 @@ pub fn query_info(deps: Deps) -> StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version.as_str() < CONTRACT_VERSION { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -185,19 +193,8 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Err(ContractError::GroupContractInstantiateError {}), } diff --git a/contracts/voting/dao-voting-cw4/src/tests.rs b/contracts/voting/dao-voting-cw4/src/tests.rs index 386d8f0e6..69ca443ef 100644 --- a/contracts/voting/dao-voting-cw4/src/tests.rs +++ b/contracts/voting/dao-voting-cw4/src/tests.rs @@ -735,7 +735,7 @@ fn test_zero_voting_power() { #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-cw721-roles/src/contract.rs b/contracts/voting/dao-voting-cw721-roles/src/contract.rs index 62d6ba185..9d0f8852d 100644 --- a/contracts/voting/dao-voting-cw721-roles/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-roles/src/contract.rs @@ -14,7 +14,7 @@ use cw_utils::parse_reply_instantiate_data; use dao_cw721_extensions::roles::{ExecuteExt, MetadataExt, QueryExt}; use crate::msg::{ExecuteMsg, InstantiateMsg, NftContract, QueryMsg}; -use crate::state::{Config, CONFIG, DAO, INITITIAL_NFTS}; +use crate::state::{Config, CONFIG, DAO, INITIAL_NFTS}; use crate::ContractError; pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-cw721-roles"; @@ -57,7 +57,7 @@ pub fn instantiate( } // Save initial NFTs for use in reply - INITITIAL_NFTS.save(deps.storage, &initial_nfts)?; + INITIAL_NFTS.save(deps.storage, &initial_nfts)?; // Create instantiate submessage for NFT roles contract let msg = SubMsg::reply_on_success( @@ -177,7 +177,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result = initial_nfts @@ -202,7 +202,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result>(); // Clear space - INITITIAL_NFTS.remove(deps.storage); + INITIAL_NFTS.remove(deps.storage); // Update minter message let update_minter_msg = WasmMsg::Execute { diff --git a/contracts/voting/dao-voting-cw721-roles/src/state.rs b/contracts/voting/dao-voting-cw721-roles/src/state.rs index de55f8d3d..fb6e98779 100644 --- a/contracts/voting/dao-voting-cw721-roles/src/state.rs +++ b/contracts/voting/dao-voting-cw721-roles/src/state.rs @@ -13,4 +13,4 @@ pub const CONFIG: Item = Item::new("config"); pub const DAO: Item = Item::new("dao"); // Holds initial NFTs messages during instantiation. -pub const INITITIAL_NFTS: Item> = Item::new("initial_nfts"); +pub const INITIAL_NFTS: Item> = Item::new("initial_nfts"); diff --git a/contracts/voting/dao-voting-cw721-staked/Cargo.toml b/contracts/voting/dao-voting-cw721-staked/Cargo.toml index e5535c673..a52107cca 100644 --- a/contracts/voting/dao-voting-cw721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-staked/Cargo.toml @@ -19,14 +19,16 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } -dao-dao-macros = { workspace = true } -dao-interface = { workspace = true } +cw-hooks = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } cw721-controllers = { workspace = true } cw-paginate-storage = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } +dao-dao-macros = { workspace = true } +dao-hooks = { workspace = true } +dao-interface = { workspace = true } dao-voting = { workspace = true } sg-std = { workspace = true } sg721 = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-staked/README.md b/contracts/voting/dao-voting-cw721-staked/README.md index 0e67eba17..ac91953ae 100644 --- a/contracts/voting/dao-voting-cw721-staked/README.md +++ b/contracts/voting/dao-voting-cw721-staked/README.md @@ -7,4 +7,4 @@ This is a basic implementation of an NFT staking contract. Staked tokens can be unbonded with a configurable unbonding period. Staked balances can be queried at any arbitrary height by external contracts. This contract implements the interface needed to be a DAO DAO [voting module](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design#the-voting-module). -`dao-voting-cw721-staked` can be used with existing NFT collections or create new `cw721` or `sg721` collections upon instantiation (with the DAO as admin and `minter`). +`dao-voting-cw721-staked` can be used with existing NFT collections or to create a new `cw721` or `sg721` collections upon instantiation (with the DAO as admin and `minter` / `creator`). diff --git a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json index 776398bd6..aabc38089 100644 --- a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json +++ b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json @@ -29,17 +29,6 @@ } ] }, - "owner": { - "description": "May change unstaking duration and add hooks.", - "anyOf": [ - { - "$ref": "#/definitions/Admin" - }, - { - "type": "null" - } - ] - }, "unstaking_duration": { "description": "Amount of time between unstaking and tokens being avaliable. To unstake with no delay, leave as `None`.", "anyOf": [ @@ -103,47 +92,6 @@ } ] }, - "Admin": { - "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", - "oneOf": [ - { - "description": "Set the admin to a specified address.", - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets the admin as the core module address.", - "type": "object", - "required": [ - "core_module" - ], - "properties": { - "core_module": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, "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. See also .", "type": "string" @@ -332,12 +280,6 @@ "type": "null" } ] - }, - "owner": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false @@ -820,16 +762,6 @@ "nft_address": { "$ref": "#/definitions/Addr" }, - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, "unstaking_duration": { "anyOf": [ { diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 613edc917..82e6ac050 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -1,10 +1,3 @@ -use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; -use crate::state::{ - register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, - INITITIAL_NFTS, MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, -}; -use crate::ContractError; use cosmwasm_schema::cw_serde; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -12,18 +5,26 @@ use cosmwasm_std::{ from_binary, to_binary, Addr, Binary, CosmosMsg, Decimal, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, StdError, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; -use cw2::set_contract_version; -use cw721::{Cw721ReceiveMsg, NumTokensResponse}; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; +use cw721::{Cw721QueryMsg, Cw721ReceiveMsg, NumTokensResponse}; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; -use dao_interface::state::Admin; +use dao_hooks::nft_stake::{stake_nft_hook_msgs, unstake_nft_hook_msgs}; use dao_interface::voting::IsActiveResponse; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; +use crate::state::{ + register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, + INITIAL_NFTS, MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, +}; +use crate::ContractError; + pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-cw721-staked"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const INSTANTIATE_NFT_CONTRACT_REPLY_ID: u64 = 0; +const VALIDATE_ABSOLUTE_COUNT_FOR_NEW_NFT_CONTRACTS: u64 = 1; // We multiply by this when calculating needed power for being active // when using active threshold with percent @@ -36,10 +37,19 @@ pub enum NftInstantiateMsg { } impl NftInstantiateMsg { - fn update_minter(&mut self, minter: &str) { + fn modify_instantiate_msg(&mut self, minter: &str, dao: &str) { match self { + // Update minter for cw721 NFTs NftInstantiateMsg::Cw721(msg) => msg.minter = minter.to_string(), - NftInstantiateMsg::Sg721(msg) => msg.minter = minter.to_string(), + NftInstantiateMsg::Sg721(msg) => { + // Update minter and collection creator for sg721 NFTs + // The collection creator is the only one able to call certain methods + // in sg721 contracts + msg.minter = minter.to_string(); + // This should be the DAO, which will be able to control metadata about + // the collection as well as royalties + msg.collection_info.creator = dao.to_string(); + } } } @@ -76,15 +86,6 @@ pub fn instantiate( DAO.save(deps.storage, &info.sender)?; - let owner = msg - .owner - .as_ref() - .map(|owner| match owner { - Admin::Address { addr } => deps.api.addr_validate(addr), - Admin::CoreModule {} => Ok(info.sender.clone()), - }) - .transpose()?; - if let Some(active_threshold) = msg.active_threshold.as_ref() { match active_threshold { ActiveThreshold::Percentage { percent } => { @@ -93,9 +94,20 @@ pub fn instantiate( } } ActiveThreshold::AbsoluteCount { count } => { + // Check Absolute count is not zero if count.is_zero() { return Err(ContractError::ZeroActiveCount {}); } + + // Check Absolute count is less than the supply of NFTs for existing NFT contracts + if let NftContract::Existing { ref address } = msg.nft_contract { + let nft_supply: NumTokensResponse = deps + .querier + .query_wasm_smart(address, &Cw721QueryMsg::NumTokens {})?; + if count > &Uint128::new(nft_supply.count.into()) { + return Err(ContractError::InvalidActiveCount {}); + } + } } } ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; @@ -106,7 +118,6 @@ pub fn instantiate( match msg.nft_contract { NftContract::Existing { address } => { let config = Config { - owner: owner.clone(), nft_address: deps.api.addr_validate(&address)?, unstaking_duration: msg.unstaking_duration, }; @@ -114,13 +125,7 @@ pub fn instantiate( Ok(Response::default() .add_attribute("method", "instantiate") - .add_attribute("nft_contract", address) - .add_attribute( - "owner", - owner - .map(|a| a.into_string()) - .unwrap_or_else(|| "None".to_string()), - )) + .add_attribute("nft_contract", address)) } NftContract::New { code_id, @@ -130,8 +135,15 @@ pub fn instantiate( } => { // Deserialize the binary msg into either cw721 or sg721 let mut instantiate_msg = try_deserialize_nft_instantiate_msg(instantiate_msg)?; - // Update the minter to be this contract - instantiate_msg.update_minter(env.contract.address.as_str()); + + // Modify the InstantiateMsg such that the minter is now this contract. + // We will update ownership of the NFT contract to be the DAO in the submessage reply. + // + // NOTE: sg721 also has a creator that is set in the `collection_info` field, + // we override this with the address of the DAO (the sender of this message). + // In sg721 the `creator` address controls metadata and royalties. + instantiate_msg + .modify_instantiate_msg(env.contract.address.as_str(), info.sender.as_str()); // Check there is at least one NFT to initialize if initial_nfts.is_empty() { @@ -140,14 +152,13 @@ pub fn instantiate( // Save config with empty nft_address let config = Config { - owner: owner.clone(), nft_address: Addr::unchecked(""), unstaking_duration: msg.unstaking_duration, }; CONFIG.save(deps.storage, &config)?; // Save initial NFTs for use in reply - INITITIAL_NFTS.save(deps.storage, &initial_nfts)?; + INITIAL_NFTS.save(deps.storage, &initial_nfts)?; // Create instantiate submessage for NFT contract let instantiate_msg = SubMsg::reply_on_success( @@ -162,13 +173,8 @@ pub fn instantiate( ); Ok(Response::default() - .add_submessage(instantiate_msg) - .add_attribute( - "owner", - owner - .map(|a| a.into_string()) - .unwrap_or_else(|| "None".to_string()), - )) + .add_attribute("method", "instantiate") + .add_submessage(instantiate_msg)) } } } @@ -184,9 +190,7 @@ pub fn execute( ExecuteMsg::ReceiveNft(msg) => execute_stake(deps, env, info, msg), ExecuteMsg::Unstake { token_ids } => execute_unstake(deps, env, info, token_ids), ExecuteMsg::ClaimNfts {} => execute_claim_nfts(deps, env, info), - ExecuteMsg::UpdateConfig { owner, duration } => { - execute_update_config(info, deps, owner, duration) - } + ExecuteMsg::UpdateConfig { duration } => execute_update_config(info, deps, duration), ExecuteMsg::AddHook { addr } => execute_add_hook(deps, info, addr), ExecuteMsg::RemoveHook { addr } => execute_remove_hook(deps, info, addr), ExecuteMsg::UpdateActiveThreshold { new_threshold } => { @@ -210,7 +214,12 @@ pub fn execute_stake( } let staker = deps.api.addr_validate(&wrapper.sender)?; register_staked_nft(deps.storage, env.block.height, &staker, &wrapper.token_id)?; - let hook_msgs = stake_hook_msgs(deps.storage, staker.clone(), wrapper.token_id.clone())?; + let hook_msgs = stake_nft_hook_msgs( + HOOKS, + deps.storage, + staker.clone(), + wrapper.token_id.clone(), + )?; Ok(Response::default() .add_submessages(hook_msgs) .add_attribute("action", "stake") @@ -261,7 +270,8 @@ pub fn execute_unstake( // so if we reach this point in execution, we may safely create // claims. - let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), token_ids.clone())?; + let hook_msgs = + unstake_nft_hook_msgs(HOOKS, deps.storage, info.sender.clone(), token_ids.clone())?; let config = CONFIG.load(deps.storage)?; match config.unstaking_duration { @@ -350,32 +360,21 @@ pub fn execute_claim_nfts( pub fn execute_update_config( info: MessageInfo, deps: DepsMut, - new_owner: Option, duration: Option, ) -> Result { let mut config: Config = CONFIG.load(deps.storage)?; + let dao = DAO.load(deps.storage)?; - if config.owner.map_or(true, |owner| owner != info.sender) { - return Err(ContractError::NotOwner {}); + // Only the DAO can update the config. + if info.sender != dao { + return Err(ContractError::Unauthorized {}); } - let new_owner = new_owner - .map(|new_owner| deps.api.addr_validate(&new_owner)) - .transpose()?; - - config.owner = new_owner; config.unstaking_duration = duration; CONFIG.save(deps.storage, &config)?; Ok(Response::default() .add_attribute("action", "update_config") - .add_attribute( - "owner", - config - .owner - .map(|a| a.to_string()) - .unwrap_or_else(|| "none".to_string()), - ) .add_attribute( "unstaking_duration", config @@ -390,9 +389,11 @@ pub fn execute_add_hook( info: MessageInfo, addr: String, ) -> Result { - let config: Config = CONFIG.load(deps.storage)?; - if config.owner.map_or(true, |owner| owner != info.sender) { - return Err(ContractError::NotOwner {}); + let dao = DAO.load(deps.storage)?; + + // Only the DAO can add a hook + if info.sender != dao { + return Err(ContractError::Unauthorized {}); } let hook = deps.api.addr_validate(&addr)?; @@ -408,9 +409,11 @@ pub fn execute_remove_hook( info: MessageInfo, addr: String, ) -> Result { - let config: Config = CONFIG.load(deps.storage)?; - if config.owner.map_or(true, |owner| owner != info.sender) { - return Err(ContractError::NotOwner {}); + let dao = DAO.load(deps.storage)?; + + // Only the DAO can remove a hook + if info.sender != dao { + return Err(ContractError::Unauthorized {}); } let hook = deps.api.addr_validate(&addr)?; @@ -620,9 +623,15 @@ pub fn query_staked_nfts( #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version.as_str() < CONTRACT_VERSION { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -640,10 +649,10 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result = initial_nfts + let mut submessages: Vec = initial_nfts .iter() .flat_map(|nft| -> Result { Ok(SubMsg::new(WasmMsg::Execute { @@ -655,31 +664,55 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result>(); // Clear space - INITITIAL_NFTS.remove(deps.storage); - - // Update minter message - let update_minter_msg = WasmMsg::Execute { - contract_addr: nft_contract.clone(), - msg: to_binary( - &cw721_base::msg::ExecuteMsg::::UpdateOwnership( - cw721_base::Action::TransferOwnership { - new_owner: dao.to_string(), - expiry: None, - }, - ), - )?, - funds: vec![], - }; + INITIAL_NFTS.remove(deps.storage); + + // The last submessage updates the minter / owner of the NFT contract, + // and triggers a reply. The reply is used for validation after setup. + submessages.push(SubMsg::reply_on_success( + WasmMsg::Execute { + contract_addr: nft_contract.clone(), + msg: to_binary( + &cw721_base::msg::ExecuteMsg::::UpdateOwnership( + cw721_base::Action::TransferOwnership { + new_owner: dao.to_string(), + expiry: None, + }, + ), + )?, + funds: vec![], + }, + VALIDATE_ABSOLUTE_COUNT_FOR_NEW_NFT_CONTRACTS, + )); Ok(Response::default() - .add_attribute("method", "instantiate") .add_attribute("nft_contract", nft_contract) - .add_message(update_minter_msg) - .add_submessages(mint_submessages)) + .add_submessages(submessages)) } Err(_) => Err(ContractError::NftInstantiateError {}), } } + VALIDATE_ABSOLUTE_COUNT_FOR_NEW_NFT_CONTRACTS => { + // Check that absolute count is not greater than supply + // NOTE: we have to check this in a reply as it is potentially possible + // to include non-mint messages in `initial_nfts`. + if let Some(ActiveThreshold::AbsoluteCount { count }) = + ACTIVE_THRESHOLD.may_load(deps.storage)? + { + // Load config for nft contract address + let collection_addr = CONFIG.load(deps.storage)?.nft_address; + + // Query the total supply of the NFT contract + let supply: NumTokensResponse = deps + .querier + .query_wasm_smart(collection_addr, &Cw721QueryMsg::NumTokens {})?; + + // Chec the count is not greater than supply + if count > Uint128::new(supply.count.into()) { + return Err(ContractError::InvalidActiveCount {}); + } + } + Ok(Response::new()) + } _ => Err(ContractError::UnknownReplyId { id: msg.id }), } } diff --git a/contracts/voting/dao-voting-cw721-staked/src/error.rs b/contracts/voting/dao-voting-cw721-staked/src/error.rs index 10fb9a2db..15e2cecea 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/error.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/error.rs @@ -10,7 +10,10 @@ pub enum ContractError { AlreadyStaked {}, #[error(transparent)] - HookError(#[from] cw_controllers::HookError), + HookError(#[from] cw_hooks::HookError), + + #[error("Active threshold count is greater than supply")] + InvalidActiveCount {}, #[error("Active threshold percentage must be greater than 0 and less than 1")] InvalidActivePercentage {}, diff --git a/contracts/voting/dao-voting-cw721-staked/src/hooks.rs b/contracts/voting/dao-voting-cw721-staked/src/hooks.rs deleted file mode 100644 index b8ec5f175..000000000 --- a/contracts/voting/dao-voting-cw721-staked/src/hooks.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::state::HOOKS; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, WasmMsg}; - -// This is just a helper to properly serialize the above message -#[cw_serde] -pub enum StakeChangedHookMsg { - Stake { addr: Addr, token_id: String }, - Unstake { addr: Addr, token_ids: Vec }, -} - -pub fn stake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - token_id: String, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Stake { addr, token_id }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.into_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -pub fn unstake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - token_ids: Vec, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Unstake { addr, token_ids }, - ))?; - - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.into_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum StakeChangedExecuteMsg { - StakeChangeHook(StakeChangedHookMsg), -} - -#[cfg(test)] -mod tests { - use crate::{ - contract::execute, - state::{Config, CONFIG}, - }; - - use super::*; - - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - - #[test] - fn test_hooks() { - let mut deps = mock_dependencies(); - - let messages = stake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - "ekez-token".to_string(), - ) - .unwrap(); - assert_eq!(messages.len(), 0); - - let messages = unstake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - vec!["ekez-token".to_string()], - ) - .unwrap(); - assert_eq!(messages.len(), 0); - - // Save a config for the execute messages we're testing. - CONFIG - .save( - deps.as_mut().storage, - &Config { - owner: Some(Addr::unchecked("ekez")), - nft_address: Addr::unchecked("ekez-token"), - unstaking_duration: None, - }, - ) - .unwrap(); - - let env = mock_env(); - let info = mock_info("ekez", &[]); - - execute( - deps.as_mut(), - env, - info, - crate::msg::ExecuteMsg::AddHook { - addr: "ekez".to_string(), - }, - ) - .unwrap(); - - let messages = stake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - "ekez-token".to_string(), - ) - .unwrap(); - assert_eq!(messages.len(), 1); - - let messages = unstake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - vec!["ekez-token".to_string()], - ) - .unwrap(); - assert_eq!(messages.len(), 1); - - let env = mock_env(); - let info = mock_info("ekez", &[]); - - execute( - deps.as_mut(), - env, - info, - crate::msg::ExecuteMsg::RemoveHook { - addr: "ekez".to_string(), - }, - ) - .unwrap(); - - let messages = stake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - "ekez-token".to_string(), - ) - .unwrap(); - assert_eq!(messages.len(), 0); - - let messages = unstake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - vec!["ekez-token".to_string()], - ) - .unwrap(); - assert_eq!(messages.len(), 0); - } -} diff --git a/contracts/voting/dao-voting-cw721-staked/src/lib.rs b/contracts/voting/dao-voting-cw721-staked/src/lib.rs index 51ae5c619..d4a73c5be 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/lib.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; mod error; -pub mod hooks; pub mod msg; pub mod state; diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 3ce904c8a..f47d82533 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -3,7 +3,6 @@ use cosmwasm_std::Binary; use cw721::Cw721ReceiveMsg; use cw_utils::Duration; use dao_dao_macros::{active_query, voting_module_query}; -use dao_interface::state::Admin; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; #[cw_serde] @@ -28,8 +27,6 @@ pub enum NftContract { #[cw_serde] pub struct InstantiateMsg { - /// May change unstaking duration and add hooks. - pub owner: Option, /// Address of the cw721 NFT contract that may be staked. pub nft_contract: NftContract, /// Amount of time between unstaking and tokens being @@ -54,7 +51,6 @@ pub enum ExecuteMsg { }, ClaimNfts {}, UpdateConfig { - owner: Option, duration: Option, }, AddHook { diff --git a/contracts/voting/dao-voting-cw721-staked/src/state.rs b/contracts/voting/dao-voting-cw721-staked/src/state.rs index b7a3c00cd..0d8e8b62f 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/state.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Binary, Empty, StdError, StdResult, Storage, Uint128}; use cw721_controllers::NftClaims; -use cw_controllers::Hooks; +use cw_hooks::Hooks; use cw_storage_plus::{Item, Map, SnapshotItem, SnapshotMap, Strategy}; use cw_utils::Duration; use dao_voting::threshold::ActiveThreshold; @@ -10,7 +10,6 @@ use crate::ContractError; #[cw_serde] pub struct Config { - pub owner: Option, pub nft_address: Addr, pub unstaking_duration: Option, } @@ -20,7 +19,7 @@ pub const CONFIG: Item = Item::new("config"); pub const DAO: Item = Item::new("dao"); // Holds initial NFTs messages during instantiation. -pub const INITITIAL_NFTS: Item> = Item::new("initial_nfts"); +pub const INITIAL_NFTS: Item> = Item::new("initial_nfts"); /// The set of NFTs currently staked by each address. The existence of /// an `(address, token_id)` pair implies that `address` has staked diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs index f8479c1c3..e5c587564 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs @@ -27,7 +27,7 @@ fn test_circular_stake() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -72,7 +72,7 @@ fn test_immediate_unstake() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -94,7 +94,7 @@ fn test_immediate_unstake() -> anyhow::Result<()> { fn test_stake_wrong_nft() -> anyhow::Result<()> { let CommonTest { mut app, module, .. - } = setup_test(None, None); + } = setup_test(None); let other_nft = instantiate_cw721_base(&mut app, CREATOR_ADDR, CREATOR_ADDR); let res = mint_and_stake_nft(&mut app, &other_nft, &module, CREATOR_ADDR, "1"); @@ -115,7 +115,7 @@ fn test_query_the_future() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; @@ -154,7 +154,7 @@ fn test_bypass_max_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); let mut to_stake = vec![]; for i in 1..(MAX_CLAIMS + 10) { let i_str = &i.to_string(); diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs index de31d35e8..dd5b60877 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs @@ -96,16 +96,12 @@ pub fn update_config( app: &mut App, module: &Addr, sender: &str, - owner: Option<&str>, duration: Option, ) -> AnyResult { app.execute_contract( addr!(sender), module.clone(), - &ExecuteMsg::UpdateConfig { - owner: owner.map(str::to_string), - duration, - }, + &ExecuteMsg::UpdateConfig { duration }, &[], ) } diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs new file mode 100644 index 000000000..4bcac6fbc --- /dev/null +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs @@ -0,0 +1,110 @@ +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + Addr, +}; +use dao_hooks::nft_stake::{stake_nft_hook_msgs, unstake_nft_hook_msgs}; + +use crate::{ + contract::execute, + state::{Config, CONFIG, DAO, HOOKS}, +}; + +#[test] +fn test_hooks() { + let mut deps = mock_dependencies(); + + let messages = stake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + "ekez-token".to_string(), + ) + .unwrap(); + assert_eq!(messages.len(), 0); + + let messages = unstake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + vec!["ekez-token".to_string()], + ) + .unwrap(); + assert_eq!(messages.len(), 0); + + // Save a DAO address for the execute messages we're testing. + DAO.save(deps.as_mut().storage, &Addr::unchecked("ekez")) + .unwrap(); + + // Save a config for the execute messages we're testing. + CONFIG + .save( + deps.as_mut().storage, + &Config { + nft_address: Addr::unchecked("ekez-token"), + unstaking_duration: None, + }, + ) + .unwrap(); + + let env = mock_env(); + let info = mock_info("ekez", &[]); + + execute( + deps.as_mut(), + env, + info, + crate::msg::ExecuteMsg::AddHook { + addr: "ekez".to_string(), + }, + ) + .unwrap(); + + let messages = stake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + "ekez-token".to_string(), + ) + .unwrap(); + assert_eq!(messages.len(), 1); + + let messages = unstake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + vec!["ekez-token".to_string()], + ) + .unwrap(); + assert_eq!(messages.len(), 1); + + let env = mock_env(); + let info = mock_info("ekez", &[]); + + execute( + deps.as_mut(), + env, + info, + crate::msg::ExecuteMsg::RemoveHook { + addr: "ekez".to_string(), + }, + ) + .unwrap(); + + let messages = stake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + "ekez-token".to_string(), + ) + .unwrap(); + assert_eq!(messages.len(), 0); + + let messages = unstake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + vec!["ekez-token".to_string()], + ) + .unwrap(); + assert_eq!(messages.len(), 0); +} diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs index d28f52201..ea43bc797 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs @@ -1,5 +1,6 @@ mod adversarial; mod execute; +mod hooks; mod instantiate; mod queries; mod tests; @@ -7,8 +8,6 @@ mod tests; use cosmwasm_std::Addr; use cw_multi_test::{App, Executor}; use cw_utils::Duration; - -use dao_interface::state::Admin; use dao_testing::contracts::voting_cw721_staked_contract; use crate::msg::{InstantiateMsg, NftContract}; @@ -24,7 +23,7 @@ pub(crate) struct CommonTest { nft: Addr, } -pub(crate) fn setup_test(owner: Option, unstaking_duration: Option) -> CommonTest { +pub(crate) fn setup_test(unstaking_duration: Option) -> CommonTest { let mut app = App::default(); let module_id = app.store_code(voting_cw721_staked_contract()); @@ -34,7 +33,6 @@ pub(crate) fn setup_test(owner: Option, unstaking_duration: Option anyhow::Result<()> { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -88,7 +87,7 @@ fn test_stake_tokens() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); let total_power = query_total_power(&app, &module, None)?; let voting_power = query_voting_power(&app, &module, CREATOR_ADDR, None)?; @@ -125,7 +124,7 @@ fn test_unstake_tokens_no_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); let friend = "friend"; @@ -183,7 +182,7 @@ fn test_update_config() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(Some(Admin::CoreModule {}), Some(Duration::Height(3))); + } = setup_test(Some(Duration::Height(3))); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -201,14 +200,8 @@ fn test_update_config() -> anyhow::Result<()> { } ); - // Make friend the new owner. - update_config( - &mut app, - &module, - CREATOR_ADDR, - Some("friend"), - Some(Duration::Time(1)), - )?; + // Update duration + update_config(&mut app, &module, CREATOR_ADDR, Some(Duration::Time(1)))?; // Existing claims should remain unchanged. let claims = query_claims(&app, &module, CREATOR_ADDR)?; @@ -256,39 +249,6 @@ fn test_update_config() -> anyhow::Result<()> { let claims = query_claims(&app, &module, CREATOR_ADDR)?; assert_eq!(claims, NftClaimsResponse { nft_claims: vec![] }); - // Creator can no longer do config updates. - let res = update_config( - &mut app, - &module, - CREATOR_ADDR, - Some("friend"), - Some(Duration::Time(1)), - ); - is_error!(res => "Only the owner of this contract my execute this message"); - - // Friend can still do config updates, and even remove themselves - // as the owner. - update_config(&mut app, &module, "friend", None, None)?; - let config = query_config(&app, &module)?; - assert_eq!( - config, - Config { - owner: None, - nft_address: nft, - unstaking_duration: None - } - ); - - // Friend has removed themselves. - let res = update_config( - &mut app, - &module, - "friend", - Some("friend"), - Some(Duration::Time(1)), - ); - is_error!(res => "Only the owner of this contract my execute this message"); - Ok(()) } @@ -301,7 +261,7 @@ fn test_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(Some(Admin::CoreModule {}), Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -344,7 +304,7 @@ fn test_max_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); for i in 0..MAX_CLAIMS { let i_str = &i.to_string(); @@ -366,7 +326,7 @@ fn test_list_staked_nfts() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(Some(Admin::CoreModule {}), Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -411,7 +371,7 @@ fn test_list_staked_nfts() -> anyhow::Result<()> { #[test] fn test_info_query_works() -> anyhow::Result<()> { - let CommonTest { app, module, .. } = setup_test(None, None); + let CommonTest { app, module, .. } = setup_test(None); let info = query_info(&app, &module)?; assert_eq!(info.info.version, env!("CARGO_PKG_VERSION").to_string()); Ok(()) @@ -424,12 +384,7 @@ fn test_add_remove_hooks() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test( - Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), - None, - ); + } = setup_test(None); add_hook(&mut app, &module, CREATOR_ADDR, "meow")?; remove_hook(&mut app, &module, CREATOR_ADDR, "meow")?; @@ -454,7 +409,7 @@ fn test_add_remove_hooks() -> anyhow::Result<()> { is_error!(res => "Given address not registered as a hook"); let res = add_hook(&mut app, &module, "ekez", "evil"); - is_error!(res => "Only the owner of this contract my execute this message"); + is_error!(res => "Unauthorized"); Ok(()) } @@ -470,9 +425,6 @@ fn test_instantiate_zero_active_threshold_count() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -502,6 +454,72 @@ fn test_instantiate_zero_active_threshold_count() { .unwrap(); } +#[test] +#[should_panic(expected = "Active threshold count is greater than supply")] +fn test_instantiate_invalid_active_threshold_count_new_nft() { + let mut app = App::default(); + let cw721_id = app.store_code(cw721_base_contract()); + let module_id = app.store_code(voting_cw721_staked_contract()); + + app.instantiate_contract( + module_id, + Addr::unchecked(CREATOR_ADDR), + &InstantiateMsg { + nft_contract: NftContract::New { + code_id: cw721_id, + label: "Test NFT".to_string(), + msg: to_binary(&Cw721InstantiateMsg { + name: "Test NFT".to_string(), + symbol: "TEST".to_string(), + minter: CREATOR_ADDR.to_string(), + }) + .unwrap(), + initial_nfts: vec![to_binary(&Cw721ExecuteMsg::::Mint { + owner: CREATOR_ADDR.to_string(), + token_uri: Some("https://example.com".to_string()), + token_id: "1".to_string(), + extension: Empty {}, + }) + .unwrap()], + }, + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(100), + }), + }, + &[], + "cw721_voting", + None, + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Active threshold count is greater than supply")] +fn test_instantiate_invalid_active_threshold_count_existing_nft() { + let mut app = App::default(); + let module_id = app.store_code(voting_cw721_staked_contract()); + let cw721_addr = instantiate_cw721_base(&mut app, CREATOR_ADDR, CREATOR_ADDR); + + app.instantiate_contract( + module_id, + Addr::unchecked(CREATOR_ADDR), + &InstantiateMsg { + nft_contract: NftContract::Existing { + address: cw721_addr.to_string(), + }, + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(100), + }), + }, + &[], + "cw721_voting", + None, + ) + .unwrap(); +} + #[test] fn test_active_threshold_absolute_count() { let mut app = App::default(); @@ -513,9 +531,6 @@ fn test_active_threshold_absolute_count() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -596,9 +611,6 @@ fn test_active_threshold_percent() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -660,9 +672,6 @@ fn test_active_threshold_percent_rounds_up() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -741,7 +750,6 @@ fn test_active_threshold_percent_rounds_up() { .wrap() .query_wasm_smart(voting_addr.clone(), &QueryMsg::IsActive {}) .unwrap(); - println!("{:?}", is_active); assert!(!is_active.active); // Stake 1 more token as creator, should now be active. @@ -766,9 +774,6 @@ fn test_update_active_threshold() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -843,9 +848,6 @@ fn test_active_threshold_percentage_gt_100() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -886,9 +888,6 @@ fn test_active_threshold_percentage_lte_0() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -929,9 +928,6 @@ fn test_invalid_instantiate_msg() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -971,9 +967,6 @@ fn test_no_initial_nfts_fails() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -1001,30 +994,30 @@ fn test_no_initial_nfts_fails() { ); } +// Setup Stargaze contracts for multi-test +fn sg721_base_contract() -> Box> { + let contract = ContractWrapper::new( + sg721_base::entry::execute, + sg721_base::entry::instantiate, + sg721_base::entry::query, + ); + Box::new(contract) +} + +// Stargze contracts need a custom message wrapper +fn voting_sg721_staked_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ) + .with_reply_empty(crate::contract::reply); + Box::new(contract) +} + // I can create new Stargaze NFT collection when creating a dao-voting-cw721-staked contract #[test] fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { - // Setup Stargaze contracts for multi-test - fn sg721_base_contract() -> Box> { - let contract = ContractWrapper::new( - sg721_base::entry::execute, - sg721_base::entry::instantiate, - sg721_base::entry::query, - ); - Box::new(contract) - } - - // Stargze contracts need a custom message wrapper - fn voting_sg721_staked_contract() -> Box> { - let contract = ContractWrapper::new_with_empty( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply_empty(crate::contract::reply); - Box::new(contract) - } - let mut app = StargazeApp::default(); let module_id = app.store_code(voting_sg721_staked_contract()); let sg721_id = app.store_code(sg721_base_contract()); @@ -1034,9 +1027,6 @@ fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: sg721_id, label: "Test NFT".to_string(), @@ -1077,7 +1067,7 @@ fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { // Check that the NFT contract was created let owner: OwnerOfResponse = app.wrap().query_wasm_smart( - sg721_addr, + sg721_addr.clone(), &cw721::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, @@ -1085,13 +1075,82 @@ fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { )?; assert_eq!(owner.owner, CREATOR_ADDR); + // Check that collection info creator is set to the DAO (in this case CREATOR_ADDR) + // Normally the DAO would instantiate this contract + let creator: CollectionInfoResponse = app + .wrap() + .query_wasm_smart(sg721_addr, &sg721_base::msg::QueryMsg::CollectionInfo {})?; + assert_eq!(creator.creator, CREATOR_ADDR.to_string()); + Ok(()) } +#[test] +#[should_panic(expected = "Active threshold count is greater than supply")] +fn test_instantiate_with_new_sg721_collection_abs_count_validation() { + let mut app = StargazeApp::default(); + let module_id = app.store_code(voting_sg721_staked_contract()); + let sg721_id = app.store_code(sg721_base_contract()); + + // Test edge case + app.instantiate_contract( + module_id, + Addr::unchecked("contract0"), + &InstantiateMsg { + nft_contract: NftContract::New { + code_id: sg721_id, + label: "Test NFT".to_string(), + msg: to_binary(&sg721::InstantiateMsg { + name: "Test NFT".to_string(), + symbol: "TEST".to_string(), + minter: "contract0".to_string(), + collection_info: CollectionInfo { + creator: "contract0".to_string(), + description: "Test NFT".to_string(), + image: "https://example.com/image.jpg".to_string(), + external_link: None, + explicit_content: None, + start_trading_time: None, + royalty_info: None, + }, + }) + .unwrap(), + initial_nfts: vec![ + to_binary(&sg721::ExecuteMsg::::Mint { + owner: "contract0".to_string(), + token_uri: Some("https://example.com".to_string()), + token_id: "1".to_string(), + extension: Empty {}, + }) + .unwrap(), + to_binary(&sg721::ExecuteMsg::::UpdateCollectionInfo { + collection_info: UpdateCollectionInfoMsg:: { + description: None, + image: None, + external_link: None, + explicit_content: None, + royalty_info: None, + }, + }) + .unwrap(), + ], + }, + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(2), + }), + }, + &[], + "cw721_voting", + None, + ) + .unwrap(); +} + #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-native-staked/Cargo.toml b/contracts/voting/dao-voting-native-staked/Cargo.toml deleted file mode 100644 index 01131e006..000000000 --- a/contracts/voting/dao-voting-native-staked/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "dao-voting-native-staked" -authors = ["Callum Anderson ", "Jake Hartnell "] -description = "A DAO DAO voting module based on staked native tokens. If your chain uses Token Factory, consider using dao-voting-token-factory-staked for additional functionality including creating new tokens." -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } -cosmwasm-schema = { workspace = true } -cosmwasm-storage = { workspace = true } -cw2 = { workspace = true } -cw-controllers = { workspace = true } -cw-hooks = { workspace = true } -cw-paginate-storage = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -dao-dao-macros = { workspace = true } -dao-interface = { workspace = true } -dao-voting = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -cw-multi-test = { workspace = true } -anyhow = { workspace = true } diff --git a/contracts/voting/dao-voting-native-staked/README.md b/contracts/voting/dao-voting-native-staked/README.md deleted file mode 100644 index d91c34d39..000000000 --- a/contracts/voting/dao-voting-native-staked/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# CW Native Staked Balance Voting - -[![dao-voting-native-staked on crates.io](https://img.shields.io/crates/v/dao-voting-native-staked.svg?logo=rust)](https://crates.io/crates/dao-voting-native-staked) -[![docs.rs](https://img.shields.io/docsrs/dao-voting-native-staked?logo=docsdotrs)](https://docs.rs/dao-voting-native-staked/latest/dao_voting_native_staked/) - -Simple native token voting contract which assumes the native denom -provided is not used for staking for securing the network e.g. IBC -denoms or secondary tokens (ION). Staked balances may be queried at an -arbitrary height. This contract implements the interface needed to be a DAO -DAO [voting -module](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design#the-voting-module). - -If your chain uses Token Factory, consider using `dao-voting-token-factory-staked` for additional functionality including creating new tokens. diff --git a/contracts/voting/dao-voting-native-staked/schema/dao-voting-native-staked.json b/contracts/voting/dao-voting-native-staked/schema/dao-voting-native-staked.json deleted file mode 100644 index ae2140695..000000000 --- a/contracts/voting/dao-voting-native-staked/schema/dao-voting-native-staked.json +++ /dev/null @@ -1,993 +0,0 @@ -{ - "contract_name": "dao-voting-native-staked", - "contract_version": "2.2.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "denom" - ], - "properties": { - "active_threshold": { - "description": "The number or percentage of tokens that must be staked for the DAO to be active", - "anyOf": [ - { - "$ref": "#/definitions/ActiveThreshold" - }, - { - "type": "null" - } - ] - }, - "denom": { - "description": "Token denom e.g. ujuno, or some ibc denom", - "type": "string" - }, - "unstaking_duration": { - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "ActiveThreshold": { - "description": "The threshold of tokens that must be staked in order for this voting module to be active. If this is not reached, this module will response to `is_active` queries with false and proposal modules which respect active thresholds will not allow the creation of proposals.", - "oneOf": [ - { - "description": "The absolute number of tokens that must be staked for the module to be active.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The percentage of tokens that must be staked for the module to be active. Computed as `staked / total_supply`.", - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Duration": { - "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", - "oneOf": [ - { - "type": "object", - "required": [ - "height" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "Time in seconds", - "type": "object", - "required": [ - "time" - ], - "properties": { - "time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Stakes tokens with the contract to get voting power in the DAO", - "type": "object", - "required": [ - "stake" - ], - "properties": { - "stake": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Unstakes tokens so that they begin unbonding", - "type": "object", - "required": [ - "unstake" - ], - "properties": { - "unstake": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates the contract configuration", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "duration": { - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Claims unstaked tokens that have completed the unbonding period", - "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets the active threshold to a new value. Only the instantiator of this contract (a DAO most likely) may call this method.", - "type": "object", - "required": [ - "update_active_threshold" - ], - "properties": { - "update_active_threshold": { - "type": "object", - "properties": { - "new_threshold": { - "anyOf": [ - { - "$ref": "#/definitions/ActiveThreshold" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Adds a hook that fires on staking / unstaking", - "type": "object", - "required": [ - "add_hook" - ], - "properties": { - "add_hook": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Removes a hook that fires on staking / unstaking", - "type": "object", - "required": [ - "remove_hook" - ], - "properties": { - "remove_hook": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "ActiveThreshold": { - "description": "The threshold of tokens that must be staked in order for this voting module to be active. If this is not reached, this module will response to `is_active` queries with false and proposal modules which respect active thresholds will not allow the creation of proposals.", - "oneOf": [ - { - "description": "The absolute number of tokens that must be staked for the module to be active.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The percentage of tokens that must be staked for the module to be active. Computed as `staked / total_supply`.", - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Duration": { - "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", - "oneOf": [ - { - "type": "object", - "required": [ - "height" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "Time in seconds", - "type": "object", - "required": [ - "time" - ], - "properties": { - "time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "get_config" - ], - "properties": { - "get_config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claims" - ], - "properties": { - "claims": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_denom" - ], - "properties": { - "get_denom": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "list_stakers" - ], - "properties": { - "list_stakers": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "active_threshold" - ], - "properties": { - "active_threshold": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_hooks" - ], - "properties": { - "get_hooks": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the voting power for an address at a given height.", - "type": "object", - "required": [ - "voting_power_at_height" - ], - "properties": { - "voting_power_at_height": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - }, - "height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the total voting power at a given block heigh.", - "type": "object", - "required": [ - "total_power_at_height" - ], - "properties": { - "total_power_at_height": { - "type": "object", - "properties": { - "height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the address of the DAO this module belongs to.", - "type": "object", - "required": [ - "dao" - ], - "properties": { - "dao": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns contract version info.", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "is_active" - ], - "properties": { - "is_active": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "type": "object", - "additionalProperties": false - }, - "sudo": null, - "responses": { - "active_threshold": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ActiveThresholdResponse", - "type": "object", - "properties": { - "active_threshold": { - "anyOf": [ - { - "$ref": "#/definitions/ActiveThreshold" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "ActiveThreshold": { - "description": "The threshold of tokens that must be staked in order for this voting module to be active. If this is not reached, this module will response to `is_active` queries with false and proposal modules which respect active thresholds will not allow the creation of proposals.", - "oneOf": [ - { - "description": "The absolute number of tokens that must be staked for the module to be active.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The percentage of tokens that must be staked for the module to be active. Computed as `staked / total_supply`.", - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "claims": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ClaimsResponse", - "type": "object", - "required": [ - "claims" - ], - "properties": { - "claims": { - "type": "array", - "items": { - "$ref": "#/definitions/Claim" - } - } - }, - "additionalProperties": false, - "definitions": { - "Claim": { - "type": "object", - "required": [ - "amount", - "release_at" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "release_at": { - "$ref": "#/definitions/Expiration" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "dao": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "get_config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - }, - "unstaking_duration": { - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Duration": { - "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", - "oneOf": [ - { - "type": "object", - "required": [ - "height" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "Time in seconds", - "type": "object", - "required": [ - "time" - ], - "properties": { - "time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - } - } - }, - "get_denom": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "DenomResponse", - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - }, - "get_hooks": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "GetHooksResponse", - "type": "object", - "required": [ - "hooks" - ], - "properties": { - "hooks": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InfoResponse", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "$ref": "#/definitions/ContractVersion" - } - }, - "additionalProperties": false, - "definitions": { - "ContractVersion": { - "type": "object", - "required": [ - "contract", - "version" - ], - "properties": { - "contract": { - "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", - "type": "string" - }, - "version": { - "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", - "type": "string" - } - }, - "additionalProperties": false - } - } - }, - "is_active": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Boolean", - "type": "boolean" - }, - "list_stakers": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ListStakersResponse", - "type": "object", - "required": [ - "stakers" - ], - "properties": { - "stakers": { - "type": "array", - "items": { - "$ref": "#/definitions/StakerBalanceResponse" - } - } - }, - "additionalProperties": false, - "definitions": { - "StakerBalanceResponse": { - "type": "object", - "required": [ - "address", - "balance" - ], - "properties": { - "address": { - "type": "string" - }, - "balance": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "total_power_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TotalPowerAtHeightResponse", - "type": "object", - "required": [ - "height", - "power" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "power": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "voting_power_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VotingPowerAtHeightResponse", - "type": "object", - "required": [ - "height", - "power" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "power": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - } - } -} diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs deleted file mode 100644 index 80a413a9f..000000000 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ /dev/null @@ -1,502 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Response, StdResult, Uint128, Uint256, -}; -use cw2::set_contract_version; -use cw_controllers::ClaimsResponse; -use cw_utils::{must_pay, Duration}; -use dao_interface::voting::{ - IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, -}; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; - -use crate::error::ContractError; -use crate::msg::{ - DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, - QueryMsg, StakerBalanceResponse, -}; -use crate::state::{ - Config, ACTIVE_THRESHOLD, CLAIMS, CONFIG, DAO, HOOKS, MAX_CLAIMS, STAKED_BALANCES, STAKED_TOTAL, -}; - -pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-native-staked"; -pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -// We multiply by this when calculating needed power for being active -// when using active threshold with percent -const PRECISION_FACTOR: u128 = 10u128.pow(9); - -fn validate_duration(duration: Option) -> Result<(), ContractError> { - if let Some(unstaking_duration) = duration { - match unstaking_duration { - Duration::Height(height) => { - if height == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - Duration::Time(time) => { - if time == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - } - } - Ok(()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - validate_duration(msg.unstaking_duration)?; - - let config = Config { - denom: msg.denom.clone(), - unstaking_duration: msg.unstaking_duration, - }; - - CONFIG.save(deps.storage, &config)?; - DAO.save(deps.storage, &info.sender)?; - - if let Some(active_threshold) = msg.active_threshold.as_ref() { - match active_threshold { - ActiveThreshold::AbsoluteCount { count } => { - assert_valid_absolute_count_threshold(deps.as_ref(), &msg.denom, *count)?; - } - ActiveThreshold::Percentage { percent } => { - if *percent > Decimal::percent(100) || *percent <= Decimal::percent(0) { - return Err(ContractError::InvalidActivePercentage {}); - } - } - } - - ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; - } - - Ok(Response::new().add_attribute("action", "instantiate")) -} - -pub fn assert_valid_absolute_count_threshold( - deps: Deps, - token_denom: &str, - count: Uint128, -) -> Result<(), ContractError> { - if count.is_zero() { - return Err(ContractError::ZeroActiveCount {}); - } - let supply: Coin = deps.querier.query_supply(token_denom.to_string())?; - if count > supply.amount { - return Err(ContractError::InvalidAbsoluteCount {}); - } - Ok(()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Stake {} => execute_stake(deps, env, info), - ExecuteMsg::Unstake { amount } => execute_unstake(deps, env, info, amount), - ExecuteMsg::UpdateConfig { duration } => execute_update_config(deps, info, duration), - ExecuteMsg::Claim {} => execute_claim(deps, env, info), - ExecuteMsg::UpdateActiveThreshold { new_threshold } => { - execute_update_active_threshold(deps, env, info, new_threshold) - } - ExecuteMsg::AddHook { addr } => execute_add_hook(deps, env, info, addr), - ExecuteMsg::RemoveHook { addr } => execute_remove_hook(deps, env, info, addr), - } -} - -pub fn execute_stake( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let config = CONFIG.load(deps.storage)?; - let amount = must_pay(&info, &config.denom)?; - - STAKED_BALANCES.update( - deps.storage, - &info.sender, - env.block.height, - |balance| -> StdResult { Ok(balance.unwrap_or_default().checked_add(amount)?) }, - )?; - STAKED_TOTAL.update( - deps.storage, - env.block.height, - |total| -> StdResult { Ok(total.unwrap_or_default().checked_add(amount)?) }, - )?; - - Ok(Response::new() - .add_attribute("action", "stake") - .add_attribute("amount", amount.to_string()) - .add_attribute("from", info.sender)) -} - -pub fn execute_unstake( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, -) -> Result { - if amount.is_zero() { - return Err(ContractError::ZeroUnstake {}); - } - - STAKED_BALANCES.update( - deps.storage, - &info.sender, - env.block.height, - |balance| -> Result { - balance - .unwrap_or_default() - .checked_sub(amount) - .map_err(|_e| ContractError::InvalidUnstakeAmount {}) - }, - )?; - STAKED_TOTAL.update( - deps.storage, - env.block.height, - |total| -> Result { - total - .unwrap_or_default() - .checked_sub(amount) - .map_err(|_e| ContractError::InvalidUnstakeAmount {}) - }, - )?; - - let config = CONFIG.load(deps.storage)?; - match config.unstaking_duration { - None => { - let msg = CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(amount.u128(), config.denom), - }); - Ok(Response::new() - .add_message(msg) - .add_attribute("action", "unstake") - .add_attribute("from", info.sender) - .add_attribute("amount", amount) - .add_attribute("claim_duration", "None")) - } - Some(duration) => { - let outstanding_claims = CLAIMS.query_claims(deps.as_ref(), &info.sender)?.claims; - if outstanding_claims.len() >= MAX_CLAIMS as usize { - return Err(ContractError::TooManyClaims {}); - } - - CLAIMS.create_claim( - deps.storage, - &info.sender, - amount, - duration.after(&env.block), - )?; - Ok(Response::new() - .add_attribute("action", "unstake") - .add_attribute("from", info.sender) - .add_attribute("amount", amount) - .add_attribute("claim_duration", format!("{duration}"))) - } - } -} - -pub fn execute_update_config( - deps: DepsMut, - info: MessageInfo, - duration: Option, -) -> Result { - let mut config: Config = CONFIG.load(deps.storage)?; - - // Only the DAO can update the config - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - validate_duration(duration)?; - - config.unstaking_duration = duration; - - CONFIG.save(deps.storage, &config)?; - Ok(Response::new().add_attribute("action", "update_config")) -} - -pub fn execute_claim( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let release = CLAIMS.claim_tokens(deps.storage, &info.sender, &env.block, None)?; - if release.is_zero() { - return Err(ContractError::NothingToClaim {}); - } - - let config = CONFIG.load(deps.storage)?; - let msg = CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(release.u128(), config.denom), - }); - - Ok(Response::new() - .add_message(msg) - .add_attribute("action", "claim") - .add_attribute("from", info.sender) - .add_attribute("amount", release)) -} - -pub fn execute_update_active_threshold( - deps: DepsMut, - _env: Env, - info: MessageInfo, - new_active_threshold: Option, -) -> Result { - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - if let Some(active_threshold) = new_active_threshold { - match active_threshold { - ActiveThreshold::Percentage { percent } => { - if percent > Decimal::percent(100) || percent.is_zero() { - return Err(ContractError::InvalidActivePercentage {}); - } - } - ActiveThreshold::AbsoluteCount { count } => { - let denom = CONFIG.load(deps.storage)?.denom; - assert_valid_absolute_count_threshold(deps.as_ref(), &denom, count)?; - } - } - ACTIVE_THRESHOLD.save(deps.storage, &active_threshold)?; - } else { - ACTIVE_THRESHOLD.remove(deps.storage); - } - - Ok(Response::new().add_attribute("action", "update_active_threshold")) -} - -pub fn execute_add_hook( - deps: DepsMut, - _env: Env, - info: MessageInfo, - addr: String, -) -> Result { - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - let hook = deps.api.addr_validate(&addr)?; - HOOKS.add_hook(deps.storage, hook)?; - Ok(Response::new() - .add_attribute("action", "add_hook") - .add_attribute("hook", addr)) -} - -pub fn execute_remove_hook( - deps: DepsMut, - _env: Env, - info: MessageInfo, - addr: String, -) -> Result { - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - let hook = deps.api.addr_validate(&addr)?; - HOOKS.remove_hook(deps.storage, hook)?; - Ok(Response::new() - .add_attribute("action", "remove_hook") - .add_attribute("hook", addr)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::VotingPowerAtHeight { address, height } => { - to_binary(&query_voting_power_at_height(deps, env, address, height)?) - } - QueryMsg::TotalPowerAtHeight { height } => { - to_binary(&query_total_power_at_height(deps, env, height)?) - } - QueryMsg::Info {} => query_info(deps), - QueryMsg::Dao {} => query_dao(deps), - QueryMsg::Claims { address } => to_binary(&query_claims(deps, address)?), - QueryMsg::GetConfig {} => to_binary(&CONFIG.load(deps.storage)?), - QueryMsg::ListStakers { start_after, limit } => { - query_list_stakers(deps, start_after, limit) - } - QueryMsg::GetDenom {} => query_denom(deps), - QueryMsg::IsActive {} => query_is_active(deps), - QueryMsg::ActiveThreshold {} => query_active_threshold(deps), - QueryMsg::GetHooks {} => to_binary(&query_hooks(deps)?), - } -} - -pub fn query_voting_power_at_height( - deps: Deps, - env: Env, - address: String, - height: Option, -) -> StdResult { - let height = height.unwrap_or(env.block.height); - let address = deps.api.addr_validate(&address)?; - let power = STAKED_BALANCES - .may_load_at_height(deps.storage, &address, height)? - .unwrap_or_default(); - Ok(VotingPowerAtHeightResponse { power, height }) -} - -pub fn query_total_power_at_height( - deps: Deps, - env: Env, - height: Option, -) -> StdResult { - let height = height.unwrap_or(env.block.height); - let power = STAKED_TOTAL - .may_load_at_height(deps.storage, height)? - .unwrap_or_default(); - Ok(TotalPowerAtHeightResponse { power, height }) -} - -pub fn query_info(deps: Deps) -> StdResult { - let info = cw2::get_contract_version(deps.storage)?; - to_binary(&dao_interface::voting::InfoResponse { info }) -} - -pub fn query_dao(deps: Deps) -> StdResult { - let dao = DAO.load(deps.storage)?; - to_binary(&dao) -} - -pub fn query_denom(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - to_binary(&DenomResponse { - denom: config.denom, - }) -} - -pub fn query_claims(deps: Deps, address: String) -> StdResult { - CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?) -} - -pub fn query_list_stakers( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - let start_at = start_after - .map(|addr| deps.api.addr_validate(&addr)) - .transpose()?; - - let stakers = cw_paginate_storage::paginate_snapshot_map( - deps, - &STAKED_BALANCES, - start_at.as_ref(), - limit, - cosmwasm_std::Order::Ascending, - )?; - - let stakers = stakers - .into_iter() - .map(|(address, balance)| StakerBalanceResponse { - address: address.into_string(), - balance, - }) - .collect(); - - to_binary(&ListStakersResponse { stakers }) -} - -pub fn query_is_active(deps: Deps) -> StdResult { - let threshold = ACTIVE_THRESHOLD.may_load(deps.storage)?; - if let Some(threshold) = threshold { - let denom = CONFIG.load(deps.storage)?.denom; - let actual_power = STAKED_TOTAL.may_load(deps.storage)?.unwrap_or_default(); - match threshold { - ActiveThreshold::AbsoluteCount { count } => to_binary(&IsActiveResponse { - active: actual_power >= count, - }), - ActiveThreshold::Percentage { percent } => { - // percent is bounded between [0, 100]. decimal - // represents percents in u128 terms as p * - // 10^15. this bounds percent between [0, 10^17]. - // - // total_potential_power is bounded between [0, 2^128] - // as it tracks the balances of a cw20 token which has - // a max supply of 2^128. - // - // with our precision factor being 10^9: - // - // total_power <= 2^128 * 10^9 <= 2^256 - // - // so we're good to put that in a u256. - // - // multiply_ratio promotes to a u512 under the hood, - // so it won't overflow, multiplying by a percent less - // than 100 is gonna make something the same size or - // smaller, applied + 10^9 <= 2^128 * 10^9 + 10^9 <= - // 2^256, so the top of the round won't overflow, and - // rounding is rounding down, so the whole thing can - // be safely unwrapped at the end of the day thank you - // for coming to my ted talk. - let total_potential_power: cosmwasm_std::SupplyResponse = - deps.querier - .query(&cosmwasm_std::QueryRequest::Bank(BankQuery::Supply { - denom, - }))?; - let total_power = total_potential_power - .amount - .amount - .full_mul(PRECISION_FACTOR); - // under the hood decimals are `atomics / 10^decimal_places`. - // cosmwasm doesn't give us a Decimal * Uint256 - // implementation so we take the decimal apart and - // multiply by the fraction. - let applied = total_power.multiply_ratio( - percent.atomics(), - Uint256::from(10u64).pow(percent.decimal_places()), - ); - let rounded = (applied + Uint256::from(PRECISION_FACTOR) - Uint256::from(1u128)) - / Uint256::from(PRECISION_FACTOR); - let count: Uint128 = rounded.try_into().unwrap(); - to_binary(&IsActiveResponse { - active: actual_power >= count, - }) - } - } - } else { - to_binary(&IsActiveResponse { active: true }) - } -} - -pub fn query_active_threshold(deps: Deps) -> StdResult { - to_binary(&ActiveThresholdResponse { - active_threshold: ACTIVE_THRESHOLD.may_load(deps.storage)?, - }) -} - -pub fn query_hooks(deps: Deps) -> StdResult { - Ok(GetHooksResponse { - hooks: HOOKS.query_hooks(deps)?.hooks, - }) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) -} diff --git a/contracts/voting/dao-voting-native-staked/src/error.rs b/contracts/voting/dao-voting-native-staked/src/error.rs deleted file mode 100644 index b87342fb4..000000000 --- a/contracts/voting/dao-voting-native-staked/src/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -use cosmwasm_std::StdError; -use cw_utils::PaymentError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - PaymentError(#[from] PaymentError), - - #[error(transparent)] - HookError(#[from] cw_hooks::HookError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Invalid unstaking duration, unstaking duration cannot be 0")] - InvalidUnstakingDuration {}, - - #[error("Nothing to claim")] - NothingToClaim {}, - - #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] - TooManyClaims {}, - - #[error("Absolute count threshold cannot be greater than the total token supply")] - InvalidAbsoluteCount {}, - - #[error("Active threshold percentage must be greater than 0 and less than 1")] - InvalidActivePercentage {}, - - #[error("Can only unstake less than or equal to the amount you have staked")] - InvalidUnstakeAmount {}, - - #[error("Active threshold count must be greater than zero")] - ZeroActiveCount {}, - - #[error("Amount being unstaked must be non-zero")] - ZeroUnstake {}, -} diff --git a/contracts/voting/dao-voting-native-staked/src/msg.rs b/contracts/voting/dao-voting-native-staked/src/msg.rs deleted file mode 100644 index a8b562524..000000000 --- a/contracts/voting/dao-voting-native-staked/src/msg.rs +++ /dev/null @@ -1,84 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; -use cw_utils::Duration; -use dao_dao_macros::{active_query, voting_module_query}; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; - -#[cw_serde] -pub struct InstantiateMsg { - /// Token denom e.g. ujuno, or some ibc denom - pub denom: String, - // How long until the tokens become liquid again - pub unstaking_duration: Option, - /// The number or percentage of tokens that must be staked - /// for the DAO to be active - pub active_threshold: Option, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Stakes tokens with the contract to get voting power in the DAO - Stake {}, - /// Unstakes tokens so that they begin unbonding - Unstake { amount: Uint128 }, - /// Updates the contract configuration - UpdateConfig { duration: Option }, - /// Claims unstaked tokens that have completed the unbonding period - Claim {}, - /// Sets the active threshold to a new value. Only the - /// instantiator of this contract (a DAO most likely) may call this - /// method. - UpdateActiveThreshold { - new_threshold: Option, - }, - /// Adds a hook that fires on staking / unstaking - AddHook { addr: String }, - /// Removes a hook that fires on staking / unstaking - RemoveHook { addr: String }, -} - -#[voting_module_query] -#[active_query] -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(crate::state::Config)] - GetConfig {}, - #[returns(cw_controllers::ClaimsResponse)] - Claims { address: String }, - #[returns(DenomResponse)] - GetDenom {}, - #[returns(ListStakersResponse)] - ListStakers { - start_after: Option, - limit: Option, - }, - #[returns(ActiveThresholdResponse)] - ActiveThreshold {}, - #[returns(GetHooksResponse)] - GetHooks {}, -} - -#[cw_serde] -pub struct MigrateMsg {} - -#[cw_serde] -pub struct ListStakersResponse { - pub stakers: Vec, -} - -#[cw_serde] -pub struct StakerBalanceResponse { - pub address: String, - pub balance: Uint128, -} - -#[cw_serde] -pub struct DenomResponse { - pub denom: String, -} - -#[cw_serde] -pub struct GetHooksResponse { - pub hooks: Vec, -} diff --git a/contracts/voting/dao-voting-native-staked/src/state.rs b/contracts/voting/dao-voting-native-staked/src/state.rs deleted file mode 100644 index 3e46d0281..000000000 --- a/contracts/voting/dao-voting-native-staked/src/state.rs +++ /dev/null @@ -1,46 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; -use cw_controllers::Claims; -use cw_hooks::Hooks; -use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; -use cw_utils::Duration; -use dao_voting::threshold::ActiveThreshold; - -#[cw_serde] -pub struct Config { - pub denom: String, - pub unstaking_duration: Option, -} - -/// The configuration of this voting contract -pub const CONFIG: Item = Item::new("config"); - -/// The address of the DAO that instantiated this contract -pub const DAO: Item = Item::new("dao"); - -/// Keeps track of staked balances by address over time -pub const STAKED_BALANCES: SnapshotMap<&Addr, Uint128> = SnapshotMap::new( - "staked_balances", - "staked_balance__checkpoints", - "staked_balance__changelog", - Strategy::EveryBlock, -); - -/// Keeps track of staked total over time -pub const STAKED_TOTAL: SnapshotItem = SnapshotItem::new( - "total_staked", - "total_staked__checkpoints", - "total_staked__changelog", - Strategy::EveryBlock, -); - -/// The maximum number of claims that may be outstanding. -pub const MAX_CLAIMS: u64 = 100; - -pub const CLAIMS: Claims = Claims::new("claims"); - -/// The minimum amount of staked tokens for the DAO to be active -pub const ACTIVE_THRESHOLD: Item = Item::new("active_threshold"); - -/// Hooks to contracts that will receive staking and unstaking messages -pub const HOOKS: Hooks = Hooks::new("hooks"); diff --git a/contracts/voting/dao-voting-token-factory-staked/.cargo/config b/contracts/voting/dao-voting-token-factory-staked/.cargo/config deleted file mode 100644 index 336b618a1..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/voting/dao-voting-token-factory-staked/examples/schema.rs b/contracts/voting/dao-voting-token-factory-staked/examples/schema.rs deleted file mode 100644 index 2aa85dbff..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/examples/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::write_api; -use dao_voting_token_factory_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - migrate: MigrateMsg, - } -} diff --git a/contracts/voting/dao-voting-token-factory-staked/src/hooks.rs b/contracts/voting/dao-voting-token-factory-staked/src/hooks.rs deleted file mode 100644 index b5941ce84..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/src/hooks.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::state::HOOKS; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; -use token_bindings::TokenFactoryMsg; - -#[cw_serde] -pub enum StakeChangedHookMsg { - Stake { addr: Addr, amount: Uint128 }, - Unstake { addr: Addr, amount: Uint128 }, -} - -pub fn stake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult>> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Stake { addr, amount }, - ))?; - HOOKS.prepare_hooks_custom_msg(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::::new(execute)) - }) -} - -pub fn unstake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult>> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Unstake { addr, amount }, - ))?; - HOOKS.prepare_hooks_custom_msg(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::::new(execute)) - }) -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum StakeChangedExecuteMsg { - StakeChangeHook(StakeChangedHookMsg), -} diff --git a/contracts/voting/dao-voting-token-factory-staked/src/lib.rs b/contracts/voting/dao-voting-token-factory-staked/src/lib.rs deleted file mode 100644 index 6c512e72b..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] - -pub mod contract; -mod error; -pub mod hooks; -pub mod msg; -pub mod state; - -#[cfg(test)] -mod tests; - -pub use crate::error::ContractError; diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs deleted file mode 100644 index eb46f700f..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs +++ /dev/null @@ -1,1370 +0,0 @@ -use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; -use crate::msg::{ - ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, - StakerBalanceResponse, TokenInfo, -}; -use crate::state::Config; -use cosmwasm_std::testing::{mock_env, MockApi, MockQuerier, MockStorage}; -use cosmwasm_std::{coins, Addr, Coin, Decimal, OwnedDeps, Uint128}; -use cw_controllers::ClaimsResponse; -use cw_multi_test::{ - next_block, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, -}; -use cw_utils::Duration; -use dao_interface::voting::{ - InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, -}; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -use std::marker::PhantomData; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; -use token_bindings_test::TokenFactoryApp as App; - -const DAO_ADDR: &str = "dao"; -const ADDR1: &str = "addr1"; -const ADDR2: &str = "addr2"; -const DENOM: &str = "ujuno"; -const INVALID_DENOM: &str = "uinvalid"; -const ODD_DENOM: &str = "uodd"; - -fn issuer_contract() -> Box> { - let contract = ContractWrapper::new( - cw_tokenfactory_issuer::contract::execute, - cw_tokenfactory_issuer::contract::instantiate, - cw_tokenfactory_issuer::contract::query, - ) - .with_reply(cw_tokenfactory_issuer::contract::reply); - Box::new(contract) -} - -fn staking_contract() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply(crate::contract::reply); - Box::new(contract) -} - -fn mock_app() -> App { - let mut app = App::new(); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: DAO_ADDR.to_string(), - amount: vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - })) - .unwrap(); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: ADDR1.to_string(), - amount: vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: ODD_DENOM.to_string(), - amount: Uint128::new(5), - }, - ], - })) - .unwrap(); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: ADDR2.to_string(), - amount: vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - })) - .unwrap(); - app -} - -fn instantiate_staking(app: &mut App, staking_id: u64, msg: InstantiateMsg) -> Addr { - app.instantiate_contract( - staking_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "Staking", - None, - ) - .unwrap() -} - -fn stake_tokens( - app: &mut App, - staking_addr: Addr, - sender: &str, - amount: u128, - denom: &str, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Stake {}, - &coins(amount, denom), - ) -} - -fn unstake_tokens( - app: &mut App, - staking_addr: Addr, - sender: &str, - amount: u128, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Unstake { - amount: Uint128::new(amount), - }, - &[], - ) -} - -fn claim(app: &mut App, staking_addr: Addr, sender: &str) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Claim {}, - &[], - ) -} - -fn update_config( - app: &mut App, - staking_addr: Addr, - sender: &str, - duration: Option, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::UpdateConfig { duration }, - &[], - ) -} - -fn get_voting_power_at_height( - app: &mut App, - staking_addr: Addr, - address: String, - height: Option, -) -> VotingPowerAtHeightResponse { - app.wrap() - .query_wasm_smart( - staking_addr, - &QueryMsg::VotingPowerAtHeight { address, height }, - ) - .unwrap() -} - -fn get_total_power_at_height( - app: &mut App, - staking_addr: Addr, - height: Option, -) -> TotalPowerAtHeightResponse { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::TotalPowerAtHeight { height }) - .unwrap() -} - -fn get_config(app: &mut App, staking_addr: Addr) -> Config { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::GetConfig {}) - .unwrap() -} - -fn get_claims(app: &mut App, staking_addr: Addr, address: String) -> ClaimsResponse { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::Claims { address }) - .unwrap() -} - -fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { - app.wrap().query_balance(address, denom).unwrap().amount -} - -#[test] -fn test_instantiate_existing() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - // Populated fields - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Non populated fields - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: None, - active_threshold: None, - }, - ); - - let token_contract: Addr = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::TokenContract {}) - .unwrap(); - assert_eq!(token_contract, Addr::unchecked("contract3")); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_instantiate_invalid_unstaking_duration_height() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - - // Populated fields - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(0)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(1), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_instantiate_invalid_unstaking_duration_time() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - - // Populated fields with height - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Time(0)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(1), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Must send reserve token 'ujuno'")] -fn test_stake_invalid_denom() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Try and stake an invalid denom - stake_tokens(&mut app, addr, ADDR1, 100, INVALID_DENOM).unwrap(); -} - -#[test] -fn test_stake_valid_denom() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Try and stake an valid denom - stake_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); -} - -#[test] -#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] -fn test_unstake_none_staked() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - unstake_tokens(&mut app, addr, ADDR1, 100).unwrap(); -} - -#[test] -#[should_panic(expected = "Amount being unstaked must be non-zero")] -fn test_unstake_zero_tokens() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - unstake_tokens(&mut app, addr, ADDR1, 0).unwrap(); -} - -#[test] -#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] -fn test_unstake_invalid_balance() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Try and unstake too many - unstake_tokens(&mut app, addr, ADDR1, 200).unwrap(); -} - -#[test] -fn test_unstake() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - - // Query claims - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 1); - app.update_block(next_block); - - // Unstake the rest - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - - // Query claims - let claims = get_claims(&mut app, addr, ADDR1.to_string()); - assert_eq!(claims.claims.len(), 2); -} - -#[test] -fn test_unstake_no_unstaking_duration() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: None, - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some tokens - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - - app.update_block(next_block); - - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 - assert_eq!(balance, Uint128::new(9975)); - - // Unstake the rest - unstake_tokens(&mut app, addr, ADDR1, 25).unwrap(); - - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 - assert_eq!(balance, Uint128::new(10000)) -} - -#[test] -#[should_panic(expected = "Nothing to claim")] -fn test_claim_no_claims() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - claim(&mut app, addr, ADDR1).unwrap(); -} - -#[test] -#[should_panic(expected = "Nothing to claim")] -fn test_claim_claim_not_reached() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake them to create the claims - unstake_tokens(&mut app, addr.clone(), ADDR1, 100).unwrap(); - app.update_block(next_block); - - // We have a claim but it isnt reached yet so this will still fail - claim(&mut app, addr, ADDR1).unwrap(); -} - -#[test] -fn test_claim() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some to create the claims - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - app.update_block(|b| { - b.height += 5; - b.time = b.time.plus_seconds(25); - }); - - // Claim - claim(&mut app, addr.clone(), ADDR1).unwrap(); - - // Query balance - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 - assert_eq!(balance, Uint128::new(9975)); - - // Unstake the rest - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(|b| { - b.height += 10; - b.time = b.time.plus_seconds(50); - }); - - // Claim - claim(&mut app, addr, ADDR1).unwrap(); - - // Query balance - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 - assert_eq!(balance, Uint128::new(10000)); -} - -#[test] -#[should_panic(expected = "Unauthorized")] -fn test_update_config_invalid_sender() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // From ADDR2, so not owner or manager - update_config(&mut app, addr, ADDR2, Some(Duration::Height(10))).unwrap(); -} - -#[test] -fn test_update_config_as_owner() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Swap owner and manager, change duration - update_config(&mut app, addr.clone(), DAO_ADDR, Some(Duration::Height(10))).unwrap(); - - let config = get_config(&mut app, addr); - assert_eq!( - Config { - unstaking_duration: Some(Duration::Height(10)), - }, - config - ); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_update_config_invalid_duration() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Change duration and manager as manager cannot change owner - update_config(&mut app, addr, DAO_ADDR, Some(Duration::Height(0))).unwrap(); -} - -#[test] -fn test_query_dao() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::Dao {}; - let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(dao, Addr::unchecked(DAO_ADDR)); -} - -#[test] -fn test_query_info() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::Info {}; - let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!( - resp.info.contract, - "crates.io:dao-voting-token-factory-staked" - ); -} - -#[test] -fn test_query_token_contract() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::TokenContract {}; - let res: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(res, Addr::unchecked("contract1")); -} - -#[test] -fn test_query_claims() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 0); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some tokens - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(next_block); - - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 1); - - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(next_block); - - let claims = get_claims(&mut app, addr, ADDR1.to_string()); - assert_eq!(claims.claims.len(), 2); -} - -#[test] -fn test_query_get_config() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let config = get_config(&mut app, addr); - assert_eq!( - config, - Config { - unstaking_duration: Some(Duration::Height(5)), - } - ) -} - -#[test] -fn test_voting_power_queries() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Total power is 0 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert!(resp.power.is_zero()); - - // ADDR1 has no power, none staked - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert!(resp.power.is_zero()); - - // ADDR1 stakes - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 100 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); - assert!(resp.power.is_zero()); - - // ADDR2 stakes - stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); - app.update_block(next_block); - let prev_height = app.block_info().height - 1; - - // Query the previous height, total 100, ADDR1 100, ADDR2 0 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 100 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); - assert!(resp.power.is_zero()); - - // For current height, total 150, ADDR1 100, ADDR2 50 - // Total power is 150 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(150)); - - // ADDR1 has 100 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 now has 50 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); - - // ADDR1 unstakes half - unstake_tokens(&mut app, addr.clone(), ADDR1, 50).unwrap(); - app.update_block(next_block); - let prev_height = app.block_info().height - 1; - - // Query the previous height, total 150, ADDR1 100, ADDR2 50 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(150)); - - // ADDR1 has 100 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(50)); - - // For current height, total 100, ADDR1 50, ADDR2 50 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 50 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); - - // ADDR2 now has 50 power - let resp = get_voting_power_at_height(&mut app, addr, ADDR2.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); -} - -#[test] -fn test_query_list_stakers() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // ADDR1 stakes - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - - // ADDR2 stakes - stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); - - // check entire result set - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr.clone(), - &QueryMsg::ListStakers { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let test_res = ListStakersResponse { - stakers: vec![ - StakerBalanceResponse { - address: ADDR1.to_string(), - balance: Uint128::new(100), - }, - StakerBalanceResponse { - address: ADDR2.to_string(), - balance: Uint128::new(50), - }, - ], - }; - - assert_eq!(stakers, test_res); - - // skipped 1, check result - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr.clone(), - &QueryMsg::ListStakers { - start_after: Some(ADDR1.to_string()), - limit: None, - }, - ) - .unwrap(); - - let test_res = ListStakersResponse { - stakers: vec![StakerBalanceResponse { - address: ADDR2.to_string(), - balance: Uint128::new(50), - }], - }; - - assert_eq!(stakers, test_res); - - // skipped 2, check result. should be nothing - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr, - &QueryMsg::ListStakers { - start_after: Some(ADDR2.to_string()), - limit: None, - }, - ) - .unwrap(); - - assert_eq!(stakers, ListStakersResponse { stakers: vec![] }); -} - -#[test] -#[should_panic(expected = "Active threshold count must be greater than zero")] -fn test_instantiate_zero_active_threshold_count() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::zero(), - }), - }, - ); -} - -#[test] -fn test_active_threshold_absolute_count() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 100 tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Active as enough staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_percent() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(20), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 6000 tokens, now active - stake_tokens(&mut app, addr.clone(), ADDR1, 6000, DENOM).unwrap(); - app.update_block(next_block); - - // Active as enough staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_percent_rounds_up() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: ODD_DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(50), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 2 tokens, should not be active. - stake_tokens(&mut app, addr.clone(), ADDR1, 2, ODD_DENOM).unwrap(); - app.update_block(next_block); - - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 1 more token, should now be active. - stake_tokens(&mut app, addr.clone(), ADDR1, 1, ODD_DENOM).unwrap(); - app.update_block(next_block); - - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_none() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Active as no threshold - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_update_active_threshold() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!(resp.active_threshold, None); - - let msg = ExecuteMsg::UpdateActiveThreshold { - new_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - }; - - // Expect failure as sender is not the DAO - app.execute_contract(Addr::unchecked(ADDR1), addr.clone(), &msg, &[]) - .unwrap_err(); - - // Expect success as sender is the DAO - app.execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) - .unwrap(); - - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!( - resp.active_threshold, - Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100) - }) - ); -} - -#[test] -#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] -fn test_active_threshold_percentage_gt_100() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(120), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] -fn test_active_threshold_percentage_lte_0() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(0), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Absolute count threshold cannot be greater than the total token supply")] -fn test_active_threshold_absolute_count_invalid() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(30001), - }), - }, - ); -} - -#[test] -fn test_add_remove_hooks() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // No hooks exist. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, Vec::::new()); - - // Add a hook. - app.execute_contract( - Addr::unchecked(DAO_ADDR), - addr.clone(), - &ExecuteMsg::AddHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap(); - - // One hook exists. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, vec!["hook".to_string()]); - - // Remove hook. - app.execute_contract( - Addr::unchecked(DAO_ADDR), - addr.clone(), - &ExecuteMsg::RemoveHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap(); - - // No hook exists. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, Vec::::new()); -} - -#[test] -pub fn test_migrate_update_version() { - let mut deps = OwnedDeps { - storage: MockStorage::default(), - api: MockApi::default(), - querier: MockQuerier::default(), - custom_query_type: PhantomData::, - }; - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); - migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); - let version = cw2::get_contract_version(&deps.storage).unwrap(); - assert_eq!(version.version, CONTRACT_VERSION); - assert_eq!(version.contract, CONTRACT_NAME); -} diff --git a/contracts/voting/dao-voting-native-staked/.cargo/config b/contracts/voting/dao-voting-token-staked/.cargo/config similarity index 100% rename from contracts/voting/dao-voting-native-staked/.cargo/config rename to contracts/voting/dao-voting-token-staked/.cargo/config diff --git a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml b/contracts/voting/dao-voting-token-staked/Cargo.toml similarity index 82% rename from contracts/voting/dao-voting-token-factory-staked/Cargo.toml rename to contracts/voting/dao-voting-token-staked/Cargo.toml index d66450ba0..60d2b5c90 100644 --- a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-staked/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dao-voting-token-factory-staked" +name = "dao-voting-token-staked" authors = ["Callum Anderson ", "Noah Saso ", "Jake Hartnell "] description = "A DAO DAO voting module based on staked token factory or native tokens. Only works with chains that support Token Factory." edition = { workspace = true } @@ -25,6 +25,7 @@ test-tube = [] cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } cosmwasm-schema = { workspace = true } cosmwasm-storage = { workspace = true } +cw-ownable = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } cw-utils = { workspace = true } @@ -32,19 +33,20 @@ cw-controllers = { workspace = true } cw-hooks = { workspace = true } thiserror = { workspace = true } dao-dao-macros = { workspace = true } +dao-hooks = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } cw-paginate-storage = { workspace = true } cw-tokenfactory-issuer = { workspace = true, features = ["library"] } -token-bindings = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -# TODO use upstream when PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 -cw-multi-test = { git = "https://github.com/JakeHartnell/cw-multi-test.git", branch = "bank-supply-support" } +# TODO use upstream when new release is tagged +cw-multi-test = { git = "https://github.com/CosmWasm/cw-multi-test.git", rev = "d38db7752b9f054c395d6108453f8b321e4cab02" } cw-tokenfactory-issuer = { workspace = true } +dao-proposal-single = { workspace = true } +dao-proposal-hook-counter = { workspace = true } dao-testing = { workspace = true, features = ["test-tube"] } osmosis-std = { workpsace = true } osmosis-test-tube = { workspace = true } serde = { workspace = true } -token-bindings-test = { workspace = true } diff --git a/contracts/voting/dao-voting-token-factory-staked/README.md b/contracts/voting/dao-voting-token-staked/README.md similarity index 59% rename from contracts/voting/dao-voting-token-factory-staked/README.md rename to contracts/voting/dao-voting-token-staked/README.md index ad427dc0b..c9275c390 100644 --- a/contracts/voting/dao-voting-token-factory-staked/README.md +++ b/contracts/voting/dao-voting-token-staked/README.md @@ -1,31 +1,34 @@ -# `dao_voting_token_factory_staked` +# `dao_voting_token_staked` Simple native or Token Factory based token voting / staking contract which assumes the native denom provided is not used for staking for securing the network e.g. IBC denoms or secondary tokens (ION). Staked balances may be queried at an arbitrary height. This contract implements the interface needed to be a DAO DAO [voting module](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design#the-voting-module). -`dao_voting_token_factory_staked` leverages the `cw_tokenfactory_issuer` contract for tokenfactory functionality. When instantiated, `dao_voting_token_factory_staked` creates a new `cw_tokenfactory_issuer` contract to manage the new Token, with the DAO as admin and owner (these can be renounced or updated by vote of the DAO). +### Token Factory support +`dao_voting_token_staked` leverages the `cw_tokenfactory_issuer` contract for tokenfactory functionality. When instantiated, `dao_voting_token_staked` creates a new `cw_tokenfactory_issuer` contract to manage the new Token, with the DAO as admin and owner (these can be renounced or updated by vote of the DAO). -NOTE: This contract requires having the Token Factory module on your chain, which allows the creation of new native tokens. If your chain does not have this module, use `dao-voting-native-staked` instead. +The `cw_tokenfactory_issuer` contract supports many features, see the [cw_tokenfactory_issuer contract README](../../external/cw-tokenfactory-issuer/README.md) for more information. ## Instantiation -When instantiating a new `dao_voting_token_factory_staked` contract there are two required fields: -- `token_issuer_code_id`: must be set to a valid Code ID for the `cw_tokenfactory_issuer` contract. -- `token_info`: you have the option to leverage an `existing` token or creating a `new` one. +When instantiating a new `dao_voting_token_staked` contract there are two required fields: +- `token_info`: you have the option to leverage an `existing` native token or creating a `new` one using the Token Factory module. There are a few optional fields: - `unstaking_duration`: can be set to `height` or `time` (in seconds), this is the amount of time that must elapse before a user can claim fully unstaked tokens. If not set, they are instantly claimable. - `active_theshold`: the amount of tokens that must be staked for the DAO to be active. This may be either an `absolute_count` or a `percentage`. ### Create a New Token +- `token_issuer_code_id`: must be set to a valid Code ID for the `cw_tokenfactory_issuer` contract. +- `initial_balances`: the initial distribution of the new token, there must be at least 1 account with a balance so as the DAO is not locked. + Creating a token has a few additional optional fields: - `metadata`: information about the token. See [Cosmos SDK Coin metadata documentation](https://docs.cosmos.network/main/architecture/adr-024-coin-metadata) for more info on coin metadata. -- `initial_dao_balance`: the initial balance created for the DAO. +- `initial_dao_balance`: the initial balance created for the DAO treasury. Example insantiation mesggage: ``` json { - "token_issuer_code_id": , "token_info": { "new": { + "token_issuer_code_id": , "subdenom": "meow", "metadata": { "description": "Meow!", @@ -60,17 +63,20 @@ Example insantiation mesggage: } ``` -### Use Existing +### Use Existing Native Token +`dao-voting-token-staked` can also be used with existing native tokens. They could be in the form of a native denom like `ion`, an IBC token, or a Token Factory token. + Example insantiation mesggage: ``` json { - "token_issuer_code_id": , "token_info": { - "new": { - "subdenom": "factory/{address}/{denom}", + "existing": { + "denom": "uion", + } } } ``` -When leveraging an existing token, cetain `cw_tokenfactory_issuer` features will not work until admin of the Token is transferred over to the `cw_tokenfactory_issuer` contract. +NOTE: if using an existing Token Factory token, double check the Token Factory admin and consider changing the Token Factory to be the DAO after the DAO is created. + diff --git a/contracts/voting/dao-voting-native-staked/examples/schema.rs b/contracts/voting/dao-voting-token-staked/examples/schema.rs similarity index 68% rename from contracts/voting/dao-voting-native-staked/examples/schema.rs rename to contracts/voting/dao-voting-token-staked/examples/schema.rs index f7ad58f28..0bb3d7c39 100644 --- a/contracts/voting/dao-voting-native-staked/examples/schema.rs +++ b/contracts/voting/dao-voting-token-staked/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use dao_voting_native_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use dao_voting_token_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/voting/dao-voting-token-factory-staked/schema/dao-voting-token-factory-staked.json b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json similarity index 98% rename from contracts/voting/dao-voting-token-factory-staked/schema/dao-voting-token-factory-staked.json rename to contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json index 624c8be17..b102a0e25 100644 --- a/contracts/voting/dao-voting-token-factory-staked/schema/dao-voting-token-factory-staked.json +++ b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json @@ -1,5 +1,5 @@ { - "contract_name": "dao-voting-token-factory-staked", + "contract_name": "dao-voting-token-staked", "contract_version": "2.2.0", "idl_version": "1.0.0", "instantiate": { @@ -7,8 +7,7 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "token_info", - "token_issuer_code_id" + "token_info" ], "properties": { "active_threshold": { @@ -30,12 +29,6 @@ } ] }, - "token_issuer_code_id": { - "description": "The code id of the cw-tokenfactory-issuer contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "unstaking_duration": { "description": "How long until the tokens become liquid again", "anyOf": [ @@ -223,7 +216,8 @@ "type": "object", "required": [ "initial_balances", - "subdenom" + "subdenom", + "token_issuer_code_id" ], "properties": { "initial_balances": { @@ -258,6 +252,12 @@ "subdenom": { "description": "The subdenom of the token to create, will also be used as an alias for the denom. The Token Factory denom will have the format of factory/{contract_address}/{subdenom}", "type": "string" + }, + "token_issuer_code_id": { + "description": "The code id of the cw-tokenfactory-issuer contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false @@ -265,7 +265,7 @@ "TokenInfo": { "oneOf": [ { - "description": "Uses an existing Token Factory token and creates a new issuer contract. Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, must be done manually. Note, for chain controlled denoms or IBC tokens use dao-voting-native-staked.", + "description": "Uses an existing Token Factory token and creates a new issuer contract. Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, must be done manually.", "type": "object", "required": [ "existing" diff --git a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs similarity index 77% rename from contracts/voting/dao-voting-token-factory-staked/src/contract.rs rename to contracts/voting/dao-voting-token-staked/src/contract.rs index 60f8b8cb3..b9d692348 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -2,34 +2,40 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Order, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, + coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, + Order, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_controllers::ClaimsResponse; use cw_storage_plus::Bound; use cw_tokenfactory_issuer::msg::{ DenomUnit, ExecuteMsg as IssuerExecuteMsg, InstantiateMsg as IssuerInstantiateMsg, Metadata, }; use cw_utils::{maybe_addr, must_pay, parse_reply_instantiate_data, Duration}; +use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; +use dao_interface::state::ModuleInstantiateCallback; use dao_interface::voting::{ IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; +use dao_voting::{ + duration::validate_duration, + threshold::{ + assert_valid_absolute_count_threshold, assert_valid_percentage_threshold, ActiveThreshold, + ActiveThresholdResponse, + }, +}; use crate::error::ContractError; -use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; use crate::msg::{ DenomResponse, ExecuteMsg, GetHooksResponse, InitialBalance, InstantiateMsg, - ListStakersResponse, MigrateMsg, QueryMsg, StakerBalanceResponse, TokenInfo, + ListStakersResponse, MigrateMsg, NewTokenInfo, QueryMsg, StakerBalanceResponse, TokenInfo, }; use crate::state::{ Config, ACTIVE_THRESHOLD, CLAIMS, CONFIG, DAO, DENOM, HOOKS, MAX_CLAIMS, STAKED_BALANCES, STAKED_TOTAL, TOKEN_INSTANTIATION_INFO, TOKEN_ISSUER_CONTRACT, }; -pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-token-factory-staked"; +pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-token-staked"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); // Settings for query pagination @@ -42,31 +48,13 @@ const INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID: u64 = 0; // when using active threshold with percent const PRECISION_FACTOR: u128 = 10u128.pow(9); -fn validate_duration(duration: Option) -> Result<(), ContractError> { - if let Some(unstaking_duration) = duration { - match unstaking_duration { - Duration::Height(height) => { - if height == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - Duration::Time(time) => { - if time == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - } - } - Ok(()) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg, -) -> Result, ContractError> { +) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; validate_duration(msg.unstaking_duration)?; @@ -78,56 +66,50 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; DAO.save(deps.storage, &info.sender)?; + // Validate Active Threshold if let Some(active_threshold) = msg.active_threshold.as_ref() { + // Only check active threshold percentage as new tokens don't exist yet + // We will check Absolute count (if configured) later for both existing + // and new tokens. if let ActiveThreshold::Percentage { percent } = active_threshold { - if *percent > Decimal::percent(100) || *percent <= Decimal::percent(0) { - return Err(ContractError::InvalidActivePercentage {}); - } + assert_valid_percentage_threshold(*percent)?; } ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; } - // Save new token info for use in reply - TOKEN_INSTANTIATION_INFO.save(deps.storage, &msg.token_info)?; - match msg.token_info { TokenInfo::Existing { denom } => { + // Validate active threshold absolute count if configured if let Some(ActiveThreshold::AbsoluteCount { count }) = msg.active_threshold { - assert_valid_absolute_count_threshold(deps.as_ref(), &denom, count)?; + let supply: Coin = deps.querier.query_supply(denom.clone())?; + assert_valid_absolute_count_threshold(count, supply.amount)?; } DENOM.save(deps.storage, &denom)?; - // Instantiate cw-token-factory-issuer contract - // DAO (sender) is set as admin - let issuer_instantiate_msg = SubMsg::reply_on_success( - WasmMsg::Instantiate { - admin: Some(info.sender.to_string()), - code_id: msg.token_issuer_code_id, - msg: to_binary(&IssuerInstantiateMsg::ExistingToken { - denom: denom.clone(), - })?, - funds: info.funds, - label: "cw-tokenfactory-issuer".to_string(), - }, - INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, - ); - - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "instantiate") .add_attribute("token", "existing_token") - .add_attribute("token_denom", denom) - .add_submessage(issuer_instantiate_msg)) + .add_attribute("denom", denom)) } - TokenInfo::New(token) => { + TokenInfo::New(ref token) => { + let NewTokenInfo { + subdenom, + token_issuer_code_id, + .. + } = token; + + // Save new token info for use in reply + TOKEN_INSTANTIATION_INFO.save(deps.storage, &msg.token_info)?; + // Tnstantiate cw-token-factory-issuer contract // DAO (sender) is set as contract admin - let issuer_instantiate_msg = SubMsg::reply_always( + let issuer_instantiate_msg = SubMsg::reply_on_success( WasmMsg::Instantiate { admin: Some(info.sender.to_string()), - code_id: msg.token_issuer_code_id, + code_id: *token_issuer_code_id, msg: to_binary(&IssuerInstantiateMsg::NewToken { - subdenom: token.subdenom.clone(), + subdenom: subdenom.to_string(), })?, funds: info.funds, label: "cw-tokenfactory-issuer".to_string(), @@ -135,7 +117,7 @@ pub fn instantiate( INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, ); - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "instantiate") .add_attribute("token", "new_token") .add_submessage(issuer_instantiate_msg)) @@ -143,28 +125,13 @@ pub fn instantiate( } } -pub fn assert_valid_absolute_count_threshold( - deps: Deps, - token_denom: &str, - count: Uint128, -) -> Result<(), ContractError> { - if count.is_zero() { - return Err(ContractError::ZeroActiveCount {}); - } - let supply: Coin = deps.querier.query_supply(token_denom.to_string())?; - if count > supply.amount { - return Err(ContractError::InvalidAbsoluteCount {}); - } - Ok(()) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result, ContractError> { +) -> Result { match msg { ExecuteMsg::Stake {} => execute_stake(deps, env, info), ExecuteMsg::Unstake { amount } => execute_unstake(deps, env, info, amount), @@ -179,10 +146,10 @@ pub fn execute( } pub fn execute_stake( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, -) -> Result, ContractError> { +) -> Result { let denom = DENOM.load(deps.storage)?; let amount = must_pay(&info, &denom)?; @@ -197,9 +164,11 @@ pub fn execute_stake( env.block.height, |total| -> StdResult { Ok(total.unwrap_or_default().checked_add(amount)?) }, )?; - let hook_msgs = stake_hook_msgs(deps.storage, info.sender.clone(), amount)?; - Ok(Response::::new() + // Add stake hook messages + let hook_msgs = stake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; + + Ok(Response::new() .add_submessages(hook_msgs) .add_attribute("action", "stake") .add_attribute("amount", amount.to_string()) @@ -207,11 +176,11 @@ pub fn execute_stake( } pub fn execute_unstake( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128, -) -> Result, ContractError> { +) -> Result { if amount.is_zero() { return Err(ContractError::ZeroUnstake {}); } @@ -237,7 +206,9 @@ pub fn execute_unstake( .map_err(|_e| ContractError::InvalidUnstakeAmount {}) }, )?; - let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + + // Add unstake hook messages + let hook_msgs = unstake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; let config = CONFIG.load(deps.storage)?; let denom = DENOM.load(deps.storage)?; @@ -247,7 +218,7 @@ pub fn execute_unstake( to_address: info.sender.to_string(), amount: coins(amount.u128(), denom), }); - Ok(Response::::new() + Ok(Response::new() .add_message(msg) .add_submessages(hook_msgs) .add_attribute("action", "unstake") @@ -267,7 +238,7 @@ pub fn execute_unstake( amount, duration.after(&env.block), )?; - Ok(Response::::new() + Ok(Response::new() .add_submessages(hook_msgs) .add_attribute("action", "unstake") .add_attribute("from", info.sender) @@ -278,10 +249,10 @@ pub fn execute_unstake( } pub fn execute_update_config( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, duration: Option, -) -> Result, ContractError> { +) -> Result { let mut config: Config = CONFIG.load(deps.storage)?; // Only the DAO can update the config @@ -295,26 +266,26 @@ pub fn execute_update_config( config.unstaking_duration = duration; CONFIG.save(deps.storage, &config)?; - Ok(Response::::new().add_attribute("action", "update_config")) + Ok(Response::new().add_attribute("action", "update_config")) } pub fn execute_claim( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, -) -> Result, ContractError> { +) -> Result { let release = CLAIMS.claim_tokens(deps.storage, &info.sender, &env.block, None)?; if release.is_zero() { return Err(ContractError::NothingToClaim {}); } let denom = DENOM.load(deps.storage)?; - let msg = CosmosMsg::::Bank(BankMsg::Send { + let msg = CosmosMsg::Bank(BankMsg::Send { to_address: info.sender.to_string(), amount: coins(release.u128(), denom), }); - Ok(Response::::new() + Ok(Response::new() .add_message(msg) .add_attribute("action", "claim") .add_attribute("from", info.sender) @@ -322,11 +293,11 @@ pub fn execute_claim( } pub fn execute_update_active_threshold( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, new_active_threshold: Option, -) -> Result, ContractError> { +) -> Result { let dao = DAO.load(deps.storage)?; if info.sender != dao { return Err(ContractError::Unauthorized {}); @@ -335,13 +306,12 @@ pub fn execute_update_active_threshold( if let Some(active_threshold) = new_active_threshold { match active_threshold { ActiveThreshold::Percentage { percent } => { - if percent > Decimal::percent(100) || percent.is_zero() { - return Err(ContractError::InvalidActivePercentage {}); - } + assert_valid_percentage_threshold(percent)?; } ActiveThreshold::AbsoluteCount { count } => { let denom = DENOM.load(deps.storage)?; - assert_valid_absolute_count_threshold(deps.as_ref(), &denom, count)?; + let supply: Coin = deps.querier.query_supply(denom.to_string())?; + assert_valid_absolute_count_threshold(count, supply.amount)?; } } ACTIVE_THRESHOLD.save(deps.storage, &active_threshold)?; @@ -349,15 +319,15 @@ pub fn execute_update_active_threshold( ACTIVE_THRESHOLD.remove(deps.storage); } - Ok(Response::::new().add_attribute("action", "update_active_threshold")) + Ok(Response::new().add_attribute("action", "update_active_threshold")) } pub fn execute_add_hook( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, addr: String, -) -> Result, ContractError> { +) -> Result { let dao = DAO.load(deps.storage)?; if info.sender != dao { return Err(ContractError::Unauthorized {}); @@ -365,17 +335,17 @@ pub fn execute_add_hook( let hook = deps.api.addr_validate(&addr)?; HOOKS.add_hook(deps.storage, hook)?; - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "add_hook") .add_attribute("hook", addr)) } pub fn execute_remove_hook( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, addr: String, -) -> Result, ContractError> { +) -> Result { let dao = DAO.load(deps.storage)?; if info.sender != dao { return Err(ContractError::Unauthorized {}); @@ -383,13 +353,13 @@ pub fn execute_remove_hook( let hook = deps.api.addr_validate(&addr)?; HOOKS.remove_hook(deps.storage, hook)?; - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "remove_hook") .add_attribute("hook", addr)) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::VotingPowerAtHeight { address, height } => { to_binary(&query_voting_power_at_height(deps, env, address, height)?) @@ -415,7 +385,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResul } pub fn query_voting_power_at_height( - deps: Deps, + deps: Deps, env: Env, address: String, height: Option, @@ -429,7 +399,7 @@ pub fn query_voting_power_at_height( } pub fn query_total_power_at_height( - deps: Deps, + deps: Deps, env: Env, height: Option, ) -> StdResult { @@ -440,22 +410,22 @@ pub fn query_total_power_at_height( Ok(TotalPowerAtHeightResponse { power, height }) } -pub fn query_info(deps: Deps) -> StdResult { +pub fn query_info(deps: Deps) -> StdResult { let info = cw2::get_contract_version(deps.storage)?; to_binary(&dao_interface::voting::InfoResponse { info }) } -pub fn query_dao(deps: Deps) -> StdResult { +pub fn query_dao(deps: Deps) -> StdResult { let dao = DAO.load(deps.storage)?; to_binary(&dao) } -pub fn query_claims(deps: Deps, address: String) -> StdResult { +pub fn query_claims(deps: Deps, address: String) -> StdResult { CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?) } pub fn query_list_stakers( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { @@ -477,7 +447,7 @@ pub fn query_list_stakers( to_binary(&ListStakersResponse { stakers }) } -pub fn query_is_active(deps: Deps) -> StdResult { +pub fn query_is_active(deps: Deps) -> StdResult { let threshold = ACTIVE_THRESHOLD.may_load(deps.storage)?; if let Some(threshold) = threshold { let denom = DENOM.load(deps.storage)?; @@ -539,35 +509,33 @@ pub fn query_is_active(deps: Deps) -> StdResult { } } -pub fn query_active_threshold(deps: Deps) -> StdResult { +pub fn query_active_threshold(deps: Deps) -> StdResult { to_binary(&ActiveThresholdResponse { active_threshold: ACTIVE_THRESHOLD.may_load(deps.storage)?, }) } -pub fn query_hooks(deps: Deps) -> StdResult { +pub fn query_hooks(deps: Deps) -> StdResult { Ok(GetHooksResponse { hooks: HOOKS.query_hooks(deps)?.hooks, }) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate( - deps: DepsMut, - _env: Env, - _msg: MigrateMsg, -) -> Result, ContractError> { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::::default()) +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version.as_str() < CONTRACT_VERSION { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply( - deps: DepsMut, - env: Env, - msg: Reply, -) -> Result, ContractError> { +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { match msg.id { INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID => { // Parse and save address of cw-tokenfactory-issuer @@ -579,13 +547,6 @@ pub fn reply( TOKEN_INSTANTIATION_INFO.remove(deps.storage); match token_info { - TokenInfo::Existing { .. } => { - // Not much to do here. - Ok( - Response::new() - .add_attribute("cw-tokenfactory-issuer-address", issuer_addr), - ) - } TokenInfo::New(token) => { // Load the DAO address let dao = DAO.load(deps.storage)?; @@ -606,6 +567,15 @@ pub fn reply( let total_supply = initial_supply + token.initial_dao_balance.unwrap_or_default(); + // Validate active threshold absolute count if configured + if let Some(ActiveThreshold::AbsoluteCount { count }) = + ACTIVE_THRESHOLD.may_load(deps.storage)? + { + // We use initial_supply here because the DAO balance is not + // able to be staked by users. + assert_valid_absolute_count_threshold(count, initial_supply)?; + } + // Cannot instantiate with no initial token owners because it would // immediately lock the DAO. if initial_supply.is_zero() { @@ -682,7 +652,7 @@ pub fn reply( msgs.push(WasmMsg::Execute { contract_addr: issuer_addr.clone(), msg: to_binary(&IssuerExecuteMsg::Mint { - to_address: dao.to_string().clone(), + to_address: dao.to_string(), amount: initial_dao_balance, })?, funds: vec![], @@ -690,20 +660,39 @@ pub fn reply( } } - // Update issuer contract owner to be the DAO + // Begin update issuer contract owner to be the DAO, this is a + // two-step ownership transfer. msgs.push(WasmMsg::Execute { contract_addr: issuer_addr.clone(), - msg: to_binary(&IssuerExecuteMsg::ChangeContractOwner { - new_owner: dao.to_string(), - })?, + msg: to_binary(&IssuerExecuteMsg::UpdateOwnership( + cw_ownable::Action::TransferOwnership { + new_owner: dao.to_string(), + expiry: None, + }, + ))?, funds: vec![], }); + // On setup success, have the DAO complete the second part of + // ownership transfer by accepting ownership in a + // ModuleInstantiateCallback. + let callback = to_binary(&ModuleInstantiateCallback { + msgs: vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: issuer_addr.clone(), + msg: to_binary(&IssuerExecuteMsg::UpdateOwnership( + cw_ownable::Action::AcceptOwnership {}, + ))?, + funds: vec![], + })], + })?; + Ok(Response::new() .add_attribute("cw-tokenfactory-issuer-address", issuer_addr) .add_attribute("denom", denom) - .add_messages(msgs)) + .add_messages(msgs) + .set_data(callback)) } + _ => unreachable!(), } } _ => Err(ContractError::UnknownReplyId { id: msg.id }), diff --git a/contracts/voting/dao-voting-token-factory-staked/src/error.rs b/contracts/voting/dao-voting-token-staked/src/error.rs similarity index 70% rename from contracts/voting/dao-voting-token-factory-staked/src/error.rs rename to contracts/voting/dao-voting-token-staked/src/error.rs index 2a5425e0b..27bea213a 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/error.rs +++ b/contracts/voting/dao-voting-token-staked/src/error.rs @@ -1,5 +1,6 @@ use cosmwasm_std::StdError; use cw_utils::{ParseReplyError, PaymentError}; +use dao_voting::threshold::ActiveThresholdError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -8,19 +9,19 @@ pub enum ContractError { Std(#[from] StdError), #[error(transparent)] - PaymentError(#[from] PaymentError), + ActiveThresholdError(#[from] ActiveThresholdError), #[error(transparent)] - ParseReplyError(#[from] ParseReplyError), + HookError(#[from] cw_hooks::HookError), #[error(transparent)] - HookError(#[from] cw_hooks::HookError), + PaymentError(#[from] PaymentError), - #[error("Absolute count threshold cannot be greater than the total token supply")] - InvalidAbsoluteCount {}, + #[error(transparent)] + ParseReplyError(#[from] ParseReplyError), - #[error("Active threshold percentage must be greater than 0 and less than 1")] - InvalidActivePercentage {}, + #[error(transparent)] + UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), #[error("Initial governance token balances must not be empty")] InitialBalancesError {}, @@ -28,9 +29,6 @@ pub enum ContractError { #[error("Can only unstake less than or equal to the amount you have staked")] InvalidUnstakeAmount {}, - #[error("Invalid unstaking duration, unstaking duration cannot be 0")] - InvalidUnstakingDuration {}, - #[error("Nothing to claim")] NothingToClaim {}, @@ -43,9 +41,6 @@ pub enum ContractError { #[error("Got a submessage reply with unknown id: {id}")] UnknownReplyId { id: u64 }, - #[error("Active threshold count must be greater than zero")] - ZeroActiveCount {}, - #[error("Amount being unstaked must be non-zero")] ZeroUnstake {}, } diff --git a/contracts/voting/dao-voting-native-staked/src/lib.rs b/contracts/voting/dao-voting-token-staked/src/lib.rs similarity index 92% rename from contracts/voting/dao-voting-native-staked/src/lib.rs rename to contracts/voting/dao-voting-token-staked/src/lib.rs index 6c512e72b..d1800adbc 100644 --- a/contracts/voting/dao-voting-native-staked/src/lib.rs +++ b/contracts/voting/dao-voting-token-staked/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; mod error; -pub mod hooks; pub mod msg; pub mod state; diff --git a/contracts/voting/dao-voting-token-factory-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs similarity index 97% rename from contracts/voting/dao-voting-token-factory-staked/src/msg.rs rename to contracts/voting/dao-voting-token-staked/src/msg.rs index 122b18148..e08123680 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -28,6 +28,8 @@ pub struct NewDenomMetadata { #[cw_serde] pub struct NewTokenInfo { + /// The code id of the cw-tokenfactory-issuer contract + pub token_issuer_code_id: u64, /// The subdenom of the token to create, will also be used as an alias /// for the denom. The Token Factory denom will have the format of /// factory/{contract_address}/{subdenom} @@ -45,7 +47,6 @@ pub enum TokenInfo { /// Uses an existing Token Factory token and creates a new issuer contract. /// Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, /// must be done manually. - /// Note, for chain controlled denoms or IBC tokens use dao-voting-native-staked. Existing { /// Token factory denom denom: String, @@ -57,8 +58,6 @@ pub enum TokenInfo { #[cw_serde] pub struct InstantiateMsg { - /// The code id of the cw-tokenfactory-issuer contract - pub token_issuer_code_id: u64, /// New or existing native token to use for voting power. pub token_info: TokenInfo, /// How long until the tokens become liquid again diff --git a/contracts/voting/dao-voting-token-factory-staked/src/state.rs b/contracts/voting/dao-voting-token-staked/src/state.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/state.rs rename to contracts/voting/dao-voting-token-staked/src/state.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/mod.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/mod.rs rename to contracts/voting/dao-voting-token-staked/src/tests/mod.rs diff --git a/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs new file mode 100644 index 000000000..14f00389d --- /dev/null +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs @@ -0,0 +1 @@ +mod tests; diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs similarity index 81% rename from contracts/voting/dao-voting-native-staked/src/tests.rs rename to contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs index 75a3b5672..ab56d26f1 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs @@ -1,15 +1,14 @@ use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; -use crate::error::ContractError; use crate::msg::{ DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, - QueryMsg, StakerBalanceResponse, + QueryMsg, StakerBalanceResponse, TokenInfo, }; use crate::state::Config; use cosmwasm_std::testing::{mock_dependencies, mock_env}; use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cw_controllers::ClaimsResponse; use cw_multi_test::{ - custom_app, next_block, App, AppResponse, Contract, ContractWrapper, Executor, + next_block, App, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, }; use cw_utils::Duration; use dao_interface::voting::{ @@ -24,70 +23,75 @@ const DENOM: &str = "ujuno"; const INVALID_DENOM: &str = "uinvalid"; const ODD_DENOM: &str = "uodd"; +fn hook_counter_contract() -> Box> { + let contract = ContractWrapper::new( + dao_proposal_hook_counter::contract::execute, + dao_proposal_hook_counter::contract::instantiate, + dao_proposal_hook_counter::contract::query, + ); + Box::new(contract) +} + fn staking_contract() -> Box> { let contract = ContractWrapper::new( crate::contract::execute, crate::contract::instantiate, crate::contract::query, - ); + ) + .with_reply(crate::contract::reply) + .with_migrate(crate::contract::migrate); Box::new(contract) } fn mock_app() -> App { - custom_app(|r, _a, s| { - r.bank - .init_balance( - s, - &Addr::unchecked(DAO_ADDR), - vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - ) - .unwrap(); - r.bank - .init_balance( - s, - &Addr::unchecked(ADDR1), - vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: ODD_DENOM.to_string(), - amount: Uint128::new(5), - }, - ], - ) - .unwrap(); - r.bank - .init_balance( - s, - &Addr::unchecked(ADDR2), - vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - ) - .unwrap(); - }) + let mut app = App::default(); + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: DAO_ADDR.to_string(), + amount: vec![ + Coin { + denom: DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: Uint128::new(10000), + }, + ], + })) + .unwrap(); + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: ADDR1.to_string(), + amount: vec![ + Coin { + denom: DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: ODD_DENOM.to_string(), + amount: Uint128::new(5), + }, + ], + })) + .unwrap(); + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: ADDR2.to_string(), + amount: vec![ + Coin { + denom: DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: Uint128::new(10000), + }, + ], + })) + .unwrap(); + app } fn instantiate_staking(app: &mut App, staking_id: u64, msg: InstantiateMsg) -> Addr { @@ -197,44 +201,63 @@ fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { } #[test] -fn test_instantiate() { +fn test_instantiate_existing() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); // Populated fields - let _addr = instantiate_staking( + instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, ); // Non populated fields - let _addr = instantiate_staking( + let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: None, active_threshold: None, }, ); + + let denom: DenomResponse = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::Denom {}) + .unwrap(); + assert_eq!( + denom, + DenomResponse { + denom: DENOM.to_string() + } + ); } #[test] #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] fn test_instantiate_invalid_unstaking_duration_height() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); - // Populated fields with height + // Populated fields instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(0)), active_threshold: Some(ActiveThreshold::AbsoluteCount { count: Uint128::new(1), @@ -247,6 +270,7 @@ fn test_instantiate_invalid_unstaking_duration_height() { #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] fn test_instantiate_invalid_unstaking_duration_time() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); // Populated fields with height @@ -254,7 +278,9 @@ fn test_instantiate_invalid_unstaking_duration_time() { &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Time(0)), active_threshold: Some(ActiveThreshold::AbsoluteCount { count: Uint128::new(1), @@ -267,12 +293,15 @@ fn test_instantiate_invalid_unstaking_duration_time() { #[should_panic(expected = "Must send reserve token 'ujuno'")] fn test_stake_invalid_denom() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -285,12 +314,15 @@ fn test_stake_invalid_denom() { #[test] fn test_stake_valid_denom() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -305,12 +337,15 @@ fn test_stake_valid_denom() { #[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] fn test_unstake_none_staked() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -323,12 +358,15 @@ fn test_unstake_none_staked() { #[should_panic(expected = "Amount being unstaked must be non-zero")] fn test_unstake_zero_tokens() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -341,12 +379,15 @@ fn test_unstake_zero_tokens() { #[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] fn test_unstake_invalid_balance() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -363,12 +404,15 @@ fn test_unstake_invalid_balance() { #[test] fn test_unstake() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -397,12 +441,15 @@ fn test_unstake() { #[test] fn test_unstake_no_unstaking_duration() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: None, active_threshold: None, }, @@ -433,12 +480,15 @@ fn test_unstake_no_unstaking_duration() { #[should_panic(expected = "Nothing to claim")] fn test_claim_no_claims() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -451,12 +501,15 @@ fn test_claim_no_claims() { #[should_panic(expected = "Nothing to claim")] fn test_claim_claim_not_reached() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -477,12 +530,15 @@ fn test_claim_claim_not_reached() { #[test] fn test_claim() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -527,30 +583,36 @@ fn test_claim() { #[should_panic(expected = "Unauthorized")] fn test_update_config_invalid_sender() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, ); // From ADDR2, so not owner or manager - update_config(&mut app, addr, ADDR1, Some(Duration::Height(10))).unwrap(); + update_config(&mut app, addr, ADDR2, Some(Duration::Height(10))).unwrap(); } #[test] -fn test_update_config_as_dao() { +fn test_update_config_as_owner() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -562,7 +624,6 @@ fn test_update_config_as_dao() { let config = get_config(&mut app, addr); assert_eq!( Config { - denom: DENOM.to_string(), unstaking_duration: Some(Duration::Height(10)), }, config @@ -573,12 +634,15 @@ fn test_update_config_as_dao() { #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] fn test_update_config_invalid_duration() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -591,12 +655,15 @@ fn test_update_config_invalid_duration() { #[test] fn test_query_dao() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -607,34 +674,18 @@ fn test_query_dao() { assert_eq!(dao, Addr::unchecked(DAO_ADDR)); } -#[test] -fn test_query_denom() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::GetDenom {}; - let denom: DenomResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(denom.denom, DENOM.to_string()); -} - #[test] fn test_query_info() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -642,18 +693,21 @@ fn test_query_info() { let msg = QueryMsg::Info {}; let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(resp.info.contract, "crates.io:dao-voting-native-staked"); + assert_eq!(resp.info.contract, "crates.io:dao-voting-token-staked"); } #[test] fn test_query_claims() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -683,12 +737,15 @@ fn test_query_claims() { #[test] fn test_query_get_config() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -699,7 +756,6 @@ fn test_query_get_config() { config, Config { unstaking_duration: Some(Duration::Height(5)), - denom: DENOM.to_string(), } ) } @@ -707,12 +763,15 @@ fn test_query_get_config() { #[test] fn test_voting_power_queries() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -812,12 +871,15 @@ fn test_voting_power_queries() { #[test] fn test_query_list_stakers() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -896,12 +958,15 @@ fn test_query_list_stakers() { #[should_panic(expected = "Active threshold count must be greater than zero")] fn test_instantiate_zero_active_threshold_count() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::AbsoluteCount { count: Uint128::zero(), @@ -913,13 +978,16 @@ fn test_instantiate_zero_active_threshold_count() { #[test] fn test_active_threshold_absolute_count() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::AbsoluteCount { count: Uint128::new(100), @@ -949,12 +1017,15 @@ fn test_active_threshold_absolute_count() { #[test] fn test_active_threshold_percent() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(20), @@ -984,12 +1055,15 @@ fn test_active_threshold_percent() { #[test] fn test_active_threshold_percent_rounds_up() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: ODD_DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: ODD_DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(50), @@ -1028,12 +1102,15 @@ fn test_active_threshold_percent_rounds_up() { #[test] fn test_active_threshold_none() { let mut app = App::default(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -1050,12 +1127,15 @@ fn test_active_threshold_none() { #[test] fn test_update_active_threshold() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -1083,7 +1163,7 @@ fn test_update_active_threshold() { let resp: ActiveThresholdResponse = app .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::ActiveThreshold {}) + .query_wasm_smart(addr, &QueryMsg::ActiveThreshold {}) .unwrap(); assert_eq!( resp.active_threshold, @@ -1091,43 +1171,21 @@ fn test_update_active_threshold() { count: Uint128::new(100) }) ); - - // Can't set threshold to invalid value - let msg = ExecuteMsg::UpdateActiveThreshold { - new_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(120), - }), - }; - let err: ContractError = app - .execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::InvalidActivePercentage {}); - - // Remove threshold - let msg = ExecuteMsg::UpdateActiveThreshold { - new_threshold: None, - }; - app.execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) - .unwrap(); - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!(resp.active_threshold, None); } #[test] #[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] fn test_active_threshold_percentage_gt_100() { let mut app = App::default(); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(120), @@ -1140,12 +1198,15 @@ fn test_active_threshold_percentage_gt_100() { #[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] fn test_active_threshold_percentage_lte_0() { let mut app = App::default(); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::Percentage { percent: Decimal::percent(0), @@ -1158,15 +1219,18 @@ fn test_active_threshold_percentage_lte_0() { #[should_panic(expected = "Absolute count threshold cannot be greater than the total token supply")] fn test_active_threshold_absolute_count_invalid() { let mut app = App::default(); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(301), + count: Uint128::new(30001), }), }, ); @@ -1175,12 +1239,16 @@ fn test_active_threshold_absolute_count_invalid() { #[test] fn test_add_remove_hooks() { let mut app = App::default(); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - denom: DENOM.to_string(), + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, unstaking_duration: Some(Duration::Height(5)), active_threshold: None, }, @@ -1193,21 +1261,6 @@ fn test_add_remove_hooks() { .unwrap(); assert_eq!(resp.hooks, Vec::::new()); - // Non-owner can't add hook - let err: ContractError = app - .execute_contract( - Addr::unchecked(ADDR2), - addr.clone(), - &ExecuteMsg::AddHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::Unauthorized {}); - // Add a hook. app.execute_contract( Addr::unchecked(DAO_ADDR), @@ -1226,21 +1279,6 @@ fn test_add_remove_hooks() { .unwrap(); assert_eq!(resp.hooks, vec!["hook".to_string()]); - // Non-owner can't remove hook - let err: ContractError = app - .execute_contract( - Addr::unchecked(ADDR2), - addr.clone(), - &ExecuteMsg::RemoveHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::Unauthorized {}); - // Remove hook. app.execute_contract( Addr::unchecked(DAO_ADDR), @@ -1260,10 +1298,68 @@ fn test_add_remove_hooks() { assert_eq!(resp.hooks, Vec::::new()); } +#[test] +fn test_staking_hooks() { + let mut app = mock_app(); + + let staking_id = app.store_code(staking_contract()); + let hook_id = app.store_code(hook_counter_contract()); + + let hook = app + .instantiate_contract( + hook_id, + Addr::unchecked(DAO_ADDR), + &dao_proposal_hook_counter::msg::InstantiateMsg { + should_error: false, + }, + &[], + "hook counter".to_string(), + None, + ) + .unwrap(); + + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Add a staking hook. + app.execute_contract( + Addr::unchecked(DAO_ADDR), + addr.clone(), + &ExecuteMsg::AddHook { + addr: hook.to_string(), + }, + &[], + ) + .unwrap(); + + // Stake some tokens + let res = stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + + // Make sure hook is included in response + assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); + + app.update_block(next_block); + + // Unstake some + let res = unstake_tokens(&mut app, addr, ADDR1, 75).unwrap(); + + // Make sure hook is included in response + assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); +} + #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs similarity index 73% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs rename to contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs index 08573a542..2c1db58f3 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs @@ -1,16 +1,34 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Uint128}; use cw_tokenfactory_issuer::msg::DenomUnit; +use dao_voting::threshold::{ActiveThreshold, ActiveThresholdError}; use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_test_tube::{Account, OsmosisTestApp}; use crate::{ msg::{ExecuteMsg, InitialBalance, InstantiateMsg, NewDenomMetadata, NewTokenInfo, TokenInfo}, - tests::test_tube::test_env::TfDaoVotingContract, + tests::test_tube::test_env::TokenVotingContract, ContractError, }; use super::test_env::{TestEnv, TestEnvBuilder}; +#[test] +fn test_full_integration_correct_setup() { + let app = OsmosisTestApp::new(); + let env = TestEnvBuilder::new(); + let TestEnv { dao, tf_issuer, .. } = env.full_dao_setup(&app); + + // Issuer owner should be set to the DAO + let issuer_admin = tf_issuer + .query::>(&cw_tokenfactory_issuer::msg::QueryMsg::Ownership {}) + .unwrap() + .owner; + assert_eq!( + issuer_admin, + Some(Addr::unchecked(dao.unwrap().contract_addr)) + ); +} + #[test] fn test_stake_unstake_new_denom() { let app = OsmosisTestApp::new(); @@ -19,7 +37,7 @@ fn test_stake_unstake_new_denom() { vp_contract, accounts, .. - } = env.default_setup(&app); + } = env.full_dao_setup(&app); let denom = vp_contract.query_denom().unwrap().denom; @@ -80,8 +98,8 @@ fn test_instantiate_no_dao_balance() { let vp_contract = env .instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "ucat".to_string(), metadata: Some(NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -143,8 +161,8 @@ fn test_instantiate_no_metadata() { env.instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "ucat".to_string(), metadata: None, initial_balances: vec![InitialBalance { @@ -173,8 +191,8 @@ fn test_instantiate_invalid_metadata_fails() { env.instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "cat".to_string(), metadata: Some(NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -202,6 +220,57 @@ fn test_instantiate_invalid_metadata_fails() { .unwrap_err(); } +#[test] +fn test_instantiate_invalid_active_threshold_count_fails() { + let app = OsmosisTestApp::new(); + let env = TestEnvBuilder::new().default_setup(&app); + let tf_issuer_id = env.get_tf_issuer_code_id(); + + let dao = app + .init_account(&[Coin::new(100000000000, "uosmo")]) + .unwrap(); + + let err = env + .instantiate( + &InstantiateMsg { + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, + subdenom: "cat".to_string(), + metadata: Some(NewDenomMetadata { + description: "Awesome token, get it meow!".to_string(), + additional_denom_units: Some(vec![DenomUnit { + denom: "cat".to_string(), + // Exponent 0 is automatically set + exponent: 0, + aliases: vec![], + }]), + display: "cat".to_string(), + name: "Cat Token".to_string(), + symbol: "CAT".to_string(), + }), + initial_balances: vec![InitialBalance { + amount: Uint128::new(100), + address: env.accounts[0].address(), + }], + initial_dao_balance: None, + }), + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(1000), + }), + }, + dao, + ) + .unwrap_err(); + + assert_eq!( + err, + TokenVotingContract::execute_submessage_error(ContractError::ActiveThresholdError( + ActiveThresholdError::InvalidAbsoluteCount {} + )) + ); +} + #[test] fn test_instantiate_no_initial_balances_fails() { let app = OsmosisTestApp::new(); @@ -214,8 +283,8 @@ fn test_instantiate_no_initial_balances_fails() { let err = env .instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "ucat".to_string(), metadata: Some(NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -239,6 +308,6 @@ fn test_instantiate_no_initial_balances_fails() { .unwrap_err(); assert_eq!( err, - TfDaoVotingContract::execute_submessage_error(ContractError::InitialBalancesError {}) + TokenVotingContract::execute_submessage_error(ContractError::InitialBalancesError {}) ); } diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/mod.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/mod.rs rename to contracts/voting/dao-voting-token-staked/src/tests/test_tube/mod.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/test_env.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs similarity index 60% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/test_env.rs rename to contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs index 26b706de5..5287cec34 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/test_env.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs @@ -7,11 +7,22 @@ use crate::{ ContractError, }; -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{to_binary, Addr, Coin, Decimal, Uint128}; use cw_tokenfactory_issuer::msg::{DenomResponse, DenomUnit}; use cw_utils::Duration; -use dao_interface::voting::{IsActiveResponse, VotingPowerAtHeightResponse}; -use dao_testing::test_tube::cw_tokenfactory_issuer::TokenfactoryIssuer; +use dao_interface::{ + msg::QueryMsg as DaoQueryMsg, + state::{Admin, ModuleInstantiateInfo, ProposalModule}, + voting::{IsActiveResponse, VotingPowerAtHeightResponse}, +}; +use dao_voting::{ + pre_propose::PreProposeInfo, threshold::PercentageThreshold, threshold::Threshold, +}; + +use dao_testing::test_tube::{ + cw_tokenfactory_issuer::TokenfactoryIssuer, dao_dao_core::DaoCore, + dao_proposal_single::DaoProposalSingle, +}; use dao_voting::threshold::ActiveThreshold; use osmosis_std::types::{ cosmos::bank::v1beta1::QueryAllBalancesRequest, cosmwasm::wasm::v1::MsgExecuteContractResponse, @@ -28,7 +39,9 @@ pub const JUNO: &str = "ujuno"; pub struct TestEnv<'a> { pub app: &'a OsmosisTestApp, - pub vp_contract: TfDaoVotingContract<'a>, + pub dao: Option>, + pub proposal_single: Option>, + pub vp_contract: TokenVotingContract<'a>, pub tf_issuer: TokenfactoryIssuer<'a>, pub accounts: Vec, } @@ -38,8 +51,8 @@ impl<'a> TestEnv<'a> { &self, msg: &InstantiateMsg, signer: SigningAccount, - ) -> Result { - TfDaoVotingContract::<'a>::instantiate(self.app, self.vp_contract.code_id, msg, &signer) + ) -> Result { + TokenVotingContract::<'a>::instantiate(self.app, self.vp_contract.code_id, msg, &signer) } pub fn get_tf_issuer_code_id(&self) -> u64 { @@ -100,6 +113,7 @@ impl TestEnvBuilder { } } + // Minimal default setup with just the key contracts pub fn default_setup(self, app: &'_ OsmosisTestApp) -> TestEnv<'_> { let accounts = app .init_accounts(&[Coin::new(1000000000000000u128, "uosmo")], 10) @@ -115,11 +129,11 @@ impl TestEnvBuilder { let issuer_id = TokenfactoryIssuer::upload(app, &accounts[0]).unwrap(); - let vp_contract = TfDaoVotingContract::deploy( + let vp_contract = TokenVotingContract::deploy( app, &InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: issuer_id, subdenom: DENOM.to_string(), metadata: Some(crate::msg::NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -145,45 +159,129 @@ impl TestEnvBuilder { .unwrap(); let issuer_addr = - TfDaoVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); + TokenVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); let tf_issuer = TokenfactoryIssuer::new_with_values(app, issuer_id, issuer_addr).unwrap(); TestEnv { app, - vp_contract, - tf_issuer, accounts, + dao: None, + proposal_single: None, + tf_issuer, + vp_contract, } } - pub fn build(self, app: &'_ OsmosisTestApp) -> TestEnv<'_> { - let accounts = self.accounts; + // Full DAO setup + pub fn full_dao_setup(self, app: &'_ OsmosisTestApp) -> TestEnv<'_> { + let accounts = app + .init_accounts(&[Coin::new(1000000000000000u128, "uosmo")], 10) + .unwrap(); - let vp_contract = TfDaoVotingContract::deploy( + let initial_balances: Vec = accounts + .iter() + .map(|acc| InitialBalance { + address: acc.address(), + amount: Uint128::new(100), + }) + .collect(); + + // Upload all needed code ids + let issuer_id = TokenfactoryIssuer::upload(app, &accounts[0]).unwrap(); + let vp_contract_id = TokenVotingContract::upload(app, &accounts[0]).unwrap(); + let proposal_single_id = DaoProposalSingle::upload(app, &accounts[0]).unwrap(); + + let msg = dao_interface::msg::InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that makes DAO tooling".to_string(), + image_url: None, + automatically_add_cw20s: false, + automatically_add_cw721s: false, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: vp_contract_id, + msg: to_binary(&InstantiateMsg { + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: issuer_id, + subdenom: DENOM.to_string(), + metadata: Some(crate::msg::NewDenomMetadata { + description: "Awesome token, get it meow!".to_string(), + additional_denom_units: Some(vec![DenomUnit { + denom: "cat".to_string(), + exponent: 6, + aliases: vec![], + }]), + display: "cat".to_string(), + name: "Cat Token".to_string(), + symbol: "CAT".to_string(), + }), + initial_balances, + initial_dao_balance: Some(Uint128::new(900)), + }), + unstaking_duration: Some(Duration::Time(2)), + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(75), + }), + }) + .unwrap(), + admin: Some(Admin::CoreModule {}), + label: "DAO DAO Voting Module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: proposal_single_id, + msg: to_binary(&dao_proposal_single::msg::InstantiateMsg { + min_voting_period: None, + threshold: Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(35)), + }, + max_voting_period: Duration::Time(432000), + allow_revoting: false, + only_members_execute: true, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + }) + .unwrap(), + admin: Some(Admin::CoreModule {}), + label: "DAO DAO Proposal Module".to_string(), + }], + initial_items: None, + }; + + // Instantiate DAO + let dao = DaoCore::new(app, &msg, &accounts[0]).unwrap(); + + // Get voting module address, setup vp_contract helper + let vp_addr: Addr = dao.query(&DaoQueryMsg::VotingModule {}).unwrap(); + let vp_contract = + TokenVotingContract::new_with_values(app, vp_contract_id, vp_addr.to_string()).unwrap(); + + // Get proposal module address, setup proposal_single helper + let proposal_modules: Vec = dao + .query(&DaoQueryMsg::ProposalModules { + limit: None, + start_after: None, + }) + .unwrap(); + let proposal_single = DaoProposalSingle::new_with_values( app, - self.instantiate_msg - .as_ref() - .expect("instantiate msg not set"), - &accounts[0], + proposal_single_id, + proposal_modules[0].address.to_string(), ) .unwrap(); + // Get issuer address, setup tf_issuer helper let issuer_addr = - TfDaoVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); - - let tf_issuer = TokenfactoryIssuer::new_with_values( - app, - self.instantiate_msg - .expect("instantiate msg not set") - .token_issuer_code_id, - issuer_addr, - ) - .unwrap(); + TokenVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); + let tf_issuer = TokenfactoryIssuer::new_with_values(app, issuer_id, issuer_addr).unwrap(); TestEnv { app, + dao: Some(dao), vp_contract, + proposal_single: Some(proposal_single), tf_issuer, accounts, } @@ -210,13 +308,13 @@ impl TestEnvBuilder { } #[derive(Debug)] -pub struct TfDaoVotingContract<'a> { +pub struct TokenVotingContract<'a> { pub app: &'a OsmosisTestApp, pub contract_addr: String, pub code_id: u64, } -impl<'a> TfDaoVotingContract<'a> { +impl<'a> TokenVotingContract<'a> { pub fn deploy( app: &'a OsmosisTestApp, instantiate_msg: &InstantiateMsg, @@ -248,6 +346,30 @@ impl<'a> TfDaoVotingContract<'a> { }) } + pub fn new_with_values( + app: &'a OsmosisTestApp, + code_id: u64, + contract_addr: String, + ) -> Result { + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + /// uploads contract and returns a code ID + pub fn upload(app: &OsmosisTestApp, signer: &SigningAccount) -> Result { + let wasm = Wasm::new(app); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + Ok(code_id) + } + pub fn instantiate( app: &'a OsmosisTestApp, code_id: u64, @@ -319,7 +441,7 @@ impl<'a> TfDaoVotingContract<'a> { .join("..") .join("..") .join("artifacts") - .join("dao_voting_token_factory_staked.wasm"), + .join("dao_voting_token_staked.wasm"), ); match byte_code { Ok(byte_code) => byte_code, @@ -330,7 +452,7 @@ impl<'a> TfDaoVotingContract<'a> { .join("..") .join("..") .join("artifacts") - .join("dao_voting_token_factory_staked-aarch64.wasm"), + .join("dao_voting_token_staked-aarch64.wasm"), ) .unwrap(), } diff --git a/packages/cw-hooks/README.md b/packages/cw-hooks/README.md index d597f945c..f078a6069 100644 --- a/packages/cw-hooks/README.md +++ b/packages/cw-hooks/README.md @@ -1,7 +1,7 @@ # CosmWasm DAO Hooks This package provides shared hook functionality used for -[proposal](../dao-proposal-hooks) and [vote](../dao-vote-hooks) hooks. +[dao-hooks](../dao-hooks). It deviates from other CosmWasm hook packages in that hooks can be modified based on their index in the hook list AND based on the diff --git a/packages/dao-dao-macros/src/lib.rs b/packages/dao-dao-macros/src/lib.rs index 4c11c8b71..8b2dddda9 100644 --- a/packages/dao-dao-macros/src/lib.rs +++ b/packages/dao-dao-macros/src/lib.rs @@ -26,7 +26,7 @@ fn merge_variants(metadata: TokenStream, left: TokenStream, right: TokenStream) }), ) = (&mut left.data, right.data) { - variants.extend(to_add.into_iter()); + variants.extend(to_add); quote! { #left }.into() } else { diff --git a/packages/dao-vote-hooks/Cargo.toml b/packages/dao-hooks/Cargo.toml similarity index 59% rename from packages/dao-vote-hooks/Cargo.toml rename to packages/dao-hooks/Cargo.toml index 067e11158..45ea0f640 100644 --- a/packages/dao-vote-hooks/Cargo.toml +++ b/packages/dao-hooks/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "dao-vote-hooks" -authors = ["ekez ekez@withoutdoing.com"] -description = "A package for managing vote hooks." +name = "dao-hooks" +authors = ["ekez ekez@withoutdoing.com", "Jake Hartnell "] +description = "A package for managing DAO vote, proposal, and stake hooks." edition = { workspace = true } license = { workspace = true } repository = { workspace = true } @@ -10,5 +10,6 @@ version = { workspace = true } [dependencies] cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } +cw4 = { workspace = true } cw-hooks = { workspace = true } dao-voting = { workspace = true } diff --git a/packages/dao-hooks/README.md b/packages/dao-hooks/README.md new file mode 100644 index 000000000..b2e8686c8 --- /dev/null +++ b/packages/dao-hooks/README.md @@ -0,0 +1,21 @@ +# DAO Hooks +This package provides an interface for managing and dispatching proposal, +staking, and voting related hooks. + +### NFT Stake Hooks +Staking hooks are fired when NFTs are staked or unstaked in a DAO. + +### Proposal Hooks +There are two types of proposal hooks: +- **New Proposal Hook:** fired when a new proposal is created. +- **Proposal Staus Changed Hook:** fired when a proposal's status changes. + +Our wiki contains more info on [Proposal Hooks](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). + +### Stake Hooks +Staking hooks are fired when tokens are staked or unstaked in a DAO. + +### Vote Hooks +Vote hooks are fired when new votes are cast. + +You can read more about vote hooks in our [wiki](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). diff --git a/packages/dao-hooks/src/all_hooks.rs b/packages/dao-hooks/src/all_hooks.rs new file mode 100644 index 000000000..3dfe9ea4a --- /dev/null +++ b/packages/dao-hooks/src/all_hooks.rs @@ -0,0 +1,23 @@ +use cosmwasm_schema::cw_serde; +use cw4::MemberChangedHookMsg; + +use crate::nft_stake::NftStakeChangedHookMsg; +use crate::proposal::ProposalHookMsg; +use crate::stake::StakeChangedHookMsg; +use crate::vote::VoteHookMsg; + +/// An enum representing all possible DAO hooks. +#[cw_serde] +pub enum DaoHooks { + /// Called when a member is added or removed + /// to a cw4-groups or cw721-roles contract. + MemberChangedHook(MemberChangedHookMsg), + /// Called when NFTs are staked or unstaked. + NftStakeChangeHook(NftStakeChangedHookMsg), + /// Called when a proposal status changes. + ProposalHook(ProposalHookMsg), + /// Called when tokens are staked or unstaked. + StakeChangeHook(StakeChangedHookMsg), + /// Called when a vote is cast. + VoteHook(VoteHookMsg), +} diff --git a/packages/dao-hooks/src/lib.rs b/packages/dao-hooks/src/lib.rs new file mode 100644 index 000000000..41e5ef970 --- /dev/null +++ b/packages/dao-hooks/src/lib.rs @@ -0,0 +1,10 @@ +#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] + +mod all_hooks; +pub mod nft_stake; +pub mod proposal; +pub mod stake; +pub mod vote; + +pub use all_hooks::DaoHooks; +pub use cw4::MemberChangedHookMsg; diff --git a/packages/dao-hooks/src/nft_stake.rs b/packages/dao-hooks/src/nft_stake.rs new file mode 100644 index 000000000..9cebad29b --- /dev/null +++ b/packages/dao-hooks/src/nft_stake.rs @@ -0,0 +1,58 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, WasmMsg}; +use cw_hooks::Hooks; + +/// An enum representing NFT staking hooks. +#[cw_serde] +pub enum NftStakeChangedHookMsg { + Stake { addr: Addr, token_id: String }, + Unstake { addr: Addr, token_ids: Vec }, +} + +/// Prepares NftStakeChangedHookMsg::Stake hook SubMsgs, +/// containing the address and the token_id staked. +pub fn stake_nft_hook_msgs( + hooks: Hooks, + storage: &dyn Storage, + addr: Addr, + token_id: String, +) -> StdResult> { + let msg = to_binary(&NftStakeChangedExecuteMsg::NftStakeChangeHook( + NftStakeChangedHookMsg::Stake { addr, token_id }, + ))?; + hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.into_string(), + msg: msg.clone(), + funds: vec![], + }; + Ok(SubMsg::new(execute)) + }) +} + +/// Prepares NftStakeChangedHookMsg::Unstake hook SubMsgs, +/// containing the address and the token_ids unstaked. +pub fn unstake_nft_hook_msgs( + hooks: Hooks, + storage: &dyn Storage, + addr: Addr, + token_ids: Vec, +) -> StdResult> { + let msg = to_binary(&NftStakeChangedExecuteMsg::NftStakeChangeHook( + NftStakeChangedHookMsg::Unstake { addr, token_ids }, + ))?; + + hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.into_string(), + msg: msg.clone(), + funds: vec![], + }; + Ok(SubMsg::new(execute)) + }) +} + +#[cw_serde] +pub enum NftStakeChangedExecuteMsg { + NftStakeChangeHook(NftStakeChangedHookMsg), +} diff --git a/packages/dao-proposal-hooks/src/lib.rs b/packages/dao-hooks/src/proposal.rs similarity index 92% rename from packages/dao-proposal-hooks/src/lib.rs rename to packages/dao-hooks/src/proposal.rs index 9e0142a88..0b95758ad 100644 --- a/packages/dao-proposal-hooks/src/lib.rs +++ b/packages/dao-hooks/src/proposal.rs @@ -1,10 +1,11 @@ -#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] - use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; use dao_voting::reply::mask_proposal_hook_index; +/// An enum representing proposal hook messages. +/// Either a new propsoal hook, fired when a new proposal is created, +/// or a proposal status hook, fired when a proposal changes status. #[cw_serde] pub enum ProposalHookMsg { NewProposal { @@ -18,12 +19,6 @@ pub enum ProposalHookMsg { }, } -// This is just a helper to properly serialize the above message -#[cw_serde] -pub enum ProposalHookExecuteMsg { - ProposalHook(ProposalHookMsg), -} - /// Prepares new proposal hook messages. These messages reply on error /// and have even reply IDs. /// IDs are set to even numbers to then be interleaved with the vote hooks. @@ -92,3 +87,8 @@ pub fn proposal_status_changed_hooks( Ok(messages) } + +#[cw_serde] +pub enum ProposalHookExecuteMsg { + ProposalHook(ProposalHookMsg), +} diff --git a/contracts/voting/dao-voting-native-staked/src/hooks.rs b/packages/dao-hooks/src/stake.rs similarity index 73% rename from contracts/voting/dao-voting-native-staked/src/hooks.rs rename to packages/dao-hooks/src/stake.rs index a04d3b043..6161bf2c9 100644 --- a/contracts/voting/dao-voting-native-staked/src/hooks.rs +++ b/packages/dao-hooks/src/stake.rs @@ -1,14 +1,18 @@ -use crate::state::HOOKS; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; +use cw_hooks::Hooks; +/// An enum representing staking hooks. #[cw_serde] pub enum StakeChangedHookMsg { Stake { addr: Addr, amount: Uint128 }, Unstake { addr: Addr, amount: Uint128 }, } +/// Prepares StakeChangedHookMsg::Stake hook SubMsgs, +/// containing the address and the amount staked. pub fn stake_hook_msgs( + hooks: Hooks, storage: &dyn Storage, addr: Addr, amount: Uint128, @@ -16,7 +20,7 @@ pub fn stake_hook_msgs( let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( StakeChangedHookMsg::Stake { addr, amount }, ))?; - HOOKS.prepare_hooks(storage, |a| { + hooks.prepare_hooks(storage, |a| { let execute = WasmMsg::Execute { contract_addr: a.to_string(), msg: msg.clone(), @@ -26,7 +30,10 @@ pub fn stake_hook_msgs( }) } +/// Prepares StakeChangedHookMsg::Unstake hook SubMsgs, +/// containing the address and the amount unstaked. pub fn unstake_hook_msgs( + hooks: Hooks, storage: &dyn Storage, addr: Addr, amount: Uint128, @@ -34,7 +41,7 @@ pub fn unstake_hook_msgs( let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( StakeChangedHookMsg::Unstake { addr, amount }, ))?; - HOOKS.prepare_hooks(storage, |a| { + hooks.prepare_hooks(storage, |a| { let execute = WasmMsg::Execute { contract_addr: a.to_string(), msg: msg.clone(), @@ -44,8 +51,7 @@ pub fn unstake_hook_msgs( }) } -// This is just a helper to properly serialize the above message #[cw_serde] -enum StakeChangedExecuteMsg { +pub enum StakeChangedExecuteMsg { StakeChangeHook(StakeChangedHookMsg), } diff --git a/packages/dao-vote-hooks/src/lib.rs b/packages/dao-hooks/src/vote.rs similarity index 89% rename from packages/dao-vote-hooks/src/lib.rs rename to packages/dao-hooks/src/vote.rs index cfc13aedd..b8a0dd772 100644 --- a/packages/dao-vote-hooks/src/lib.rs +++ b/packages/dao-hooks/src/vote.rs @@ -1,10 +1,9 @@ -#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] - use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; use dao_voting::reply::mask_vote_hook_index; +/// An enum representing vote hooks, fired when new votes are cast. #[cw_serde] pub enum VoteHookMsg { NewVote { @@ -14,12 +13,6 @@ pub enum VoteHookMsg { }, } -// This is just a helper to properly serialize the above message -#[cw_serde] -pub enum VoteHookExecuteMsg { - VoteHook(VoteHookMsg), -} - /// Prepares new vote hook messages. These messages reply on error /// and have even reply IDs. /// IDs are set to odd numbers to then be interleaved with the proposal hooks. @@ -48,3 +41,8 @@ pub fn new_vote_hooks( Ok(tmp) }) } + +#[cw_serde] +pub enum VoteHookExecuteMsg { + VoteHook(VoteHookMsg), +} diff --git a/packages/dao-pre-propose-base/Cargo.toml b/packages/dao-pre-propose-base/Cargo.toml index ebc864973..a0be0d404 100644 --- a/packages/dao-pre-propose-base/Cargo.toml +++ b/packages/dao-pre-propose-base/Cargo.toml @@ -24,7 +24,7 @@ cw-denom = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-hooks = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } serde = { workspace = true } diff --git a/packages/dao-proposal-hooks/Cargo.toml b/packages/dao-proposal-hooks/Cargo.toml deleted file mode 100644 index 7ecb29919..000000000 --- a/packages/dao-proposal-hooks/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "dao-proposal-hooks" -authors = ["Callum Anderson "] -description = "A package for managing proposal hooks." -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } - -[dependencies] -cosmwasm-std = { workspace = true } -cosmwasm-schema = { workspace = true } -cw-hooks = { workspace = true } -dao-voting = { workspace = true } diff --git a/packages/dao-proposal-hooks/README.md b/packages/dao-proposal-hooks/README.md deleted file mode 100644 index 2ab9ba5bb..000000000 --- a/packages/dao-proposal-hooks/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# CosmWasm DAO Proposal Hooks - -This package provides an interface for managing and dispatching -proposal hooks from a proposal module. - -There are two types of proposal hooks: -- **New Proposal Hook:** fired when a new proposal is created. -- **Proposal Staus Changed Hook:** fired when a proposal's status changes. - -Our wiki contains more info on [Proposal Hooks](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). diff --git a/packages/dao-testing/Cargo.toml b/packages/dao-testing/Cargo.toml index d7a9d0c10..0416bdc84 100644 --- a/packages/dao-testing/Cargo.toml +++ b/packages/dao-testing/Cargo.toml @@ -33,6 +33,7 @@ osmosis-test-tube = { workspace = true } rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +token-bindings = { workpsace = true } cw-core-v1 = { workspace = true, features = ["library"] } cw-hooks = { workspace = true } @@ -54,8 +55,6 @@ dao-voting-cw20-staked = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-cw721-staked = { workspace = true } dao-voting-cw721-roles = { workspace = true } -dao-voting-native-staked = { workspace = true } -dao-voting-token-factory-staked = { workspace = true } +dao-voting-token-staked = { workspace = true } voting-v1 = { workspace = true } stake-cw20-v03 = { workspace = true } -token-bindings = { workpsace = true } diff --git a/packages/dao-testing/src/contracts.rs b/packages/dao-testing/src/contracts.rs index e82c2b9c1..b2fb42b27 100644 --- a/packages/dao-testing/src/contracts.rs +++ b/packages/dao-testing/src/contracts.rs @@ -110,9 +110,9 @@ pub fn cw20_balances_voting_contract() -> Box> { pub fn native_staked_balances_voting_contract() -> Box> { let contract = ContractWrapper::new( - dao_voting_native_staked::contract::execute, - dao_voting_native_staked::contract::instantiate, - dao_voting_native_staked::contract::query, + dao_voting_token_staked::contract::execute, + dao_voting_token_staked::contract::instantiate, + dao_voting_token_staked::contract::query, ); Box::new(contract) } diff --git a/packages/dao-testing/src/test_tube/dao_dao_core.rs b/packages/dao-testing/src/test_tube/dao_dao_core.rs new file mode 100644 index 000000000..0b9f6752f --- /dev/null +++ b/packages/dao-testing/src/test_tube/dao_dao_core.rs @@ -0,0 +1,127 @@ +use cosmwasm_std::Coin; +use dao_dao_core::ContractError; +use dao_interface::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use osmosis_test_tube::{ + osmosis_std::types::cosmwasm::wasm::v1::MsgExecuteContractResponse, Account, Module, + OsmosisTestApp, RunnerError, RunnerExecuteResult, SigningAccount, Wasm, +}; +use serde::de::DeserializeOwned; +use std::fmt::Debug; +use std::path::PathBuf; + +#[derive(Debug)] +pub struct DaoCore<'a> { + pub app: &'a OsmosisTestApp, + pub code_id: u64, + pub contract_addr: String, +} + +impl<'a> DaoCore<'a> { + pub fn new( + app: &'a OsmosisTestApp, + instantiate_msg: &InstantiateMsg, + signer: &SigningAccount, + ) -> Result { + let wasm = Wasm::new(app); + let token_creation_fee = Coin::new(10000000, "uosmo"); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + let contract_addr = wasm + .instantiate( + code_id, + &instantiate_msg, + Some(&signer.address()), + None, + &[token_creation_fee], + signer, + )? + .data + .address; + + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + pub fn new_with_values( + app: &'a OsmosisTestApp, + code_id: u64, + contract_addr: String, + ) -> Result { + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + /// uploads contract and returns a code ID + pub fn upload(app: &OsmosisTestApp, signer: &SigningAccount) -> Result { + let wasm = Wasm::new(app); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + Ok(code_id) + } + + // executes + pub fn execute( + &self, + execute_msg: &ExecuteMsg, + funds: &[Coin], + signer: &SigningAccount, + ) -> RunnerExecuteResult { + let wasm = Wasm::new(self.app); + wasm.execute(&self.contract_addr, execute_msg, funds, signer) + } + + // queries + pub fn query(&self, query_msg: &QueryMsg) -> Result + where + T: DeserializeOwned, + { + let wasm = Wasm::new(self.app); + wasm.query(&self.contract_addr, query_msg) + } + + fn get_wasm_byte_code() -> Vec { + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let byte_code = std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_dao_core.wasm"), + ); + match byte_code { + Ok(byte_code) => byte_code, + // On arm processors, the above path is not found, so we try the following path + Err(_) => std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_dao_core-aarch64.wasm"), + ) + .unwrap(), + } + } + + pub fn execute_error(err: ContractError) -> RunnerError { + RunnerError::ExecuteError { + msg: format!( + "failed to execute message; message index: 0: {}: execute wasm contract failed", + err + ), + } + } +} diff --git a/packages/dao-testing/src/test_tube/dao_proposal_single.rs b/packages/dao-testing/src/test_tube/dao_proposal_single.rs new file mode 100644 index 000000000..4f6d51a96 --- /dev/null +++ b/packages/dao-testing/src/test_tube/dao_proposal_single.rs @@ -0,0 +1,129 @@ +use cosmwasm_std::Coin; +use dao_proposal_single::{ + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + ContractError, +}; +use osmosis_test_tube::{ + osmosis_std::types::cosmwasm::wasm::v1::MsgExecuteContractResponse, Account, Module, + OsmosisTestApp, RunnerError, RunnerExecuteResult, SigningAccount, Wasm, +}; +use serde::de::DeserializeOwned; +use std::fmt::Debug; +use std::path::PathBuf; + +#[derive(Debug)] +pub struct DaoProposalSingle<'a> { + pub app: &'a OsmosisTestApp, + pub code_id: u64, + pub contract_addr: String, +} + +impl<'a> DaoProposalSingle<'a> { + pub fn new( + app: &'a OsmosisTestApp, + instantiate_msg: &InstantiateMsg, + signer: &SigningAccount, + ) -> Result { + let wasm = Wasm::new(app); + let token_creation_fee = Coin::new(10000000, "uosmo"); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + let contract_addr = wasm + .instantiate( + code_id, + &instantiate_msg, + Some(&signer.address()), + None, + &[token_creation_fee], + signer, + )? + .data + .address; + + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + pub fn new_with_values( + app: &'a OsmosisTestApp, + code_id: u64, + contract_addr: String, + ) -> Result { + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + /// uploads contract and returns a code ID + pub fn upload(app: &OsmosisTestApp, signer: &SigningAccount) -> Result { + let wasm = Wasm::new(app); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + Ok(code_id) + } + + // executes + pub fn execute( + &self, + execute_msg: &ExecuteMsg, + funds: &[Coin], + signer: &SigningAccount, + ) -> RunnerExecuteResult { + let wasm = Wasm::new(self.app); + wasm.execute(&self.contract_addr, execute_msg, funds, signer) + } + + // queries + pub fn query(&self, query_msg: &QueryMsg) -> Result + where + T: DeserializeOwned, + { + let wasm = Wasm::new(self.app); + wasm.query(&self.contract_addr, query_msg) + } + + fn get_wasm_byte_code() -> Vec { + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let byte_code = std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_proposal_single.wasm"), + ); + match byte_code { + Ok(byte_code) => byte_code, + // On arm processors, the above path is not found, so we try the following path + Err(_) => std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_proposal_single-aarch64.wasm"), + ) + .unwrap(), + } + } + + pub fn execute_error(err: ContractError) -> RunnerError { + RunnerError::ExecuteError { + msg: format!( + "failed to execute message; message index: 0: {}: execute wasm contract failed", + err + ), + } + } +} diff --git a/packages/dao-testing/src/test_tube/mod.rs b/packages/dao-testing/src/test_tube/mod.rs index f87c833bc..a5f997792 100644 --- a/packages/dao-testing/src/test_tube/mod.rs +++ b/packages/dao-testing/src/test_tube/mod.rs @@ -7,3 +7,9 @@ // cargo test --features test-tube #[cfg(feature = "test-tube")] pub mod cw_tokenfactory_issuer; + +#[cfg(feature = "test-tube")] +pub mod dao_dao_core; + +#[cfg(feature = "test-tube")] +pub mod dao_proposal_single; diff --git a/packages/dao-vote-hooks/README.md b/packages/dao-vote-hooks/README.md deleted file mode 100644 index ae2be9cca..000000000 --- a/packages/dao-vote-hooks/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# CosmWasm DAO Vote Hooks - -This package provides an interface for managing and dispatching -vote hooks from a proposal module. Vote hooks are fired when new -votes are cast. - -You can read more about vote hooks in our [wiki](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). diff --git a/packages/dao-voting/src/duration.rs b/packages/dao-voting/src/duration.rs new file mode 100644 index 000000000..bea32880d --- /dev/null +++ b/packages/dao-voting/src/duration.rs @@ -0,0 +1,26 @@ +use cw_utils::Duration; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum UnstakingDurationError { + #[error("Invalid unstaking duration, unstaking duration cannot be 0")] + InvalidUnstakingDuration {}, +} + +pub fn validate_duration(duration: Option) -> Result<(), UnstakingDurationError> { + if let Some(unstaking_duration) = duration { + match unstaking_duration { + Duration::Height(height) => { + if height == 0 { + return Err(UnstakingDurationError::InvalidUnstakingDuration {}); + } + } + Duration::Time(time) => { + if time == 0 { + return Err(UnstakingDurationError::InvalidUnstakingDuration {}); + } + } + } + } + Ok(()) +} diff --git a/packages/dao-voting/src/lib.rs b/packages/dao-voting/src/lib.rs index 208cfc468..7395691b2 100644 --- a/packages/dao-voting/src/lib.rs +++ b/packages/dao-voting/src/lib.rs @@ -1,6 +1,7 @@ #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] pub mod deposit; +pub mod duration; pub mod error; pub mod multiple_choice; pub mod pre_propose; diff --git a/packages/dao-voting/src/threshold.rs b/packages/dao-voting/src/threshold.rs index f22fb9849..e27e46c7b 100644 --- a/packages/dao-voting/src/threshold.rs +++ b/packages/dao-voting/src/threshold.rs @@ -23,6 +23,38 @@ pub struct ActiveThresholdResponse { pub active_threshold: Option, } +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ActiveThresholdError { + #[error("Absolute count threshold cannot be greater than the total token supply")] + InvalidAbsoluteCount {}, + + #[error("Active threshold percentage must be greater than 0 and less than 1")] + InvalidActivePercentage {}, + + #[error("Active threshold count must be greater than zero")] + ZeroActiveCount {}, +} + +pub fn assert_valid_absolute_count_threshold( + count: Uint128, + supply: Uint128, +) -> Result<(), ActiveThresholdError> { + if count.is_zero() { + return Err(ActiveThresholdError::ZeroActiveCount {}); + } + if count > supply { + return Err(ActiveThresholdError::InvalidAbsoluteCount {}); + } + Ok(()) +} + +pub fn assert_valid_percentage_threshold(percent: Decimal) -> Result<(), ActiveThresholdError> { + if percent.is_zero() || percent > Decimal::one() { + return Err(ActiveThresholdError::InvalidActivePercentage {}); + } + Ok(()) +} + #[derive(Error, Debug, PartialEq, Eq)] pub enum ThresholdError { #[error("Required threshold cannot be zero")] diff --git a/scripts/publish.sh b/scripts/publish.sh index ba42f086e..5d70f7888 100644 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -67,16 +67,12 @@ cd packages/dao-voting cargo publish cd "$START_DIR" -cd packages/dao-vote-hooks +cd packages/dao-hooks cargo publish cd "$START_DIR" sleep 120 -cd packages/dao-proposal-hooks -cargo publish -cd "$START_DIR" - cd packages/dao-pre-propose-base cargo publish cd "$START_DIR" @@ -167,7 +163,7 @@ cd contracts/voting/dao-voting-cw721-staked cargo hack publish --no-dev-deps --allow-dirty cd "$START_DIR" -cd contracts/voting/dao-voting-native-staked +cd contracts/voting/dao-voting-token-staked cargo hack publish --no-dev-deps --allow-dirty cd "$START_DIR" diff --git a/test-contracts/dao-proposal-hook-counter/Cargo.toml b/test-contracts/dao-proposal-hook-counter/Cargo.toml index 5effd61ef..395fb338a 100644 --- a/test-contracts/dao-proposal-hook-counter/Cargo.toml +++ b/test-contracts/dao-proposal-hook-counter/Cargo.toml @@ -22,8 +22,7 @@ cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } thiserror = { workspace = true } -dao-proposal-hooks = { workspace = true } -dao-vote-hooks = { workspace = true } +dao-hooks = { workspace = true } [dev-dependencies] cw-hooks = { workspace = true } diff --git a/test-contracts/dao-proposal-hook-counter/src/contract.rs b/test-contracts/dao-proposal-hook-counter/src/contract.rs index 2a9163d02..be3cddc8d 100644 --- a/test-contracts/dao-proposal-hook-counter/src/contract.rs +++ b/test-contracts/dao-proposal-hook-counter/src/contract.rs @@ -1,13 +1,17 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, +}; use cw2::set_contract_version; -use dao_proposal_hooks::ProposalHookMsg; -use dao_vote_hooks::VoteHookMsg; +use dao_hooks::stake::StakeChangedHookMsg; +use dao_hooks::{proposal::ProposalHookMsg, vote::VoteHookMsg}; use crate::error::ContractError; use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{Config, CONFIG, PROPOSAL_COUNTER, STATUS_CHANGED_COUNTER, VOTE_COUNTER}; +use crate::state::{ + Config, CONFIG, PROPOSAL_COUNTER, STAKE_COUNTER, STATUS_CHANGED_COUNTER, VOTE_COUNTER, +}; const CONTRACT_NAME: &str = "crates.io:proposal-hooks-counter"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -26,6 +30,7 @@ pub fn instantiate( }; CONFIG.save(deps.storage, &config)?; PROPOSAL_COUNTER.save(deps.storage, &0)?; + STAKE_COUNTER.save(deps.storage, &Uint128::zero())?; VOTE_COUNTER.save(deps.storage, &0)?; STATUS_CHANGED_COUNTER.save(deps.storage, &0)?; Ok(Response::new().add_attribute("action", "instantiate")) @@ -47,6 +52,7 @@ pub fn execute( ExecuteMsg::ProposalHook(proposal_hook) => { execute_proposal_hook(deps, env, info, proposal_hook) } + ExecuteMsg::StakeChangeHook(stake_hook) => execute_stake_hook(deps, env, info, stake_hook), ExecuteMsg::VoteHook(vote_hook) => execute_vote_hook(deps, env, info, vote_hook), } } @@ -73,6 +79,28 @@ pub fn execute_proposal_hook( Ok(Response::new().add_attribute("action", "proposal_hook")) } +pub fn execute_stake_hook( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + stake_hook: StakeChangedHookMsg, +) -> Result { + match stake_hook { + StakeChangedHookMsg::Stake { .. } => { + let mut count = STAKE_COUNTER.load(deps.storage)?; + count += Uint128::new(1); + STAKE_COUNTER.save(deps.storage, &count)?; + } + StakeChangedHookMsg::Unstake { .. } => { + let mut count = STAKE_COUNTER.load(deps.storage)?; + count += Uint128::new(1); + STAKE_COUNTER.save(deps.storage, &count)?; + } + } + + Ok(Response::new().add_attribute("action", "stake_hook")) +} + pub fn execute_vote_hook( deps: DepsMut, _env: Env, @@ -93,14 +121,15 @@ pub fn execute_vote_hook( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::VoteCounter {} => to_binary(&CountResponse { - count: VOTE_COUNTER.load(deps.storage)?, - }), QueryMsg::ProposalCounter {} => to_binary(&CountResponse { count: PROPOSAL_COUNTER.load(deps.storage)?, }), + QueryMsg::StakeCounter {} => to_binary(&STAKE_COUNTER.load(deps.storage)?), QueryMsg::StatusChangedCounter {} => to_binary(&CountResponse { count: STATUS_CHANGED_COUNTER.load(deps.storage)?, }), + QueryMsg::VoteCounter {} => to_binary(&CountResponse { + count: VOTE_COUNTER.load(deps.storage)?, + }), } } diff --git a/test-contracts/dao-proposal-hook-counter/src/msg.rs b/test-contracts/dao-proposal-hook-counter/src/msg.rs index 825c57bf4..ad6048228 100644 --- a/test-contracts/dao-proposal-hook-counter/src/msg.rs +++ b/test-contracts/dao-proposal-hook-counter/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use dao_proposal_hooks::ProposalHookMsg; -use dao_vote_hooks::VoteHookMsg; +use cosmwasm_std::Uint128; +use dao_hooks::{proposal::ProposalHookMsg, stake::StakeChangedHookMsg, vote::VoteHookMsg}; #[cw_serde] pub struct InstantiateMsg { @@ -10,12 +10,15 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { ProposalHook(ProposalHookMsg), + StakeChangeHook(StakeChangedHookMsg), VoteHook(VoteHookMsg), } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(Uint128)] + StakeCounter {}, #[returns(u64)] VoteCounter {}, #[returns(u64)] diff --git a/test-contracts/dao-proposal-hook-counter/src/state.rs b/test-contracts/dao-proposal-hook-counter/src/state.rs index 4f22cb0e7..6bdbdfa23 100644 --- a/test-contracts/dao-proposal-hook-counter/src/state.rs +++ b/test-contracts/dao-proposal-hook-counter/src/state.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; use cw_storage_plus::Item; #[cw_serde] @@ -6,6 +7,7 @@ pub struct Config { pub should_error: bool, } pub const CONFIG: Item = Item::new("config"); -pub const VOTE_COUNTER: Item = Item::new("vote_counter"); pub const PROPOSAL_COUNTER: Item = Item::new("proposal_counter"); +pub const STAKE_COUNTER: Item = Item::new("stake_counter"); pub const STATUS_CHANGED_COUNTER: Item = Item::new("stauts_changed_counter"); +pub const VOTE_COUNTER: Item = Item::new("vote_counter");