From 6dffdb67f9b683f19856d598b55a0a7fb2ce8f5d Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 09:45:20 -0400 Subject: [PATCH 01/15] add epoch limit, remove cw-orch --- Cargo.lock | 590 +-------------- Cargo.toml | 3 - contracts/gauges/gauge-adapter/Cargo.toml | 6 +- .../gauges/gauge-adapter/src/contract.rs | 37 +- contracts/gauges/gauge-adapter/src/error.rs | 5 + contracts/gauges/gauge-adapter/src/msg.rs | 12 +- .../gauge-adapter/src/multitest/options.rs | 102 +-- .../gauge-adapter/src/multitest/submission.rs | 693 ++++++++---------- .../gauge-adapter/src/multitest/suite.rs | 407 +++++++--- contracts/gauges/gauge-adapter/src/state.rs | 2 +- contracts/gauges/gauge/Cargo.toml | 1 + contracts/gauges/gauge/src/contract.rs | 72 +- contracts/gauges/gauge/src/error.rs | 4 + contracts/gauges/gauge/src/msg.rs | 18 +- contracts/gauges/gauge/src/multitest/gauge.rs | 68 +- contracts/gauges/gauge/src/multitest/reset.rs | 80 +- contracts/gauges/gauge/src/multitest/suite.rs | 16 +- contracts/gauges/gauge/src/multitest/tally.rs | 4 + .../gauges/gauge/src/multitest/voting.rs | 28 +- contracts/gauges/gauge/src/state.rs | 25 +- 20 files changed, 966 insertions(+), 1207 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51663573b..d3b72a713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,273 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "abstract-cw-multi-test" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c77f8d4bac08f74fbc4fce8943cb2d35e742682b6cae8cb65555d6cd3830feb" -dependencies = [ - "anyhow", - "bech32 0.11.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw20-ics20", - "derivative", - "hex", - "itertools 0.12.1", - "log", - "prost 0.12.3", - "schemars", - "serde", - "serde_json", - "sha2 0.10.8", - "thiserror", -] - -[[package]] -name = "abstract-cw-plus-interface" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7441425a805439500492977107d154af02b1702aa044945775c245d0b3469968" -dependencies = [ - "abstract-cw1", - "abstract-cw1-subkeys", - "abstract-cw1-whitelist", - "abstract-cw20-base", - "abstract-cw20-ics20", - "abstract-cw3-fixed-multisig", - "abstract-cw3-flex-multisig", - "abstract-cw4-group", - "abstract-cw4-stake", - "cosmwasm-std", - "cw-orch", -] - -[[package]] -name = "abstract-cw1" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0895c076ab6a5165133a453f983ec9ccc9b6c41de256b6eb74e523eb555b3ebb" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "abstract-cw1-subkeys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab08cdd6008afa38a52427943bf4aef9541bde78cc9c14849a53ad2608a1161e" -dependencies = [ - "abstract-cw1", - "abstract-cw1-whitelist", - "abstract-cw2", - "cosmwasm-schema", - "cosmwasm-std", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw1-whitelist" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171a0b5b3694627cf0fa554500d72431169d4013fffd14650d2b7d660230a205" -dependencies = [ - "abstract-cw1", - "abstract-cw2", - "cosmwasm-schema", - "cosmwasm-std", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw2" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945af4c176b4539be2a74c06aa166287ba964ab58aec98c644addd812431f141" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw20" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00d5e4b8084c3a2b3e42502e6c4fe3ed985dc72e86eb612bcc527f4a0443fa42" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-orch", - "cw-utils 1.0.3", - "schemars", - "serde", -] - -[[package]] -name = "abstract-cw20-base" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d300dec7d602e00841c5ab6fe598d4d290bab32e489c6885c607633c4f3fe67" -dependencies = [ - "abstract-cw2", - "abstract-cw20", - "cosmwasm-schema", - "cosmwasm-std", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw20-ics20" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027678ddb0e62b4aba5f0167d2b0a3ec0182e1e32c47759be7e30b56775598ee" -dependencies = [ - "abstract-cw2", - "abstract-cw20", - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers 1.1.2", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw3" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c080cc760333d1d3477857aeac19aa7e6e661f1e58d04a7a78212913d49bf517" -dependencies = [ - "abstract-cw20", - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 1.0.3", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw3-fixed-multisig" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1882e05bef33bd1c6b25e735eda8a23332a78c4df0b24a18ca56a8ca8ed6f222" -dependencies = [ - "abstract-cw2", - "abstract-cw3", - "cosmwasm-schema", - "cosmwasm-std", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw3-flex-multisig" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92379f3e7c467f081312d6953eb8d300456efa352c9f7c5ef095ad99083d92db" -dependencies = [ - "abstract-cw2", - "abstract-cw20", - "abstract-cw3", - "abstract-cw3-fixed-multisig", - "abstract-cw4", - "cosmwasm-schema", - "cosmwasm-std", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw4" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aacb0124dce37ee6f2b5636684285bcbaa65a1678980f95ea76366ab74a8912" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "schemars", - "serde", -] - -[[package]] -name = "abstract-cw4-group" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af5ef484ba1d48fee8485452c81ac3465ba16a5941db90bda4dd6b58b50a9a6" -dependencies = [ - "abstract-cw2", - "abstract-cw4", - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers 1.1.2", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "abstract-cw4-stake" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1eb9985e8b752396a2c5d8fde8ebf65ea81070a95f167a3d31af0746f8e4b4e" -dependencies = [ - "abstract-cw2", - "abstract-cw20", - "abstract-cw4", - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers 1.1.2", - "cw-orch", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "addr2line" version = "0.22.0" @@ -451,12 +184,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" -[[package]] -name = "bech32" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" - [[package]] name = "bindgen" version = "0.68.1" @@ -682,15 +409,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -774,17 +492,6 @@ dependencies = [ "tonic 0.9.2", ] -[[package]] -name = "cosmos-sdk-proto" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e23f6ab56d5f031cde05b8b82a5fefd3a1a223595c79e32317a97189e612bc" -dependencies = [ - "prost 0.12.3", - "prost-types 0.12.3", - "tendermint-proto 0.35.0", -] - [[package]] name = "cosmrs" version = "0.9.0" @@ -880,7 +587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2685c2182624b2e9e17f7596192de49a3f86b7a0c9a5f6b25c1df5e24592e836" dependencies = [ "base64 0.21.7", - "bech32 0.9.1", + "bech32", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -983,7 +690,7 @@ dependencies = [ name = "cw-admin-factory" version = "2.5.0" dependencies = [ - "bech32 0.9.1", + "bech32", "cosmwasm-schema", "cosmwasm-std", "cw-admin-factory", @@ -1186,7 +893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" dependencies = [ "anyhow", - "bech32 0.9.1", + "bech32", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1199,96 +906,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-orch" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1ddc937c28c59ccf2765fa05ddc0437644d3b283408a7cc64f7b371b0b9309" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cw-orch-contract-derive", - "cw-orch-core", - "cw-orch-fns-derive", - "cw-orch-mock", - "cw-orch-traits", - "cw-utils 1.0.3", - "hex", - "log", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw-orch-contract-derive" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc8ba75692fc7bd30e91c78fad2dc208a738e4e6ea26b232f9352c320e35543" -dependencies = [ - "convert_case", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "cw-orch-core" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abd640f67f655411542a3c148769270c7a9e63d4097c2dc4a1f6edf23b7a9b4" -dependencies = [ - "abstract-cw-multi-test", - "anyhow", - "cosmos-sdk-proto 0.21.1", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "dirs", - "log", - "serde", - "serde_json", - "sha2 0.10.8", - "thiserror", -] - -[[package]] -name = "cw-orch-fns-derive" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9acb7a15bfacc52abdf312a9fffb139883c1effb6ea7e645cd39580a8527463" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "cw-orch-mock" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae9536620b86ee78c2729fd8449538feb4f6257a9809c72c5f9e461e720cf3b" -dependencies = [ - "abstract-cw-multi-test", - "cosmwasm-std", - "cw-orch-core", - "cw-utils 1.0.3", - "log", - "serde", - "sha2 0.10.8", -] - -[[package]] -name = "cw-orch-traits" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5959ce29e9d8a52594b47933a0a2736ea94dd9bf5e29b220cbdbe2b097f07c3a" -dependencies = [ - "cw-orch-core", - "prost 0.12.3", - "prost-types 0.12.3", -] - [[package]] name = "cw-ownable" version = "0.5.1" @@ -1724,25 +1341,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw20-ics20" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76221201da08fed611c857ea3aa21c031a4a7dc771a8b1750559ca987335dc02" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers 1.1.2", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw2 1.1.2", - "cw20 1.1.2", - "schemars", - "semver", - "serde", - "thiserror", -] - [[package]] name = "cw20-stake" version = "0.2.6" @@ -2976,27 +2574,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - [[package]] name = "dlv-list" version = "0.3.0" @@ -3308,16 +2885,12 @@ dependencies = [ name = "gauge-adapter" version = "2.5.0" dependencies = [ - "abstract-cw-plus-interface", - "abstract-cw20", - "abstract-cw20-base", "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-denom 2.5.0", "cw-multi-test", - "cw-orch", - "cw-orch-core", + "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -3335,6 +2908,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -3824,17 +3398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.6.0", - "libc", + "windows-targets", ] [[package]] @@ -3921,17 +3485,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -4006,12 +3559,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "ordered-multimap" version = "0.4.3" @@ -4375,17 +3922,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.10.6" @@ -5078,7 +4614,7 @@ checksum = "68ce80bf536476db81ecc9ebab834dc329c9c1509a694f211a73858814bfe023" dependencies = [ "bytes", "flex-error", - "num-derive 0.3.3", + "num-derive", "num-traits", "prost 0.11.9", "prost-types 0.11.9", @@ -5096,7 +4632,7 @@ checksum = "974d6330a19dfa6720e9f663fc59101d207a817db3f9c730d3f31caaa565b574" dependencies = [ "bytes", "flex-error", - "num-derive 0.3.3", + "num-derive", "num-traits", "prost 0.11.9", "prost-types 0.11.9", @@ -5114,7 +4650,7 @@ checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" dependencies = [ "bytes", "flex-error", - "num-derive 0.3.3", + "num-derive", "num-traits", "prost 0.11.9", "prost-types 0.11.9", @@ -5124,24 +4660,6 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-proto" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff525d5540a9fc535c38dc0d92a98da3ee36fcdfbda99cecb9f3cce5cd4d41d7" -dependencies = [ - "bytes", - "flex-error", - "num-derive 0.4.2", - "num-traits", - "prost 0.12.3", - "prost-types 0.12.3", - "serde", - "serde_bytes", - "subtle-encoding", - "time", -] - [[package]] name = "tendermint-rpc" version = "0.23.9" @@ -5560,12 +5078,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -5765,22 +5277,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5789,22 +5292,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -5813,46 +5301,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5865,48 +5335,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 723158773..109e4592a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,9 +81,6 @@ syn = { version = "1.0", features = ["derive"] } test-context = "0.1" thiserror = { version = "1.0" } wynd-utils = "0.4" -cw-orch = "0.22.2" -cw-orch-core = "1.0.0-rc" - # One commit ahead of version 0.3.0. Allows initialization with an # optional owner. diff --git a/contracts/gauges/gauge-adapter/Cargo.toml b/contracts/gauges/gauge-adapter/Cargo.toml index f8989d57d..25aa120c8 100644 --- a/contracts/gauges/gauge-adapter/Cargo.toml +++ b/contracts/gauges/gauge-adapter/Cargo.toml @@ -25,17 +25,13 @@ cw-storage-plus = { workspace = true } cw2 = { workspace = true } cw20 = { workspace = true } cw-denom = { workspace = true } -cw-orch = { workspace = true } +cw-ownable = { workspace = true } cw-utils = { workspace = true } semver = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -abstract-cw-plus-interface = "2.0.1" -abstract-cw20 = "2.0.0" -abstract-cw20-base = "2.0.0" anyhow = { workspace = true } cw-multi-test = { workspace = true } cw20 = { workspace = true } cw20-base = { workspace = true } -cw-orch-core = { workspace = true } diff --git a/contracts/gauges/gauge-adapter/src/contract.rs b/contracts/gauges/gauge-adapter/src/contract.rs index fdcbec92d..1e0a7cdca 100644 --- a/contracts/gauges/gauge-adapter/src/contract.rs +++ b/contracts/gauges/gauge-adapter/src/contract.rs @@ -8,6 +8,7 @@ use cw2::set_contract_version; use cw20::Cw20ReceiveMsg; use cw_denom::UncheckedDenom; use cw_utils::{one_coin, PaymentError}; +use execute::execute_update_owner; use crate::{ error::ContractError, @@ -39,8 +40,15 @@ pub fn instantiate( }, )?; + // set owner + cw_ownable::initialize_owner( + deps.storage, + deps.api, + Some(deps.api.addr_validate(&msg.owner)?.as_str()), + )?; + let config = Config { - admin: deps.api.addr_validate(&msg.admin)?, + owner: deps.api.addr_validate(&msg.owner)?, required_deposit: msg .required_deposit .map(|x| x.into_checked(deps.as_ref())) @@ -56,7 +64,7 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { @@ -76,6 +84,7 @@ pub fn execute( execute::create_submission(deps, info.sender, name, url, address, received) } ExecuteMsg::ReturnDeposits {} => execute::return_deposits(deps, info.sender), + ExecuteMsg::UpdateOwnership(action) => execute_update_owner(deps, info, env, action), } } @@ -118,7 +127,7 @@ pub mod execute { required_deposit, community_pool: _, reward: _, - admin: _, + owner: _, } = CONFIG.load(deps.storage)?; if let Some(required_deposit) = required_deposit { if let Some(received) = received { @@ -157,7 +166,7 @@ pub mod execute { pub fn return_deposits(deps: DepsMut, sender: Addr) -> Result { let Config { - admin, + owner, required_deposit, community_pool: _, reward: _, @@ -166,7 +175,7 @@ pub mod execute { // No refund if no deposit was required. let required_deposit = required_deposit.ok_or(ContractError::NoDepositToRefund {})?; - ensure_eq!(sender, admin, ContractError::Unauthorized {}); + ensure_eq!(sender, owner, ContractError::Unauthorized {}); let msgs = SUBMISSIONS .range(deps.storage, None, None, Order::Ascending) @@ -181,6 +190,16 @@ pub mod execute { Ok(Response::new().add_messages(msgs)) } + + pub fn execute_update_owner( + deps: DepsMut, + info: MessageInfo, + env: Env, + action: cw_ownable::Action, + ) -> Result { + let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; + Ok(Response::default().add_attributes(ownership.into_attributes())) + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -303,7 +322,7 @@ mod tests { fn proper_initialization() { let mut deps = mock_dependencies(); let msg = InstantiateMsg { - admin: "admin".to_owned(), + owner: "owner".to_owned(), required_deposit: Some(AssetUnchecked::new_native("wynd", 10_000_000)), community_pool: "community".to_owned(), reward: AssetUnchecked::new_native("ujuno", 150_000_000_000), @@ -318,7 +337,7 @@ mod tests { // Check if the config is stored. let config = CONFIG.load(deps.as_ref().storage).unwrap(); - assert_eq!(config.admin, Addr::unchecked("admin")); + assert_eq!(config.owner, Addr::unchecked("owner")); assert_eq!( config.required_deposit, Some(Asset { @@ -370,7 +389,7 @@ mod tests { let reward = Uint128::new(150_000_000_000); let msg = InstantiateMsg { - admin: "admin".to_owned(), + owner: "owner".to_owned(), required_deposit: Some(AssetUnchecked::new_native("wynd", 10_000_000)), community_pool: "community".to_owned(), reward: AssetUnchecked::new_native("ujuno", reward.into()), @@ -416,7 +435,7 @@ mod tests { fn return_deposits_authorization() { let mut deps = mock_dependencies(); let msg = InstantiateMsg { - admin: "admin".to_owned(), + owner: "owner".to_owned(), required_deposit: None, community_pool: "community".to_owned(), reward: AssetUnchecked::new_native("ujuno", 150_000_000_000), diff --git a/contracts/gauges/gauge-adapter/src/error.rs b/contracts/gauges/gauge-adapter/src/error.rs index 6994dc494..8eb5101f6 100644 --- a/contracts/gauges/gauge-adapter/src/error.rs +++ b/contracts/gauges/gauge-adapter/src/error.rs @@ -14,6 +14,9 @@ pub enum ContractError { #[error("{0}")] DenomError(#[from] DenomError), + #[error("{0}")] + Ownership(#[from] cw_ownable::OwnershipError), + #[error("Operation unauthorized - only admin can release deposits")] Unauthorized {}, @@ -28,4 +31,6 @@ pub enum ContractError { #[error("No deposit was required, therefore no deposit can be returned")] NoDepositToRefund {}, + #[error("Deposit required, cannot create submission.")] + DepositRequired {}, } diff --git a/contracts/gauges/gauge-adapter/src/msg.rs b/contracts/gauges/gauge-adapter/src/msg.rs index 5d8a16f7d..e38eec3fc 100644 --- a/contracts/gauges/gauge-adapter/src/msg.rs +++ b/contracts/gauges/gauge-adapter/src/msg.rs @@ -2,11 +2,12 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, CosmosMsg, Decimal, Uint128}; use cw20::Cw20ReceiveMsg; use cw_denom::UncheckedDenom; +use cw_ownable::cw_ownable_execute; #[cw_serde] pub struct InstantiateMsg { /// Address that is allowed to return deposits. - pub admin: String, + pub owner: String, /// Deposit required for valid submission. This option allows to reduce spam. pub required_deposit: Option, /// Address of contract where each deposit is transferred. @@ -15,8 +16,8 @@ pub struct InstantiateMsg { pub reward: AssetUnchecked, } +#[cw_ownable_execute] #[cw_serde] -#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Implements the Cw20 receiver interface. Receive(Cw20ReceiveMsg), @@ -48,21 +49,24 @@ pub enum MigrateMsg {} // Queries copied from gauge-orchestrator for now (we could use a common crate for this). /// Queries the gauge requires from the adapter contract in order to function. #[cw_serde] -#[derive(QueryResponses, cw_orch::QueryFns)] +#[derive(QueryResponses)] pub enum AdapterQueryMsg { + /// Returns adapters internal Config state. #[returns(crate::state::Config)] Config {}, + /// Returns all available options to vote for. #[returns(AllOptionsResponse)] AllOptions {}, + /// Checks if a provided option is included in the available options. Returns a boolean. #[returns(CheckOptionResponse)] CheckOption { option: String }, + /// Returns the messages determined by the current voting results for options.Used by the gauge orchestrator to pass messages for DAO to execute. #[returns(SampleGaugeMsgsResponse)] SampleGaugeMsgs { /// Option along with weight. /// Sum of all weights should be 1.0 (within rounding error). selected: Vec<(String, Decimal)>, }, - // Marketing-gauge specific queries to help on frontend #[returns(SubmissionResponse)] Submission { address: String }, diff --git a/contracts/gauges/gauge-adapter/src/multitest/options.rs b/contracts/gauges/gauge-adapter/src/multitest/options.rs index 2d6343cff..21c24277a 100644 --- a/contracts/gauges/gauge-adapter/src/multitest/options.rs +++ b/contracts/gauges/gauge-adapter/src/multitest/options.rs @@ -1,80 +1,50 @@ -use cosmwasm_std::{coin, coins}; -use cw_denom::UncheckedDenom; -use cw_orch::{contract::interface_traits::CwOrchQuery, mock::MockBech32}; +use cosmwasm_std::{coin, Addr}; -use crate::{ - msg::{ - AdapterQueryMsg, AllOptionsResponse, AllSubmissionsResponse, AssetUnchecked, - CheckOptionResponse, - }, - multitest::suite::{native_submission_helper, setup_gauge_adapter}, -}; +use crate::multitest::suite::SuiteBuilder; #[test] fn option_queries() { - let mock = MockBech32::new("mock"); - let adapter = setup_gauge_adapter( - mock.clone(), - Some(AssetUnchecked { - denom: UncheckedDenom::Native("juno".into()), - amount: 1_000u128.into(), - }), - ); + let mut suite = SuiteBuilder::new() + .with_community_pool("community_pool") + .with_funds("owner", &[coin(100_000, "juno")]) + .with_funds("einstein", &[coin(100_000, "juno")]) + .with_native_deposit(1_000) + .build(); - let recipient = mock.addr_make("recipient"); - let newton = mock.addr_make("newton"); - let einstein = mock - .addr_make_with_balance("einstein", coins(1_000, "juno")) - .unwrap(); + let recipient = "user".to_owned(); - mock.add_balance(&mock.sender, coins(1_000, "juno")) - .unwrap(); - let options: AllSubmissionsResponse = - adapter.query(&AdapterQueryMsg::AllSubmissions {}).unwrap(); + let options = suite.query_all_options().unwrap(); // account for a default option - assert_eq!(options.submissions.len(), 1); + assert_eq!(options.len(), 1); // Valid submission. - native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - Some(coin(1_000u128, "juno")), - ) - .unwrap(); + _ = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[coin(1_000, "juno")], + ) + .unwrap(); // Valid submission. - native_submission_helper( - adapter.clone(), - einstein.clone(), - einstein.clone(), - Some(coin(1_000u128, "juno")), - ) - .unwrap(); + suite + .execute_create_submission( + Addr::unchecked("einstein"), + "MIBers".to_owned(), + "https://www.mib.tech/".to_owned(), + "einstein".to_owned(), + &[coin(1_000, "juno")], + ) + .unwrap(); - let options: AllOptionsResponse = adapter.query(&AdapterQueryMsg::AllOptions {}).unwrap(); - assert_eq!( - options, - AllOptionsResponse { - options: vec![ - einstein.to_string(), - mock.addr_make("community_pool").to_string(), - recipient.to_string() - ] - }, - ); + let options = suite.query_all_options().unwrap(); + assert_eq!(options, vec!["community_pool", "einstein", &recipient],); - let option: CheckOptionResponse = adapter - .query(&AdapterQueryMsg::CheckOption { - option: einstein.to_string(), - }) - .unwrap(); - assert!(option.valid); + let option = suite.query_check_option("einstein".to_owned()).unwrap(); + assert!(option); - let option: CheckOptionResponse = adapter - .query(&AdapterQueryMsg::CheckOption { - option: newton.to_string(), - }) - .unwrap(); - assert!(!option.valid); -} + let option = suite.query_check_option("newton".to_owned()).unwrap(); + assert!(!option); +} \ No newline at end of file diff --git a/contracts/gauges/gauge-adapter/src/multitest/submission.rs b/contracts/gauges/gauge-adapter/src/multitest/submission.rs index 41f8a88cb..19f3eba5c 100644 --- a/contracts/gauges/gauge-adapter/src/multitest/submission.rs +++ b/contracts/gauges/gauge-adapter/src/multitest/submission.rs @@ -1,60 +1,45 @@ -use crate::{ - msg::{ - AdapterQueryMsg, AdapterQueryMsgFns, AllSubmissionsResponse, AssetUnchecked, ExecuteMsg, - ExecuteMsgFns, ReceiveMsg, SubmissionResponse, - }, - multitest::suite::{ - cw20_helper, native_submission_helper, setup_cw20_reward_gauge_adapter, setup_gauge_adapter, - }, - ContractError, -}; - -use abstract_cw20::{msg::Cw20ExecuteMsgFns, Cw20ExecuteMsg as AbsCw20ExecuteMsg}; -use abstract_cw20_base::msg::QueryMsgFns; -use cosmwasm_std::{coin, to_json_binary, Addr, CosmosMsg, Decimal, Uint128, WasmMsg}; -use cw_denom::UncheckedDenom; -use cw_orch::{contract::interface_traits::CwOrchExecute, mock::MockBech32, prelude::*}; +use crate::{msg::SubmissionResponse, ContractError}; + +use super::suite::SuiteBuilder; + +use cosmwasm_std::{coin, Addr, Uint128}; #[test] fn create_default_submission() { - let mock = MockBech32::new("mock"); - let treasury = &mock.addr_make("community_pool"); - - let adapter = setup_gauge_adapter(mock.clone(), None); + let suite = SuiteBuilder::new() + .with_community_pool("community_pool") + .build(); // this one is created by default during instantiation assert_eq!( SubmissionResponse { - sender: adapter.address().unwrap(), + sender: suite.gauge_adapter.clone(), name: "Unimpressed".to_owned(), url: "Those funds go back to the community pool".to_owned(), - address: treasury.clone(), + address: Addr::unchecked("community_pool"), }, - adapter - .query(&crate::msg::AdapterQueryMsg::Submission { - address: treasury.to_string() - }) - .unwrap() + suite.query_submission("community_pool".to_owned()).unwrap() ) } #[test] fn create_submission_no_required_deposit() { - let mock = MockBech32::new("mock"); - let adapter = setup_gauge_adapter(mock.clone(), None); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(100_000, "juno")]) + .build(); - let recipient = mock.addr_make("recipient"); - mock.add_balance(&mock.sender, vec![coin(1_000, "juno")]) - .unwrap(); + let recipient = "user".to_owned(); // Fails send funds along with the tx. - let err = native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - Some(coin(1_000, "juno")), - ) - .unwrap_err(); + let err = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[coin(1_000, "juno")], + ) + .unwrap_err(); assert_eq!( ContractError::InvalidDepositAmount { @@ -64,116 +49,134 @@ fn create_submission_no_required_deposit() { ); // Valid submission. - let result = native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - None, - ); - assert!(result.is_ok()); + _ = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[], + ) + .unwrap(); assert_eq!( SubmissionResponse { - sender: mock.sender, - name: "DAOers".to_owned(), - url: "https://daodao.zone".to_owned(), - address: recipient.clone(), + sender: suite.owner.clone(), + name: "WYNDers".to_owned(), + url: "https://www.wynddao.com/".to_owned(), + address: Addr::unchecked(recipient.clone()), }, - adapter - .query(&crate::msg::AdapterQueryMsg::Submission { - address: recipient.to_string() - }) - .unwrap(), + suite.query_submission(recipient).unwrap() ) } #[test] fn overwrite_existing_submission() { - let mock = MockBech32::new("mock"); - let adapter = setup_gauge_adapter(mock.clone(), None); - let recipient = mock.addr_make("recipient"); - native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - None, - ) - .unwrap(); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(100_000, "juno")]) + .build(); + + let recipient = "user".to_owned(); + + suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[], + ) + .unwrap(); assert_eq!( SubmissionResponse { - sender: mock.sender.clone(), - name: "DAOers".to_owned(), - url: "https://daodao.zone".to_string(), - address: recipient.clone(), + sender: suite.owner.clone(), + name: "WYNDers".to_owned(), + url: "https://www.wynddao.com/".to_owned(), + address: Addr::unchecked(recipient.clone()), }, - adapter.submission(recipient.to_string()).unwrap() + suite.query_submission(recipient.clone()).unwrap() ); // Try to submit to the same address with different user - let err = native_submission_helper( - adapter.clone(), - Addr::unchecked("anotheruser"), - recipient.clone(), - None, - ) - .unwrap_err(); - + let err = suite + .execute_create_submission( + Addr::unchecked("anotheruser"), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[], + ) + .unwrap_err(); assert_eq!( ContractError::UnauthorizedSubmission {}, err.downcast().unwrap() ); // Overwriting submission as same author works - native_submission_helper(adapter.clone(), mock.sender, recipient.clone(), None).unwrap(); + let err = suite + .execute_create_submission( + Addr::unchecked("anotheruser"), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[], + ) + .unwrap_err(); + assert_eq!( + ContractError::UnauthorizedSubmission {}, + err.downcast().unwrap() + ); + + suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "wynddao".to_owned(), + recipient.clone(), + &[], + ) + .unwrap(); - let response = adapter.submission(recipient.to_string()).unwrap(); - assert_eq!(response.url, "https://daodao.zone".to_owned()); + let response = suite.query_submission(recipient).unwrap(); + assert_eq!(response.url, "wynddao".to_owned()); } #[test] fn create_submission_required_deposit() { - let mock = MockBech32::new("mock"); - let adapter = setup_gauge_adapter( - mock.clone(), - Some(AssetUnchecked { - denom: UncheckedDenom::Native("juno".into()), - amount: 1_000u128.into(), - }), - ); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(100_000, "juno"), coin(100_000, "wynd")]) + .with_native_deposit(1_000) + .build(); - let recipient = mock.addr_make("recipient"); - mock.add_balance(&mock.sender.clone(), vec![coin(1_000, "wynd")]) - .unwrap(); - mock.add_balance(&mock.sender.clone(), vec![coin(1_000, "juno")]) - .unwrap(); + let recipient = "user".to_owned(); // Fails if no funds sent. - let err = native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - None, - ) - .unwrap_err(); + let err = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[], + ) + .unwrap_err(); assert_eq!( ContractError::PaymentError(cw_utils::PaymentError::NoFunds {}), err.downcast().unwrap() ); - // Fails if correct denom but not enough amount. - // Fails if no funds sent. - let err = native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - Some(Coin { - denom: "juno".into(), - amount: 999u128.into(), - }), - ) - .unwrap_err(); + // Fails if correct denom but not enought amount. + let err = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[coin(1, "juno")], + ) + .unwrap_err(); assert_eq!( ContractError::InvalidDepositAmount { @@ -183,16 +186,15 @@ fn create_submission_required_deposit() { ); // Fails if enough amount but incorrect denom. - let err = native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - Some(Coin { - denom: "wynd".into(), - amount: 1_000u128.into(), - }), - ) - .unwrap_err(); + let err = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[coin(1_000, "wynd")], + ) + .unwrap_err(); assert_eq!( ContractError::InvalidDepositType {}, @@ -200,69 +202,48 @@ fn create_submission_required_deposit() { ); // Valid submission. - native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - Some(Coin { - denom: "juno".into(), - amount: 1_000u128.into(), - }), - ) - .unwrap(); + _ = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[coin(1_000, "juno")], + ) + .unwrap(); assert_eq!( SubmissionResponse { - sender: mock.sender.clone(), - name: "DAOers".to_owned(), - url: "https://daodao.zone".to_owned(), - address: recipient.clone(), + sender: suite.owner.clone(), + name: "WYNDers".to_owned(), + url: "https://www.wynddao.com/".to_owned(), + address: Addr::unchecked(recipient.clone()), }, - adapter - .query(&AdapterQueryMsg::Submission { - address: recipient.to_string() - }) - .unwrap() + suite.query_submission(recipient).unwrap() ) } #[test] fn create_receive_required_deposit() { - let mock = MockBech32::new("mock"); - let cw20 = cw20_helper(mock.clone()); - let bad_cw20 = cw20_helper(mock.clone()); - let cw20_addr = cw20.address().unwrap(); - let bad_cw20_addr = bad_cw20.address().unwrap(); - println!("good cw20: {:#?}", cw20_addr); - println!("bad cw20: {:#?}", bad_cw20_addr); - let adapter = setup_gauge_adapter( - mock.clone(), - Some(AssetUnchecked { - denom: UncheckedDenom::Cw20(cw20_addr.to_string()), - amount: 1_000u128.into(), - }), - ); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(100_000, "juno")]) + .with_cw20_funds("owner", 1_000) + .with_cw20_deposit(1_000) + .build(); - let recipient = mock.sender_addr().to_string(); + let recipient = "user".to_owned(); + + let cw20_addr = suite.instantiate_token(suite.owner.clone().as_ref(), "moonbites", 1_000_000); - let binary_msg = to_json_binary(&ReceiveMsg::CreateSubmission { - name: "DAOers".into(), - url: "https://daodao.zone".into(), - address: recipient.clone(), - }) - .unwrap(); // Fails by sending wrong cw20. - let err = adapter - .call_as(&Addr::unchecked( - "mock1mzdhwvvh22wrt07w59wxyd58822qavwkx5lcej7aqfkpqqlhaqfsetqc4t", - )) - .execute( - &ExecuteMsg::Receive(cw20::Cw20ReceiveMsg { - sender: recipient.to_string(), - amount: Uint128::from(1_000u128), - msg: binary_msg.clone(), - }), - None, + let err = suite + .execute_receive_through_cw20( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + 1_000, + cw20_addr, ) .unwrap_err(); @@ -272,15 +253,14 @@ fn create_receive_required_deposit() { ); // Fails by sending less tokens than required. - let err = adapter - .call_as(&cw20.address().unwrap()) - .execute( - &ExecuteMsg::Receive(cw20::Cw20ReceiveMsg { - sender: recipient.to_string(), - amount: Uint128::from(999u128), - msg: binary_msg.clone(), - }), - None, + let err = suite + .execute_receive_through_cw20( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + 1, + suite.default_cw20.clone(), ) .unwrap_err(); @@ -292,49 +272,38 @@ fn create_receive_required_deposit() { ); // Valid submission. - adapter - .call_as(&cw20.address().unwrap()) - .execute( - &ExecuteMsg::Receive(cw20::Cw20ReceiveMsg { - sender: recipient.to_string(), - amount: Uint128::from(1_000u128), - msg: binary_msg, - }), - None, + _ = suite + .execute_receive_through_cw20( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + 1_000, + suite.default_cw20.clone(), ) .unwrap(); assert_eq!( SubmissionResponse { - sender: mock.sender.clone(), - name: "DAOers".to_owned(), - url: "https://daodao.zone".to_owned(), + sender: suite.owner.clone(), + name: "WYNDers".to_owned(), + url: "https://www.wynddao.com/".to_owned(), address: Addr::unchecked(recipient.clone()), }, - adapter - .query(&AdapterQueryMsg::Submission { - address: recipient.to_string() - }) - .unwrap() + suite.query_submission(recipient).unwrap() ); - assert_eq!( - 2, - adapter - .query::(&AdapterQueryMsg::AllSubmissions {}) - .unwrap() - .submissions - .len() - ) + assert_eq!(2, suite.query_submissions().unwrap().len()) } #[test] fn return_deposits_no_required_deposit() { - let mock = MockBech32::new("mock"); - let adapter = setup_gauge_adapter(mock.clone(), None); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(100_000, "juno")]) + .build(); - let err = adapter - .execute(&ExecuteMsg::ReturnDeposits {}, None) + let err = suite + .execute_return_deposit(suite.owner.clone().as_ref()) .unwrap_err(); assert_eq!(ContractError::NoDepositToRefund {}, err.downcast().unwrap()) @@ -342,245 +311,177 @@ fn return_deposits_no_required_deposit() { #[test] fn return_deposits_no_admin() { - let mock = MockBech32::new("mock"); - let bad_addr = mock.addr_make("einstien"); - let adapter = setup_gauge_adapter( - mock.clone(), - Some(AssetUnchecked { - denom: UncheckedDenom::Native("juno".into()), - amount: 1_000u128.into(), - }), - ); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(100_000, "juno")]) + .with_native_deposit(1_000) + .build(); - let err = adapter - .call_as(&bad_addr) - .execute(&ExecuteMsg::ReturnDeposits {}, None) - .unwrap_err(); + let err = suite.execute_return_deposit("einstein").unwrap_err(); assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()) } #[test] fn return_deposits_required_native_deposit() { - let mock = MockBech32::new("mock"); - let adapter = setup_gauge_adapter( - mock.clone(), - Some(AssetUnchecked { - denom: UncheckedDenom::Native("juno".into()), - amount: 1_000u128.into(), - }), - ); - mock.add_balance(&mock.sender, vec![coin(1_000u128, "juno")]) - .unwrap(); - let recipient = mock.addr_make("recipient"); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(1_000, "juno")]) + .with_native_deposit(1_000) + .build(); + + let recipient = "user".to_owned(); // Valid submission. - native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - Some(coin(1_000u128, "juno")), - ) - .unwrap(); + _ = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[coin(1_000, "juno")], + ) + .unwrap(); assert_eq!( - mock.query_balance(&mock.sender.clone(), "juno").unwrap(), - Uint128::zero() - ); - assert_eq!( - mock.query_balance(&recipient, "juno").unwrap(), - Uint128::zero() + suite.query_native_balance(suite.owner.as_ref()).unwrap(), + 0u128, ); + assert_eq!(suite.query_native_balance(&recipient).unwrap(), 0u128,); assert_eq!( - mock.query_balance(&adapter.address().unwrap(), "juno") + suite + .query_native_balance(suite.gauge_adapter.as_ref()) .unwrap(), - Uint128::from(1000u128) + 1_000u128, ); - adapter - .execute(&ExecuteMsg::ReturnDeposits {}, None) + _ = suite + .execute_return_deposit(suite.owner.clone().as_ref()) .unwrap(); + assert_eq!( - mock.query_balance(&mock.sender.clone(), "juno").unwrap(), - Uint128::from(1000u128) - ); - assert_eq!( - mock.query_balance(&recipient, "juno").unwrap(), - Uint128::zero() + suite.query_native_balance(suite.owner.as_ref()).unwrap(), + 1_000u128, ); + assert_eq!(suite.query_native_balance(&recipient).unwrap(), 0u128,); assert_eq!( - mock.query_balance(&adapter.address().unwrap(), "juno") + suite + .query_native_balance(suite.gauge_adapter.as_ref()) .unwrap(), - Uint128::zero() + 0u128, ); } #[test] fn return_deposits_required_native_deposit_multiple_deposits() { - let mock = MockBech32::new("mock"); - let adapter = setup_gauge_adapter( - mock.clone(), - Some(AssetUnchecked { - denom: UncheckedDenom::Native("juno".into()), - amount: 1_000u128.into(), - }), - ); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(1_000, "juno")]) + .with_funds("einstein", &[coin(1_000, "juno")]) + .with_native_deposit(1_000) + .build(); + + let recipient = "user".to_owned(); - let recipient = mock.addr_make("recipient"); - let einstien = mock - .addr_make_with_balance("einstien", vec![coin(1_000u128, "juno")]) - .unwrap(); - mock.add_balance(&mock.sender, vec![coin(1_000u128, "juno")]) - .unwrap(); // Valid submission. - native_submission_helper( - adapter.clone(), - mock.sender.clone(), - recipient.clone(), - Some(coin(1_000u128, "juno")), - ) - .unwrap(); + _ = suite + .execute_create_submission( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + &[coin(1_000, "juno")], + ) + .unwrap(); + // Valid submission. - native_submission_helper( - adapter.clone(), - einstien.clone(), - einstien.clone(), - Some(coin(1_000u128, "juno")), - ) - .unwrap(); + _ = suite + .execute_create_submission( + Addr::unchecked("einstein"), + "MIBers".to_owned(), + "https://www.mib.tech/".to_owned(), + "einstein".to_owned(), + &[coin(1_000, "juno")], + ) + .unwrap(); + + _ = suite + .execute_return_deposit(suite.owner.clone().as_ref()) + .unwrap(); - adapter.return_deposits().unwrap(); - assert_eq!( - mock.query_balance(&mock.sender.clone(), "juno").unwrap(), - Uint128::from(1000u128) - ); - assert_eq!( - mock.query_balance(&einstien, "juno").unwrap(), - Uint128::from(1000u128) - ); assert_eq!( - mock.query_balance(&recipient, "juno").unwrap(), - Uint128::zero() + suite.query_native_balance(suite.owner.as_ref()).unwrap(), + 1_000u128, ); + assert_eq!(suite.query_native_balance("einstein").unwrap(), 1_000u128,); + assert_eq!(suite.query_native_balance(&recipient).unwrap(), 0u128,); assert_eq!( - mock.query_balance(&adapter.address().unwrap(), "juno") + suite + .query_native_balance(suite.gauge_adapter.as_ref()) .unwrap(), - Uint128::zero() + 0u128, ); } #[test] fn return_deposits_required_cw20_deposit() { - let mock = MockBech32::new("mock"); - let cw20 = cw20_helper(mock.clone()); - let recipient = mock.addr_make("recipient"); - let adapter = setup_gauge_adapter( - mock.clone(), - Some(AssetUnchecked { - denom: UncheckedDenom::Cw20(cw20.addr_str().unwrap()), - amount: 1_000u128.into(), - }), - ); - let binary_msg = to_json_binary(&ReceiveMsg::CreateSubmission { - name: "DAOers".into(), - url: "https://daodao.zone".into(), - address: recipient.to_string(), - }) - .unwrap(); + let mut suite = SuiteBuilder::new() + .with_funds("owner", &[coin(1_000, "juno")]) + .with_cw20_funds("owner", 1_000) + .with_cw20_deposit(1_000) + .build(); + + let recipient = "user".to_owned(); // Valid submission. - cw20.send(1_000u128.into(), adapter.addr_str().unwrap(), binary_msg) + _ = suite + .execute_receive_through_cw20( + suite.owner.clone(), + "WYNDers".to_owned(), + "https://www.wynddao.com/".to_owned(), + recipient.clone(), + 1_000, + suite.default_cw20.clone(), + ) .unwrap(); assert_eq!( - cw20.balance(mock.sender.to_string()).unwrap().balance, - Uint128::from(999_000u128) + suite + .query_cw20_balance(suite.owner.as_ref(), &suite.default_cw20) + .unwrap(), + 0u128, ); assert_eq!( - cw20.balance(recipient.to_string()).unwrap().balance, - Uint128::zero() + suite + .query_cw20_balance(&recipient, &suite.default_cw20) + .unwrap(), + 0u128, ); assert_eq!( - cw20.balance(adapter.address().unwrap().to_string()) - .unwrap() - .balance, - Uint128::from(1_000u128), + suite + .query_cw20_balance(suite.gauge_adapter.as_ref(), &suite.default_cw20) + .unwrap(), + 1_000u128, ); - adapter.return_deposits().unwrap(); + _ = suite + .execute_return_deposit(suite.owner.clone().as_ref()) + .unwrap(); assert_eq!( - cw20.balance(mock.sender.to_string()).unwrap().balance, - Uint128::from(1_000_000u128), - ); - // Tokens are sent back to submission sender, not recipient. - assert_eq!( - cw20.balance(recipient.to_string()).unwrap().balance, - Uint128::zero(), + suite + .query_cw20_balance(suite.owner.as_ref(), &suite.default_cw20) + .unwrap(), + 1_000u128, ); + // Tokens are sent back to the address specified in the sumbission. assert_eq!( - cw20.balance(adapter.address().unwrap().to_string()) - .unwrap() - .balance, - Uint128::zero(), + suite + .query_cw20_balance(&recipient, &suite.default_cw20) + .unwrap(), + 0u128, ); -} - -#[test] -fn sample_gauge_msgs_cw20() { - let mock = MockBech32::new("mock"); - let addr_1 = mock.addr_make("addr1"); - let addr_2 = mock.addr_make("addr2"); - let addr_3 = mock.addr_make("addr3"); - let reward = Uint128::new(1_000_000); - let (adapter, cw20) = setup_cw20_reward_gauge_adapter(mock.clone(), None); - - adapter - .create_submission(addr_1.to_string(), "name".into(), "https://test.url".into()) - .unwrap(); - adapter - .create_submission(addr_2.to_string(), "name".into(), "https://test.url".into()) - .unwrap(); - - let selected = vec![ - (addr_1.to_string(), Decimal::percent(41)), - (addr_2.to_string(), Decimal::percent(33)), - (addr_3.to_string(), Decimal::percent(26)), - ]; - - let res: crate::msg::SampleGaugeMsgsResponse = - adapter.sample_gauge_msgs(selected.clone()).unwrap(); - assert_eq!(res.execute.len(), 3); assert_eq!( - res.execute, - [ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cw20.addr_str().unwrap(), - msg: to_json_binary(&AbsCw20ExecuteMsg::Transfer { - recipient: addr_1.to_string(), - amount: reward * Decimal::percent(41) - }) - .unwrap(), - funds: vec![] - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cw20.addr_str().unwrap(), - msg: to_json_binary(&AbsCw20ExecuteMsg::Transfer { - recipient: addr_2.to_string(), - amount: reward * Decimal::percent(33) - }) - .unwrap(), - funds: vec![] - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cw20.addr_str().unwrap(), - msg: to_json_binary(&AbsCw20ExecuteMsg::Transfer { - recipient: addr_3.to_string(), - amount: reward * Decimal::percent(26) - }) - .unwrap(), - funds: vec![] - }), - ] + suite + .query_cw20_balance(suite.gauge_adapter.as_ref(), &suite.default_cw20) + .unwrap(), + 0u128, ); } diff --git a/contracts/gauges/gauge-adapter/src/multitest/suite.rs b/contracts/gauges/gauge-adapter/src/multitest/suite.rs index a667097fd..564ce9762 100644 --- a/contracts/gauges/gauge-adapter/src/multitest/suite.rs +++ b/contracts/gauges/gauge-adapter/src/multitest/suite.rs @@ -1,116 +1,333 @@ -use abstract_cw_plus_interface::cw20_base::Cw20Base; -use cosmwasm_std::{Addr, Coin, Uint128}; -use cw_orch::{interface, mock::cw_multi_test::AppResponse, prelude::*}; -use cw_orch_core::CwEnvError; +use crate::msg::AssetUnchecked; +use crate::msg::CheckOptionResponse; +use crate::msg::{ + AdapterQueryMsg, AllOptionsResponse, AllSubmissionsResponse, ExecuteMsg, ReceiveMsg, + SubmissionResponse, +}; +use anyhow::Result as AnyResult; +use cosmwasm_std::{to_json_binary, Addr, Binary, Coin, Uint128}; +use cw20::{BalanceResponse, Cw20QueryMsg}; +use cw20::{Cw20Coin, MinterResponse}; +use cw20_base::msg::ExecuteMsg as Cw20BaseExecuteMsg; +use cw20_base::msg::InstantiateMsg as Cw20BaseInstantiateMsg; -use abstract_cw20::{Cw20Coin as AbsCw20Coin, MinterResponse}; +use cw_denom::UncheckedDenom; +use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; -use crate::{ - contract::{execute, instantiate, migrate, query}, - msg::{AdapterQueryMsg as QueryMsg, AssetUnchecked, ExecuteMsg, InstantiateMsg, MigrateMsg}, -}; +pub const NATIVE: &str = "juno"; +pub const CW20: &str = "wynd"; +pub const OWNER: &str = "owner"; // Store the marketing gauge adapter contract and returns the code id. -#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] -pub struct GaugeAdapter; - -impl Uploadable for GaugeAdapter { - /// Return the path to the wasm file corresponding to the contract - fn wasm(_chain: &ChainInfoOwned) -> WasmPath { - artifacts_dir_from_workspace!() - .find_wasm_path("gauge_adapter") - .unwrap() - } - /// Returns a CosmWasm contract wrapper - fn wrapper() -> Box> { - Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate(migrate)) - } +fn store_gauge_adapter(app: &mut App) -> u64 { + let contract = Box::new(ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + )); + + app.store_code(contract) } -pub fn setup_gauge_adapter( - mock: MockBech32, - required_deposit: Option, -) -> GaugeAdapter { - let adapter = GaugeAdapter::new("gauge_adapter", mock.clone()); - adapter.upload().unwrap(); - - let instantiate = InstantiateMsg { - admin: mock.sender_addr().to_string(), - required_deposit, - reward: AssetUnchecked::new_native("juno", 1_000_000), - community_pool: mock.addr_make("community_pool").to_string(), - }; - adapter.instantiate(&instantiate, None, None).unwrap(); - adapter +// Store the cw20 contract and returns the code id. +fn store_cw20(app: &mut App) -> u64 { + let contract = Box::new(ContractWrapper::new_with_empty( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + )); + + app.store_code(contract) } -pub fn setup_cw20_reward_gauge_adapter( - mock: MockBech32, +#[derive(Debug)] +pub struct SuiteBuilder { + // Gauge adapter's instantiate params + community_pool: String, required_deposit: Option, -) -> (GaugeAdapter, Cw20Base) { - let adapter = GaugeAdapter::new("gauge_adapter", mock.clone()); - adapter.upload().unwrap(); - let cw20 = cw20_helper(mock.clone()); - - let instantiate = InstantiateMsg { - admin: mock.sender_addr().to_string(), - required_deposit, - reward: AssetUnchecked::new_cw20(&cw20.addr_str().unwrap(), 1_000_000), - community_pool: mock.addr_make("community_pool").to_string(), - }; - adapter.instantiate(&instantiate, None, None).unwrap(); - (adapter, cw20) + reward: AssetUnchecked, + funds: Vec<(Addr, Vec)>, + cw20_funds: Vec, } -// -pub fn native_submission_helper( - adapter: GaugeAdapter, - sender: Addr, - recipient: Addr, - native_tokens: Option, -) -> Result { - if let Some(assets) = native_tokens.clone() { - adapter.call_as(&sender).execute( - &crate::msg::ExecuteMsg::CreateSubmission { - name: "DAOers".to_string(), - url: "https://daodao.zone".to_string(), - address: recipient.to_string(), +impl SuiteBuilder { + pub fn new() -> Self { + Self { + community_pool: "community".to_owned(), + required_deposit: None, + reward: AssetUnchecked { + denom: UncheckedDenom::Native(NATIVE.into()), + amount: Uint128::new(1_000_000), }, - Some(&[assets]), - ) - } else { - adapter.call_as(&sender).execute( - &crate::msg::ExecuteMsg::CreateSubmission { - name: "DAOers".to_string(), - url: "https://daodao.zone".to_string(), - address: recipient.to_string(), + funds: vec![], + cw20_funds: vec![], + } + } + + pub fn with_community_pool(mut self, community_pool: &str) -> Self { + self.community_pool = community_pool.to_string(); + self + } + + // Allows to initialize the suite with native coins associated to an address. + pub fn with_funds(mut self, addr: &str, funds: &[Coin]) -> Self { + self.funds.push((Addr::unchecked(addr), funds.into())); + self + } + + // Allows to initialize the suite with default cw20 tokens associated to an address. + pub fn with_cw20_funds(mut self, addr: &str, amount: u128) -> Self { + self.cw20_funds.push(Cw20Coin { + address: addr.into(), + amount: Uint128::from(amount), + }); + self + } + + // Allows to initialize the marketing gauge adapter with required native coins in the config. + pub fn with_native_deposit(mut self, amount: u128) -> Self { + self.required_deposit = Some(AssetUnchecked { + denom: UncheckedDenom::Native(NATIVE.into()), + amount: Uint128::from(amount), + }); + self + } + + // Allows to initialize the marketing gauge adapter with required cw20 tokens in the config. + pub fn with_cw20_deposit(mut self, amount: u128) -> Self { + self.required_deposit = Some(AssetUnchecked { + denom: UncheckedDenom::Cw20("contract0".into()), + amount: Uint128::from(amount), + }); + self + } + + // Instantiate a marketing gauge adapter and returns its address. + fn instantiate_marketing_gauge_adapter( + &self, + app: &mut App, + gauge_id: u64, + owner: &str, + ) -> Addr { + app.instantiate_contract( + gauge_id, + Addr::unchecked(owner), + &crate::msg::InstantiateMsg { + owner: owner.to_owned(), + required_deposit: self.required_deposit.clone(), + community_pool: self.community_pool.clone(), + reward: self.reward.clone(), }, + &[], + "Marketing Gauge Adapter", None, ) + .unwrap() + } + + // Instantiate a cw20 and returns its address. + fn instantiate_default_cw20(&self, app: &mut App, cw20_code_id: u64, owner: &str) -> Addr { + let res = app + .instantiate_contract( + cw20_code_id, + Addr::unchecked(owner), + &Cw20BaseInstantiateMsg { + name: CW20.to_owned(), + symbol: CW20.to_owned(), + decimals: 6, + initial_balances: self.cw20_funds.clone(), + mint: Some(MinterResponse { + minter: owner.to_string(), + cap: None, + }), + marketing: None, + }, + &[], + CW20.to_owned(), + None, + ) + .unwrap(); + println!("{:#?}", res); + res + } + + #[track_caller] + pub fn build(self) -> Suite { + let mut app = App::default(); + let owner = Addr::unchecked(OWNER); + + // Store required contracts. + let cw20_code_id = store_cw20(&mut app); + let gauge_adapter_code_id = store_gauge_adapter(&mut app); + + // cw20 address must be "contract0" otherwise cannot use `with_cw20_deposit()`. Not very + // elegant but do its job. + let cw20_addr = self.instantiate_default_cw20(&mut app, cw20_code_id, OWNER); + + // Instantiate default contracts. + let gauge_adapter_addr = + self.instantiate_marketing_gauge_adapter(&mut app, gauge_adapter_code_id, OWNER); + + // Mint initial native token if any. + app.init_modules(|router, _, storage| -> AnyResult<()> { + for (addr, coin) in self.funds { + router.bank.init_balance(storage, &addr, coin)?; + } + Ok(()) + }) + .unwrap(); + + Suite { + owner, + app, + gauge_adapter: gauge_adapter_addr, + default_cw20: cw20_addr, + cw20_code_id, + } } } -pub fn cw20_helper(mock: MockBech32) -> Cw20Base { - let cw20 = Cw20Base::new("cw20", mock.clone()); - cw20.upload().unwrap(); - init_cw20(cw20.clone(), mock.sender.to_string()); - cw20 +pub struct Suite { + pub owner: Addr, + pub app: App, + pub gauge_adapter: Addr, + pub default_cw20: Addr, + // This is stored to instantiate other cw20 tokens in tests. + cw20_code_id: u64, } -pub fn init_cw20(cw20: Cw20Base, minter: String) -> String { - let init_msg = abstract_cw20_base::msg::InstantiateMsg { - name: "test".to_string(), - symbol: "TEST".to_string(), - decimals: 6u8, - initial_balances: vec![AbsCw20Coin { - address: minter.clone(), - amount: Uint128::from(1_000_000u128), - }], - mint: Some(MinterResponse { minter, cap: None }), - marketing: None, - }; - cw20.instantiate(&init_msg, None, None).unwrap(); - let addr = cw20.address().unwrap(); - println!("correct cw20 addr: {:#?}", addr.clone()); - addr.to_string() +impl Suite { + // --------------------------------------------------------------------------------------------- + // Execute + // --------------------------------------------------------------------------------------------- + pub fn execute_create_submission( + &mut self, + sender: Addr, + name: String, + url: String, + address: String, + funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + sender, + self.gauge_adapter.clone(), + &ExecuteMsg::CreateSubmission { name, url, address }, + funds, + ) + } + + pub fn execute_receive_through_cw20( + &mut self, + sender: Addr, + name: String, + url: String, + address: String, + // amount refers to CW20 amount. + amount: u128, + cw20_addr: Addr, + ) -> AnyResult { + let msg: Binary = to_json_binary(&ReceiveMsg::CreateSubmission { name, url, address })?; + + self.app.execute_contract( + sender, + cw20_addr, + &Cw20BaseExecuteMsg::Send { + contract: self.gauge_adapter.to_string(), + amount: Uint128::from(amount), + msg, + }, + &[], + ) + } + + pub fn execute_return_deposit(&mut self, sender: &str) -> AnyResult { + self.app.execute_contract( + Addr::unchecked(sender), + self.gauge_adapter.clone(), + &ExecuteMsg::ReturnDeposits {}, + &[], + ) + } + + // --------------------------------------------------------------------------------------------- + // Queries + // --------------------------------------------------------------------------------------------- + pub fn query_submission(&self, address: String) -> AnyResult { + Ok(self.app.wrap().query_wasm_smart( + self.gauge_adapter.clone(), + &AdapterQueryMsg::Submission { address }, + )?) + } + + pub fn query_submissions(&self) -> AnyResult> { + let res: AllSubmissionsResponse = self.app.wrap().query_wasm_smart( + self.gauge_adapter.clone(), + &AdapterQueryMsg::AllSubmissions {}, + )?; + + Ok(res.submissions) + } + + pub fn query_all_options(&self) -> AnyResult> { + let res: AllOptionsResponse = self + .app + .wrap() + .query_wasm_smart(self.gauge_adapter.clone(), &AdapterQueryMsg::AllOptions {})?; + + Ok(res.options) + } + + pub fn query_check_option(&self, option: String) -> AnyResult { + let res: CheckOptionResponse = self.app.wrap().query_wasm_smart( + self.gauge_adapter.clone(), + &AdapterQueryMsg::CheckOption { option }, + )?; + + Ok(res.valid) + } + + // --------------------------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------------------------- + + // Instantiate a cw20 token and assign to address a specific amount. + pub fn instantiate_token(&mut self, address: &str, token: &str, amount: u128) -> Addr { + self.app + .instantiate_contract( + self.cw20_code_id, + Addr::unchecked(address), + &Cw20BaseInstantiateMsg { + name: token.to_owned(), + symbol: token.to_owned(), + decimals: 6, + initial_balances: vec![Cw20Coin { + address: address.to_owned(), + amount: Uint128::from(amount), + }], + mint: None, + marketing: None, + }, + &[], + token, + None, + ) + .unwrap() + } + + pub fn query_cw20_balance(&self, user: &str, contract: &Addr) -> AnyResult { + let balance: BalanceResponse = self.app.wrap().query_wasm_smart( + contract, + &Cw20QueryMsg::Balance { + address: user.to_owned(), + }, + )?; + + Ok(balance.balance.into()) + } + + pub fn query_native_balance(&self, user: &str) -> AnyResult { + let balance = self.app.wrap().query_balance(user, NATIVE)?; + + Ok(balance.amount.into()) + } } diff --git a/contracts/gauges/gauge-adapter/src/state.rs b/contracts/gauges/gauge-adapter/src/state.rs index 68bd19d12..df2e2cea2 100644 --- a/contracts/gauges/gauge-adapter/src/state.rs +++ b/contracts/gauges/gauge-adapter/src/state.rs @@ -6,7 +6,7 @@ use cw_storage_plus::{Item, Map}; #[cw_serde] pub struct Config { /// Address that is allowed to return deposits. - pub admin: Addr, + pub owner: Addr, /// Deposit required for valid submission. pub required_deposit: Option, /// Address of contract where each deposit is transferred. diff --git a/contracts/gauges/gauge/Cargo.toml b/contracts/gauges/gauge/Cargo.toml index 6cbc8a550..9ed133f95 100644 --- a/contracts/gauges/gauge/Cargo.toml +++ b/contracts/gauges/gauge/Cargo.toml @@ -19,6 +19,7 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } dao-interface = { workspace = true } +cw-ownable = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } diff --git a/contracts/gauges/gauge/src/contract.rs b/contracts/gauges/gauge/src/contract.rs index 20ca4faec..b86990698 100644 --- a/contracts/gauges/gauge/src/contract.rs +++ b/contracts/gauges/gauge/src/contract.rs @@ -34,14 +34,13 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + cw_ownable::initialize_owner(deps.storage, deps.api, Some(&msg.owner))?; let voting_powers = deps.api.addr_validate(&msg.voting_powers)?; let hook_caller = deps.api.addr_validate(&msg.hook_caller)?; - let owner = deps.api.addr_validate(&msg.owner)?; let config = Config { voting_powers, hook_caller, - owner, dao_core: info.sender, }; CONFIG.save(deps.storage, &config)?; @@ -72,6 +71,7 @@ pub fn execute( ExecuteMsg::CreateGauge(options) => execute::create_gauge(deps, env, info.sender, options), ExecuteMsg::UpdateGauge { gauge_id, + epoch_limit, epoch_size, min_percent_selected, max_options_selected, @@ -81,6 +81,7 @@ pub fn execute( info.sender, gauge_id, epoch_size, + epoch_limit, min_percent_selected, max_options_selected, max_available_percentage, @@ -103,11 +104,15 @@ pub fn execute( } mod execute { + use cosmwasm_std::CosmosMsg; use cw4::MemberDiff; use dao_hooks::{nft_stake::NftStakeChangedHookMsg, stake::StakeChangedHookMsg}; use super::*; - use crate::state::{remove_tally, update_tallies, Reset, Vote}; + use crate::{ + msg::CreateGaugeReply, + state::{remove_tally, update_tallies, Reset, Vote}, + }; use std::collections::HashMap; pub fn member_changed( @@ -366,16 +371,14 @@ mod execute { sender: Addr, options: GaugeConfig, ) -> Result { - let config = CONFIG.load(deps.storage)?; - if sender != config.owner { - return Err(ContractError::Unauthorized {}); - } + cw_ownable::assert_owner(deps.storage, &sender)?; let adapter = attach_gauge(deps, env, options)?; Ok(Response::new() .add_attribute("action", "create_gauge") - .add_attribute("adapter", adapter)) + .add_attribute("gauge-id", adapter.id.to_string()) + .add_attribute("adapter", adapter.addr)) } pub fn attach_gauge( @@ -389,8 +392,9 @@ mod execute { max_options_selected, max_available_percentage, reset_epoch, + total_epochs, }: GaugeConfig, - ) -> Result { + ) -> Result { let adapter = deps.api.addr_validate(&adapter)?; // gauge parameter validation ensure!(epoch_size > 60u64, ContractError::EpochSizeTooShort {}); @@ -408,6 +412,7 @@ mod execute { title, adapter: adapter.clone(), epoch: epoch_size, + count: Some(0), min_percent_selected, max_options_selected, max_available_percentage, @@ -419,6 +424,7 @@ mod execute { reset_each: r, next: env.block.time.plus_seconds(r).seconds(), }), + total_epoch: total_epochs, }; let last_id: GaugeId = fetch_last_id(deps.storage)?; GAUGES.save(deps.storage, last_id, &gauge)?; @@ -434,28 +440,33 @@ mod execute { Ok::<_, ContractError>(()) })?; - Ok(adapter) + Ok(CreateGaugeReply { + id: last_id, + addr: adapter.to_string(), + }) } pub fn update_gauge( deps: DepsMut, sender: Addr, gauge_id: u64, + epoch_limit: Option, epoch_size: Option, min_percent_selected: Option, max_options_selected: Option, max_available_percentage: Option, ) -> Result { - let config = CONFIG.load(deps.storage)?; - if sender != config.owner { - return Err(ContractError::Unauthorized {}); - } + cw_ownable::assert_owner(deps.storage, &sender)?; let mut gauge = GAUGES.load(deps.storage, gauge_id)?; if let Some(epoch_size) = epoch_size { ensure!(epoch_size > 60u64, ContractError::EpochSizeTooShort {}); gauge.epoch = epoch_size; } + if let Some(epoch_limit) = epoch_limit { + let e = gauge.gauge_epoch()?; + ensure!(e < epoch_limit, ContractError::EpochLimitTooShort {}) + } if let Some(min_percent_selected) = min_percent_selected { if min_percent_selected.is_zero() { gauge.min_percent_selected = None @@ -495,10 +506,7 @@ mod execute { sender: Addr, gauge_id: GaugeId, ) -> Result { - let config = CONFIG.load(deps.storage)?; - if sender != config.owner { - return Err(ContractError::Unauthorized {}); - } + cw_ownable::assert_owner(deps.storage, &sender)?; let gauge = GAUGES.load(deps.storage, gauge_id)?; let gauge = Gauge { @@ -524,10 +532,7 @@ mod execute { }; // only owner can remove option for now - if sender != CONFIG.load(deps.storage)?.owner { - return Err(ContractError::Unauthorized {}); - } - + cw_ownable::assert_owner(deps.storage, &sender)?; remove_tally(deps.storage, gauge_id, &option)?; Ok(Response::new() @@ -754,7 +759,8 @@ mod execute { } pub fn execute(deps: DepsMut, env: Env, gauge_id: u64) -> Result { - let mut gauge = GAUGES.load(deps.storage, gauge_id)?; + let mut gauge = GAUGES.load(deps.storage, gauge_id.clone())?; + let mut msgs = vec![]; if gauge.is_stopped { return Err(ContractError::GaugeStopped(gauge_id)); @@ -796,12 +802,25 @@ mod execute { &AdapterQueryMsg::SampleGaugeMsgs { selected }, )?; + // Adds msgs from query to response + msgs.extend(execute_messages.execute); + + // increments epoch count + gauge.count = gauge.increment_gauge_count()?; + + // Will add a stop_gauge msg to list of msgs for DAO if gauge config has total_epochs set + if gauge.will_reach_epoch_limit() { + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&ExecuteMsg::StopGauge { gauge: gauge_id })?, + funds: vec![], + })) + } + let config = CONFIG.load(deps.storage)?; let execute_msg = WasmMsg::Execute { contract_addr: config.dao_core.to_string(), - msg: to_json_binary(&DaoExecuteMsg::ExecuteProposalHook { - msgs: execute_messages.execute, - })?, + msg: to_json_binary(&DaoExecuteMsg::ExecuteProposalHook { msgs })?, funds: vec![], }; @@ -874,6 +893,7 @@ mod query { is_stopped: gauge.is_stopped, next_epoch: gauge.next_epoch, reset: gauge.reset, + total_epochs: gauge.total_epoch, } } diff --git a/contracts/gauges/gauge/src/error.rs b/contracts/gauges/gauge/src/error.rs index c320a4ec7..beacdd402 100644 --- a/contracts/gauges/gauge/src/error.rs +++ b/contracts/gauges/gauge/src/error.rs @@ -8,6 +8,8 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized {}, + #[error("{0}")] + Ownership(#[from] cw_ownable::OwnershipError), #[error("Gauge with ID {0} does not exists")] GaugeMissing(u64), @@ -51,6 +53,8 @@ pub enum ContractError { #[error("Epoch size must be bigger then 60 seconds")] EpochSizeTooShort {}, + #[error("Epoch limit must be bigger then current epoch")] + EpochLimitTooShort {}, #[error("Minimum percent selected parameter needs to be smaller then 1.0")] MinPercentSelectedTooBig {}, diff --git a/contracts/gauges/gauge/src/msg.rs b/contracts/gauges/gauge/src/msg.rs index 3578e32ec..df7874d54 100644 --- a/contracts/gauges/gauge/src/msg.rs +++ b/contracts/gauges/gauge/src/msg.rs @@ -11,9 +11,9 @@ type GaugeId = u64; pub struct InstantiateMsg { /// Address of contract to that contains all voting powers (where we query) pub voting_powers: String, - /// Addres that will call voting power change hooks (often same as voting power contract) + /// Address that will call voting power change hooks (often same as voting power contract) pub hook_caller: String, - /// Address that can add new gauges or stop them + /// Optional Address that can add new gauges or stop them pub owner: String, /// Allow attaching multiple adaptors during instantiation. /// Important, as instantiation and CreateGauge both come from DAO proposals @@ -39,6 +39,8 @@ pub struct GaugeConfig { pub max_available_percentage: Option, /// If set, the gauge can be reset periodically, every `reset_epoch` seconds. pub reset_epoch: Option, + /// if set, the gauge will disable itself after this many epochs + pub total_epochs: Option, } #[cw_serde] @@ -61,6 +63,7 @@ pub enum ExecuteMsg { min_percent_selected: Option, max_options_selected: Option, max_available_percentage: Option, + epoch_limit: Option, }, /// Stops a given gauge, meaning it will not execute any more messages, /// Or receive any more updates on MemberChangedHook. @@ -94,37 +97,46 @@ pub enum ExecuteMsg { pub struct CreateGaugeReply { /// Id of the gauge that was just created pub id: u64, + pub addr: String, } /// Queries the gauge exposes #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + /// General contract info #[returns(dao_interface::voting::InfoResponse)] Info {}, + /// Returns details for a specific gauge. #[returns(GaugeResponse)] Gauge { id: u64 }, + /// List all gauges #[returns(ListGaugesResponse)] ListGauges { start_after: Option, limit: Option, }, + /// Returns the vote for a given voter #[returns(VoteResponse)] Vote { gauge: u64, voter: String }, + /// Returns a list of all unexpired votes for a specific gauge-id #[returns(ListVotesResponse)] ListVotes { gauge: u64, start_after: Option, limit: Option, }, + /// Returns a list of all options available to vote for a specific gauge-id #[returns(ListOptionsResponse)] ListOptions { gauge: u64, start_after: Option, limit: Option, }, + /// Returns the selected messages that were determined by voting #[returns(SelectedSetResponse)] SelectedSet { gauge: u64 }, + /// Returns the last selected messages that were executed by the DAO #[returns(LastExecutedSetResponse)] LastExecutedSet { gauge: u64 }, } @@ -139,6 +151,8 @@ pub struct GaugeResponse { pub adapter: String, /// Frequency (in seconds) the gauge executes messages, typically something like 7*86400 pub epoch_size: u64, + /// Total epoch duration + pub total_epochs: Option, /// Minimum percentage of votes needed by a given option to be in the selected set. /// If unset, there is no minimum percentage, just the `max_options_selected` limit. pub min_percent_selected: Option, diff --git a/contracts/gauges/gauge/src/multitest/gauge.rs b/contracts/gauges/gauge/src/multitest/gauge.rs index 1b9543f4f..57aabeffa 100644 --- a/contracts/gauges/gauge/src/multitest/gauge.rs +++ b/contracts/gauges/gauge/src/multitest/gauge.rs @@ -25,6 +25,7 @@ fn create_gauge() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -42,6 +43,7 @@ fn create_gauge() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None } ); } @@ -62,6 +64,7 @@ fn gauge_can_upgrade_from_self() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -82,6 +85,7 @@ fn gauge_can_upgrade_from_self() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None } ); } @@ -102,6 +106,7 @@ fn gauge_migrate_with_next_epochs() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -120,6 +125,7 @@ fn gauge_migrate_with_next_epochs() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None } ); @@ -153,6 +159,7 @@ fn gauge_migrate_with_next_epochs() { is_stopped: false, next_epoch: suite.current_time() + 14 * 86400, reset: None, + total_epochs: None } ); @@ -186,7 +193,13 @@ fn execute_gauge() { suite.next_block(); let gauge_config = suite - .instantiate_adapter_and_return_config(&[voter1, voter2], reward_to_distribute, None, None) + .instantiate_adapter_and_return_config( + &[voter1, voter2], + reward_to_distribute, + None, + None, + None, + ) .unwrap(); suite .propose_update_proposal_module(voter1.to_string(), vec![gauge_config]) @@ -289,6 +302,7 @@ fn query_last_execution() { (1000, "ujuno"), None, None, + None, ) .unwrap(); let gauge_id = 0; @@ -381,7 +395,7 @@ fn execute_gauge_twice_same_epoch() { suite.next_block(); let gauge_config = suite - .instantiate_adapter_and_return_config(&[voter1, voter2], (1000, "ujuno"), None, None) // reward per + .instantiate_adapter_and_return_config(&[voter1, voter2], (1000, "ujuno"), None, None, None) // reward per // epoch .unwrap(); suite @@ -492,7 +506,13 @@ fn execute_stopped_gauge() { suite.next_block(); let gauge_config = suite - .instantiate_adapter_and_return_config(&[voter1, voter2], reward_to_distribute, None, None) + .instantiate_adapter_and_return_config( + &[voter1, voter2], + reward_to_distribute, + None, + None, + None, + ) .unwrap(); suite .propose_update_proposal_module(voter1.to_string(), vec![gauge_config]) @@ -520,11 +540,14 @@ fn execute_stopped_gauge() { let err = suite .stop_gauge(&gauge_contract, voter1, gauge_id) .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + assert_eq!( + ContractError::Ownership(cw_ownable::OwnershipError::NotOwner), + err.downcast().unwrap() + ); // stop the gauge by owner suite - .stop_gauge(&gauge_contract, suite.owner.clone(), gauge_id) + .stop_gauge(&gauge_contract, suite.core.clone(), gauge_id) .unwrap(); // vote for one of the options in gauge @@ -578,6 +601,7 @@ fn update_gauge() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -588,6 +612,7 @@ fn update_gauge() { (1000, "uusdc"), None, None, + None, ) .unwrap(); @@ -606,6 +631,7 @@ fn update_gauge() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None }, GaugeResponse { id: 1, @@ -618,22 +644,25 @@ fn update_gauge() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None } ] ); // update parameters on the first gauge - let owner = suite.owner.clone(); + let dao = suite.core.clone(); let new_epoch = EPOCH * 2; + let epoch_limit = 8u64; let new_min_percent = Some(Decimal::percent(10)); let new_max_options = 15; let new_max_available_percentage = Some(Decimal::percent(5)); suite .update_gauge( - &owner, + &dao.as_str(), gauge_contract.clone(), 0, new_epoch, + epoch_limit, new_min_percent, new_max_options, new_max_available_percentage, @@ -655,6 +684,7 @@ fn update_gauge() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None }, GaugeResponse { id: 1, @@ -667,6 +697,7 @@ fn update_gauge() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None } ] ); @@ -674,10 +705,11 @@ fn update_gauge() { // clean setting of min_percent_selected on second gauge suite .update_gauge( - &owner, + &dao.as_str(), gauge_contract.clone(), 1, None, + epoch_limit, Some(Decimal::zero()), None, None, @@ -699,6 +731,7 @@ fn update_gauge() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None }, GaugeResponse { id: 1, @@ -711,6 +744,7 @@ fn update_gauge() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None } ] ); @@ -722,19 +756,24 @@ fn update_gauge() { gauge_contract.clone(), 0, new_epoch, + epoch_limit, new_min_percent, new_max_options, None, ) .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + assert_eq!( + ContractError::Ownership(cw_ownable::OwnershipError::NotOwner), + err.downcast().unwrap() + ); let err = suite .update_gauge( - &owner, + &dao.as_str(), gauge_contract.clone(), 0, 50, + epoch_limit, new_min_percent, new_max_options, None, @@ -744,10 +783,11 @@ fn update_gauge() { let err = suite .update_gauge( - &owner, + &dao.as_str(), gauge_contract.clone(), 0, new_epoch, + epoch_limit, Some(Decimal::one()), new_max_options, None, @@ -760,10 +800,11 @@ fn update_gauge() { let err = suite .update_gauge( - &owner, + &dao.as_str(), gauge_contract.clone(), 0, new_epoch, + epoch_limit, new_min_percent, 0, None, @@ -776,10 +817,11 @@ fn update_gauge() { let err = suite .update_gauge( - &owner, + &dao.as_str(), gauge_contract, 1, None, + epoch_limit, Some(Decimal::zero()), None, Some(Decimal::percent(101)), diff --git a/contracts/gauges/gauge/src/multitest/reset.rs b/contracts/gauges/gauge/src/multitest/reset.rs index ffe572bd4..32d59a61a 100644 --- a/contracts/gauges/gauge/src/multitest/reset.rs +++ b/contracts/gauges/gauge/src/multitest/reset.rs @@ -27,6 +27,7 @@ fn basic_gauge_reset() { reward_to_distribute, None, RESET_EPOCH, + None, ) .unwrap(); suite @@ -48,7 +49,6 @@ fn basic_gauge_reset() { .unwrap(); let proposal_modules = suite.query_proposal_modules().unwrap(); let gauge_contract = proposal_modules[1].clone(); - let gauge_id = 0; // vote for one of the options in gauge @@ -179,6 +179,7 @@ fn gauge_migrate_with_reset() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -197,6 +198,7 @@ fn gauge_migrate_with_reset() { is_stopped: false, next_epoch: suite.current_time() + 7 * 86400, reset: None, + total_epochs: None, } ); @@ -262,6 +264,7 @@ fn gauge_migrate_with_reset() { reset_each: RESET_EPOCH, next: suite.current_time() + 100, }), + total_epochs: None, } ); } @@ -295,6 +298,7 @@ fn gauge_migrate_keeps_last_reset() { (1000, "ujuno"), None, Some(RESET_EPOCH), + None, ) .unwrap(); let gauge_id = 0; @@ -346,6 +350,7 @@ fn partial_reset() { reward_to_distribute, None, RESET_EPOCH, + None, ) .unwrap(); suite @@ -415,3 +420,76 @@ fn partial_reset() { .reset_gauge("someone", &gauge_contract, gauge_id, 1) .unwrap(); } + +#[test] +fn test_epoch_limit() -> anyhow::Result<()> { + let voter1 = "voter1"; + let voter2 = "voter2"; + let reward_to_distribute = (2000, "ujuno"); + let mut suite = SuiteBuilder::new() + .with_voting_members(&[(voter1, 100), (voter2, 100)]) + .with_core_balance((4000, "ujuno")) + .build(); + suite.next_block(); + let mut gauge_config = suite + .instantiate_adapter_and_return_config( + &[voter1, voter2], + reward_to_distribute, + None, + RESET_EPOCH, + None, + ) + .unwrap(); + gauge_config.total_epochs = Some(2); + + suite + .propose_update_proposal_module(voter1.to_string(), vec![gauge_config]) + .unwrap(); + + suite.next_block(); + let proposal = suite.list_proposals().unwrap()[0]; + suite + .place_vote_single(voter1, proposal, Vote::Yes) + .unwrap(); + suite + .place_vote_single(voter2, proposal, Vote::Yes) + .unwrap(); + + suite.next_block(); + suite + .execute_single_proposal(voter1.to_string(), proposal) + .unwrap(); + let proposal_modules = suite.query_proposal_modules().unwrap(); + let gauge_contract = proposal_modules[1].clone(); + + let gauge_id = 0; + + // vote for the gauge options + suite + .place_vote( + &gauge_contract, + voter1.to_owned(), + gauge_id, + Some(voter1.to_owned()), + ) + .unwrap(); + suite + .place_vote( + &gauge_contract, + voter2.to_owned(), + gauge_id, + Some(voter2.to_owned()), + ) + .unwrap(); + + // before advancing specified epoch tally won't get sampled + suite.advance_time(EPOCH); + suite + .execute_options(&gauge_contract, voter1, gauge_id) + .unwrap(); + suite.advance_time(EPOCH); + suite + .execute_options(&gauge_contract, voter1, gauge_id) + .unwrap(); + Ok(()) +} diff --git a/contracts/gauges/gauge/src/multitest/suite.rs b/contracts/gauges/gauge/src/multitest/suite.rs index de16b1d32..3440728fc 100644 --- a/contracts/gauges/gauge/src/multitest/suite.rs +++ b/contracts/gauges/gauge/src/multitest/suite.rs @@ -518,7 +518,7 @@ impl Suite { msg: to_json_binary(&InstantiateMsg { voting_powers: self.voting.to_string(), hook_caller: self.group_contract.to_string(), - owner: self.owner.clone(), + owner: self.core.to_string(), gauges: gauge_config.into(), })?, admin: Some(Admin::Address { @@ -559,7 +559,7 @@ impl Suite { msg: to_json_binary(&InstantiateMsg { voting_powers: self.voting.to_string(), hook_caller: Addr::unchecked(hook_caller).to_string(), - owner: self.owner.clone(), + owner: self.core.to_string(), gauges: gauge_config.into(), })?, admin: Some(Admin::Address { @@ -616,16 +616,18 @@ impl Suite { to_distribute: (u128, &str), max_available_percentage: impl Into>, reset_epoch: impl Into>, + epoch_limit: impl Into>, ) -> AnyResult { let option = self.instantiate_adapter_and_return_config( options, to_distribute, max_available_percentage, reset_epoch, + epoch_limit, )?; let gauge_adapter = option.adapter.clone(); self.app.execute_contract( - Addr::unchecked(&self.owner), + Addr::unchecked(&self.core), gauge_contract, &ExecuteMsg::CreateGauge(option), &[], @@ -639,17 +641,18 @@ impl Suite { to_distribute: (u128, &str), max_available_percentage: impl Into>, reset_epoch: impl Into>, + epoch_limit: impl Into>, ) -> AnyResult { let gauge_adapter = self.app.instantiate_contract( self.gauge_adapter_code_id, - Addr::unchecked(&self.owner), + Addr::unchecked(&self.core), &AdapterInstantiateMsg { options: options.iter().map(|&s| s.into()).collect(), to_distribute: coin(to_distribute.0, to_distribute.1), }, &[], "gauge adapter", - None, + Some(self.core.to_string()), )?; Ok(GaugeConfig { @@ -660,6 +663,7 @@ impl Suite { max_options_selected: 10, max_available_percentage: max_available_percentage.into(), reset_epoch: reset_epoch.into(), + total_epochs: epoch_limit.into(), }) } @@ -669,6 +673,7 @@ impl Suite { sender: &str, gauge_contract: Addr, gauge_id: u64, + epoch_limit: impl Into>, epoch_size: impl Into>, min_percent_selected: Option, max_options_selected: impl Into>, @@ -683,6 +688,7 @@ impl Suite { min_percent_selected, max_options_selected: max_options_selected.into(), max_available_percentage: max_available_percentage.into(), + epoch_limit: epoch_limit.into(), }, &[], ) diff --git a/contracts/gauges/gauge/src/multitest/tally.rs b/contracts/gauges/gauge/src/multitest/tally.rs index 9de07ecb4..ab00ad1c7 100644 --- a/contracts/gauges/gauge/src/multitest/tally.rs +++ b/contracts/gauges/gauge/src/multitest/tally.rs @@ -47,6 +47,7 @@ fn multiple_options_one_gauge() { reward_to_distribute, None, None, + None, ) .unwrap(); let gauge_id = 0; @@ -152,6 +153,7 @@ fn multiple_options_two_gauges() { reward_to_distribute, None, None, + None, ) .unwrap(); suite @@ -179,6 +181,7 @@ fn multiple_options_two_gauges() { reward_to_distribute, None, None, + None, ) .unwrap(); let second_gauge_id = 1; @@ -282,6 +285,7 @@ fn not_voted_options_are_not_selected() { reward_to_distribute, None, None, + None, ) .unwrap(); let first_gauge_id = 0; diff --git a/contracts/gauges/gauge/src/multitest/voting.rs b/contracts/gauges/gauge/src/multitest/voting.rs index 962daa1ac..c43b979d5 100644 --- a/contracts/gauges/gauge/src/multitest/voting.rs +++ b/contracts/gauges/gauge/src/multitest/voting.rs @@ -48,6 +48,7 @@ fn add_option() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -100,7 +101,6 @@ fn add_option() { #[test] fn remove_option() { - let owner = "owner"; let voter1 = "voter1"; let voter2 = "voter2"; let mut suite = SuiteBuilder::new() @@ -111,7 +111,7 @@ fn remove_option() { suite .propose_update_proposal_module(voter1.to_string(), None) .unwrap(); - + let dao = suite.core.clone(); suite.next_block(); let proposal = suite.list_proposals().unwrap()[0]; suite @@ -135,6 +135,7 @@ fn remove_option() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -175,7 +176,7 @@ fn remove_option() { // owner can remove an option that has been added already suite - .remove_option(&gauge_contract, owner, gauge_id, "addedoption1") + .remove_option(&gauge_contract, dao.clone(), gauge_id, "addedoption1") .unwrap(); // Anyone else cannot remove options @@ -183,7 +184,10 @@ fn remove_option() { .remove_option(&gauge_contract, voter1, gauge_id, "addedoption2") .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + assert_eq!( + ContractError::Ownership(cw_ownable::OwnershipError::NotOwner), + err.downcast().unwrap() + ); let options = suite.query_list_options(&gauge_contract, gauge_id).unwrap(); // one has been removed @@ -200,7 +204,7 @@ fn remove_option() { // owner can remove an option that is no longer valid suite - .remove_option(&gauge_contract, owner, gauge_id, "addedoption2") + .remove_option(&gauge_contract, dao, gauge_id, "addedoption2") .unwrap(); // Both options are now removed @@ -282,6 +286,7 @@ fn vote_for_option() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -423,6 +428,7 @@ fn remove_vote() { (1000, "ujuno"), None, None, + None, ) .unwrap(); @@ -493,7 +499,13 @@ fn votes_stays_the_same_after_execution() { suite.next_block(); let gauge_config = suite - .instantiate_adapter_and_return_config(&[voter1, voter2], reward_to_distribute, None, None) + .instantiate_adapter_and_return_config( + &[voter1, voter2], + reward_to_distribute, + None, + None, + None, + ) .unwrap(); suite .propose_update_proposal_module(voter1.to_string(), vec![gauge_config]) @@ -617,6 +629,7 @@ fn vote_for_max_capped_option() { (1000, "ujuno"), Some(Decimal::percent(10)), None, + None, ) .unwrap(); @@ -732,6 +745,7 @@ fn membership_voting_power_change() { (1000, "ujuno"), None, None, + None, ) .unwrap(); let gauge_id = 0; // first created gauge @@ -913,6 +927,7 @@ fn token_staking_voting_power_change() { (1000, "ujuno"), None, None, + None, ) .unwrap(); let gauge_id = 0; // first created gauge @@ -1111,6 +1126,7 @@ fn nft_staking_voting_power_change() { (1000, "ujuno"), None, None, + None, ) .unwrap(); let gauge_id = 0; // first created gauge diff --git a/contracts/gauges/gauge/src/state.rs b/contracts/gauges/gauge/src/state.rs index a48c4fd20..ed4e12783 100644 --- a/contracts/gauges/gauge/src/state.rs +++ b/contracts/gauges/gauge/src/state.rs @@ -37,8 +37,6 @@ pub struct Config { pub voting_powers: Addr, /// Addres that will call voting power change hooks (often same as voting power contract) pub hook_caller: Addr, - /// Address that can add new gauges or stop them - pub owner: Addr, /// Address of DAO core module resposible for instantiation and execution of messages pub dao_core: Addr, } @@ -51,6 +49,10 @@ pub struct Gauge { pub adapter: Addr, /// Frequency (in seconds) the gauge executes messages, typically something like 7*86400 pub epoch: u64, + /// Epoch count. + pub count: Option, + /// total possible count for a gauge to run. will automatially disable itself when reaching this epoch count. + pub total_epoch: Option, /// Minimum percentage of votes needed by a given option to be in the selected set pub min_percent_selected: Option, /// Maximum number of Options to make the selected set. Needed even with @@ -86,6 +88,19 @@ impl Gauge { .map(|r| r.last == Some(r.next)) .unwrap_or_default() } + pub fn will_reach_epoch_limit(&self) -> bool { + if let Some(total) = self.total_epoch { + total == self.count.unwrap_or_default() + } else { + false + } + } + pub fn increment_gauge_count(&self) -> StdResult> { + Ok(self.count.map_or(Some(0), |o| Some(o + 1))) + } + pub fn gauge_epoch(&self) -> StdResult { + Ok(self.count.map_or(Some(0), |o| Some(o)).unwrap_or_default()) + } } #[cw_serde] @@ -102,7 +117,7 @@ pub struct WeightedVotes { } impl WeightedVotes { - /// Returns `true` if the vote is + /// Returns `true` if the vote is expired pub fn is_expired(&self, gauge: &Gauge) -> bool { // check if the vote is older than the last reset match &gauge.reset { @@ -451,6 +466,8 @@ mod tests { next_epoch: env.block.time.seconds(), last_executed_set: None, reset: None, + count: Some(0), + total_epoch: None, }, ) .unwrap(); @@ -578,6 +595,8 @@ mod tests { next_epoch: env.block.time.seconds(), last_executed_set: None, reset: None, + count: Some(0), + total_epoch: None, }, ) .unwrap(); From 1329e18ef899bf11a4c59a449811aa4ce2fbd038 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 09:50:54 -0400 Subject: [PATCH 02/15] clippy --- contracts/gauges/gauge/src/contract.rs | 3 ++- contracts/gauges/gauge/src/multitest/gauge.rs | 12 ++++++------ contracts/gauges/gauge/src/state.rs | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/gauges/gauge/src/contract.rs b/contracts/gauges/gauge/src/contract.rs index b86990698..05f9420f2 100644 --- a/contracts/gauges/gauge/src/contract.rs +++ b/contracts/gauges/gauge/src/contract.rs @@ -446,6 +446,7 @@ mod execute { }) } + #[allow(clippy::too_many_arguments)] pub fn update_gauge( deps: DepsMut, sender: Addr, @@ -759,7 +760,7 @@ mod execute { } pub fn execute(deps: DepsMut, env: Env, gauge_id: u64) -> Result { - let mut gauge = GAUGES.load(deps.storage, gauge_id.clone())?; + let mut gauge = GAUGES.load(deps.storage, gauge_id)?; let mut msgs = vec![]; if gauge.is_stopped { diff --git a/contracts/gauges/gauge/src/multitest/gauge.rs b/contracts/gauges/gauge/src/multitest/gauge.rs index 57aabeffa..7c5dfb518 100644 --- a/contracts/gauges/gauge/src/multitest/gauge.rs +++ b/contracts/gauges/gauge/src/multitest/gauge.rs @@ -658,7 +658,7 @@ fn update_gauge() { let new_max_available_percentage = Some(Decimal::percent(5)); suite .update_gauge( - &dao.as_str(), + dao.as_str(), gauge_contract.clone(), 0, new_epoch, @@ -705,7 +705,7 @@ fn update_gauge() { // clean setting of min_percent_selected on second gauge suite .update_gauge( - &dao.as_str(), + dao.as_str(), gauge_contract.clone(), 1, None, @@ -769,7 +769,7 @@ fn update_gauge() { let err = suite .update_gauge( - &dao.as_str(), + dao.as_str(), gauge_contract.clone(), 0, 50, @@ -783,7 +783,7 @@ fn update_gauge() { let err = suite .update_gauge( - &dao.as_str(), + dao.as_str(), gauge_contract.clone(), 0, new_epoch, @@ -800,7 +800,7 @@ fn update_gauge() { let err = suite .update_gauge( - &dao.as_str(), + dao.as_str(), gauge_contract.clone(), 0, new_epoch, @@ -817,7 +817,7 @@ fn update_gauge() { let err = suite .update_gauge( - &dao.as_str(), + dao.as_str(), gauge_contract, 1, None, diff --git a/contracts/gauges/gauge/src/state.rs b/contracts/gauges/gauge/src/state.rs index ed4e12783..4ea45b8f7 100644 --- a/contracts/gauges/gauge/src/state.rs +++ b/contracts/gauges/gauge/src/state.rs @@ -99,7 +99,7 @@ impl Gauge { Ok(self.count.map_or(Some(0), |o| Some(o + 1))) } pub fn gauge_epoch(&self) -> StdResult { - Ok(self.count.map_or(Some(0), |o| Some(o)).unwrap_or_default()) + Ok(self.count.map_or(Some(0), Some).unwrap_or_default()) } } From a27f8800367491141b5b6cace23b3963d6f11d9b Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 09:52:30 -0400 Subject: [PATCH 03/15] fmt --- contracts/gauges/gauge-adapter/src/multitest/options.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/gauges/gauge-adapter/src/multitest/options.rs b/contracts/gauges/gauge-adapter/src/multitest/options.rs index 21c24277a..d1c57e6cf 100644 --- a/contracts/gauges/gauge-adapter/src/multitest/options.rs +++ b/contracts/gauges/gauge-adapter/src/multitest/options.rs @@ -47,4 +47,4 @@ fn option_queries() { let option = suite.query_check_option("newton".to_owned()).unwrap(); assert!(!option); -} \ No newline at end of file +} From feedcf82f826e68704862a6d7e529aceb758087d Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 09:57:23 -0400 Subject: [PATCH 04/15] assert gauge reset --- contracts/gauges/gauge/src/multitest/reset.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/gauges/gauge/src/multitest/reset.rs b/contracts/gauges/gauge/src/multitest/reset.rs index 32d59a61a..76877845f 100644 --- a/contracts/gauges/gauge/src/multitest/reset.rs +++ b/contracts/gauges/gauge/src/multitest/reset.rs @@ -482,14 +482,18 @@ fn test_epoch_limit() -> anyhow::Result<()> { ) .unwrap(); - // before advancing specified epoch tally won't get sampled + // advance to 1st epoch time suite.advance_time(EPOCH); suite .execute_options(&gauge_contract, voter1, gauge_id) .unwrap(); + // advance to 2nd epoch time suite.advance_time(EPOCH); suite .execute_options(&gauge_contract, voter1, gauge_id) .unwrap(); + // confirm gauge is now turned off + let res = suite.query_gauge(gauge_contract, gauge_id)?; + assert_eq!(res.is_stopped, true); Ok(()) } From ef0759a17c2da2c9b75a0eae50de77c512698b9c Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 10:25:20 -0400 Subject: [PATCH 05/15] bump integration ci toolchain --- .github/workflows/integration_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 42af0ab81..9210813c0 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -21,7 +21,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2023-02-02 + toolchain: nightly-2024-01-08 target: wasm32-unknown-unknown override: true From 75c369b3259dcd721b451123dd6209c65c31a3ae Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 10:26:29 -0400 Subject: [PATCH 06/15] clippy --- contracts/gauges/gauge/src/multitest/reset.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/gauges/gauge/src/multitest/reset.rs b/contracts/gauges/gauge/src/multitest/reset.rs index 76877845f..53380ab07 100644 --- a/contracts/gauges/gauge/src/multitest/reset.rs +++ b/contracts/gauges/gauge/src/multitest/reset.rs @@ -494,6 +494,6 @@ fn test_epoch_limit() -> anyhow::Result<()> { .unwrap(); // confirm gauge is now turned off let res = suite.query_gauge(gauge_contract, gauge_id)?; - assert_eq!(res.is_stopped, true); + assert!(res.is_stopped); Ok(()) } From d415c5bc4a832d0b539ec68b5a35cf89ca17e23e Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 15:02:23 -0400 Subject: [PATCH 07/15] cw_ownable logic, more detailed docs --- .../gauges/gauge-adapter/src/contract.rs | 1 + contracts/gauges/gauge-adapter/src/error.rs | 1 + contracts/gauges/gauge-adapter/src/msg.rs | 3 ++- contracts/gauges/gauge/src/contract.rs | 24 +++++++++++++++++++ contracts/gauges/gauge/src/error.rs | 12 +++++++--- contracts/gauges/gauge/src/msg.rs | 5 +++- contracts/gauges/gauge/src/state.rs | 6 ++++- 7 files changed, 46 insertions(+), 6 deletions(-) diff --git a/contracts/gauges/gauge-adapter/src/contract.rs b/contracts/gauges/gauge-adapter/src/contract.rs index 1e0a7cdca..b37f944fb 100644 --- a/contracts/gauges/gauge-adapter/src/contract.rs +++ b/contracts/gauges/gauge-adapter/src/contract.rs @@ -217,6 +217,7 @@ pub fn query(deps: Deps, _env: Env, msg: AdapterQueryMsg) -> StdResult { to_json_binary(&query::submission(deps, address)?) } AdapterQueryMsg::AllSubmissions {} => to_json_binary(&query::all_submissions(deps)?), + AdapterQueryMsg::Ownership {} => to_json_binary(&cw_ownable::get_ownership(deps.storage)?), } } diff --git a/contracts/gauges/gauge-adapter/src/error.rs b/contracts/gauges/gauge-adapter/src/error.rs index 8eb5101f6..2ad89d63c 100644 --- a/contracts/gauges/gauge-adapter/src/error.rs +++ b/contracts/gauges/gauge-adapter/src/error.rs @@ -31,6 +31,7 @@ pub enum ContractError { #[error("No deposit was required, therefore no deposit can be returned")] NoDepositToRefund {}, + #[error("Deposit required, cannot create submission.")] DepositRequired {}, } diff --git a/contracts/gauges/gauge-adapter/src/msg.rs b/contracts/gauges/gauge-adapter/src/msg.rs index e38eec3fc..90b2c6b0f 100644 --- a/contracts/gauges/gauge-adapter/src/msg.rs +++ b/contracts/gauges/gauge-adapter/src/msg.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, CosmosMsg, Decimal, Uint128}; use cw20::Cw20ReceiveMsg; use cw_denom::UncheckedDenom; -use cw_ownable::cw_ownable_execute; +use cw_ownable::{cw_ownable_execute, cw_ownable_query}; #[cw_serde] pub struct InstantiateMsg { @@ -48,6 +48,7 @@ pub enum MigrateMsg {} // Queries copied from gauge-orchestrator for now (we could use a common crate for this). /// Queries the gauge requires from the adapter contract in order to function. +#[cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] pub enum AdapterQueryMsg { diff --git a/contracts/gauges/gauge/src/contract.rs b/contracts/gauges/gauge/src/contract.rs index 05f9420f2..a55b7aaa7 100644 --- a/contracts/gauges/gauge/src/contract.rs +++ b/contracts/gauges/gauge/src/contract.rs @@ -10,6 +10,7 @@ use dao_interface::{ msg::ExecuteMsg as DaoExecuteMsg, voting::{Query as DaoQuery, VotingPowerAtHeightResponse}, }; +use execute::execute_update_owner; use crate::msg::{ AdapterQueryMsg, AllOptionsResponse, CheckOptionResponse, ExecuteMsg, GaugeConfig, @@ -100,12 +101,14 @@ pub fn execute( execute::place_votes(deps, env, info.sender, gauge, votes) } ExecuteMsg::Execute { gauge } => execute::execute(deps, env, gauge), + ExecuteMsg::UpdateOwnership(action) => execute_update_owner(deps, info, env, action), } } mod execute { use cosmwasm_std::CosmosMsg; use cw4::MemberDiff; + use cw_utils::nonpayable; use dao_hooks::{nft_stake::NftStakeChangedHookMsg, stake::StakeChangedHookMsg}; use super::*; @@ -460,14 +463,17 @@ mod execute { cw_ownable::assert_owner(deps.storage, &sender)?; let mut gauge = GAUGES.load(deps.storage, gauge_id)?; + // updated epoch size must be greater than 60 seconds if let Some(epoch_size) = epoch_size { ensure!(epoch_size > 60u64, ContractError::EpochSizeTooShort {}); gauge.epoch = epoch_size; } + // updated epoch limit count must not already have passed if let Some(epoch_limit) = epoch_limit { let e = gauge.gauge_epoch()?; ensure!(e < epoch_limit, ContractError::EpochLimitTooShort {}) } + // min_perfect_selected percent must be less than 100%. None if 0. if let Some(min_percent_selected) = min_percent_selected { if min_percent_selected.is_zero() { gauge.min_percent_selected = None @@ -479,6 +485,7 @@ mod execute { gauge.min_percent_selected = Some(min_percent_selected) }; } + // max_options_selected must be at least 1 if let Some(max_options_selected) = max_options_selected { ensure!( max_options_selected > 0, @@ -486,6 +493,7 @@ mod execute { ); gauge.max_options_selected = max_options_selected; } + // max_available_percentage must be less than 100%. None if 0. if let Some(max_available_percentage) = max_available_percentage { if max_available_percentage.is_zero() { gauge.max_available_percentage = None @@ -831,6 +839,21 @@ mod execute { .add_attribute("action", "execute_tally") .add_message(execute_msg)) } + + pub fn execute_update_owner( + deps: DepsMut, + info: MessageInfo, + env: Env, + action: cw_ownable::Action, + ) -> Result { + nonpayable(&info)?; + + // Update the current contract owner. Note, this is a two step process, the + // new owner must accept this ownership transfer. First the owner specifies + // the new owner, then the new owner must accept. + let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; + Ok(Response::new().add_attributes(ownership.into_attributes())) + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -868,6 +891,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::LastExecutedSet { gauge } => { Ok(to_json_binary(&query::last_executed_set(deps, gauge)?)?) } + QueryMsg::Ownership {} => to_json_binary(&cw_ownable::get_ownership(deps.storage)?), } } diff --git a/contracts/gauges/gauge/src/error.rs b/contracts/gauges/gauge/src/error.rs index beacdd402..37334366c 100644 --- a/contracts/gauges/gauge/src/error.rs +++ b/contracts/gauges/gauge/src/error.rs @@ -1,15 +1,20 @@ use cosmwasm_std::{Decimal, StdError}; +use cw_utils::PaymentError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), + #[error(transparent)] + Ownership(#[from] cw_ownable::OwnershipError), + + #[error(transparent)] + Payment(#[from] PaymentError), + #[error("Unauthorized")] Unauthorized {}, - #[error("{0}")] - Ownership(#[from] cw_ownable::OwnershipError), #[error("Gauge with ID {0} does not exists")] GaugeMissing(u64), @@ -53,6 +58,7 @@ pub enum ContractError { #[error("Epoch size must be bigger then 60 seconds")] EpochSizeTooShort {}, + #[error("Epoch limit must be bigger then current epoch")] EpochLimitTooShort {}, diff --git a/contracts/gauges/gauge/src/msg.rs b/contracts/gauges/gauge/src/msg.rs index df7874d54..498570935 100644 --- a/contracts/gauges/gauge/src/msg.rs +++ b/contracts/gauges/gauge/src/msg.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{CosmosMsg, Decimal, Uint128}; use cw4::MemberChangedHookMsg; +use cw_ownable::{cw_ownable_execute, cw_ownable_query}; use dao_hooks::{nft_stake::NftStakeChangedHookMsg, stake::StakeChangedHookMsg}; use crate::state::{Reset, Vote}; @@ -39,10 +40,11 @@ pub struct GaugeConfig { pub max_available_percentage: Option, /// If set, the gauge can be reset periodically, every `reset_epoch` seconds. pub reset_epoch: Option, - /// if set, the gauge will disable itself after this many epochs + /// If set, the gauge will disable itself after this many epochs pub total_epochs: Option, } +#[cw_ownable_execute] #[cw_serde] pub enum ExecuteMsg { /// Updates gauge voting power in Token DAOs when a user stakes or unstakes @@ -101,6 +103,7 @@ pub struct CreateGaugeReply { } /// Queries the gauge exposes +#[cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { diff --git a/contracts/gauges/gauge/src/state.rs b/contracts/gauges/gauge/src/state.rs index 4ea45b8f7..de5a3a57c 100644 --- a/contracts/gauges/gauge/src/state.rs +++ b/contracts/gauges/gauge/src/state.rs @@ -51,7 +51,8 @@ pub struct Gauge { pub epoch: u64, /// Epoch count. pub count: Option, - /// total possible count for a gauge to run. will automatially disable itself when reaching this epoch count. + /// Total possible count for a gauge to run. Will automatially disable itself when reaching this epoch count. + /// If `None`, gauge epoch cycles may run indefinetly. pub total_epoch: Option, /// Minimum percentage of votes needed by a given option to be in the selected set pub min_percent_selected: Option, @@ -88,6 +89,7 @@ impl Gauge { .map(|r| r.last == Some(r.next)) .unwrap_or_default() } + // Helper checks if the epoch count equals the total # of epochs pub fn will_reach_epoch_limit(&self) -> bool { if let Some(total) = self.total_epoch { total == self.count.unwrap_or_default() @@ -95,9 +97,11 @@ impl Gauge { false } } + // Increments the contracts global gauge count pub fn increment_gauge_count(&self) -> StdResult> { Ok(self.count.map_or(Some(0), |o| Some(o + 1))) } + // returns the current epoch of a single gauge pub fn gauge_epoch(&self) -> StdResult { Ok(self.count.map_or(Some(0), Some).unwrap_or_default()) } From 27f10d0a6004708793c77fa0e1f5e25e5222485d Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 15:05:15 -0400 Subject: [PATCH 08/15] fmt --- contracts/gauges/gauge-adapter/src/error.rs | 2 +- contracts/gauges/gauge/src/error.rs | 2 +- contracts/gauges/gauge/src/state.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/gauges/gauge-adapter/src/error.rs b/contracts/gauges/gauge-adapter/src/error.rs index 2ad89d63c..b696a40dc 100644 --- a/contracts/gauges/gauge-adapter/src/error.rs +++ b/contracts/gauges/gauge-adapter/src/error.rs @@ -31,7 +31,7 @@ pub enum ContractError { #[error("No deposit was required, therefore no deposit can be returned")] NoDepositToRefund {}, - + #[error("Deposit required, cannot create submission.")] DepositRequired {}, } diff --git a/contracts/gauges/gauge/src/error.rs b/contracts/gauges/gauge/src/error.rs index 37334366c..e65612ad0 100644 --- a/contracts/gauges/gauge/src/error.rs +++ b/contracts/gauges/gauge/src/error.rs @@ -9,7 +9,7 @@ pub enum ContractError { #[error(transparent)] Ownership(#[from] cw_ownable::OwnershipError), - + #[error(transparent)] Payment(#[from] PaymentError), diff --git a/contracts/gauges/gauge/src/state.rs b/contracts/gauges/gauge/src/state.rs index de5a3a57c..de427e5fa 100644 --- a/contracts/gauges/gauge/src/state.rs +++ b/contracts/gauges/gauge/src/state.rs @@ -52,7 +52,7 @@ pub struct Gauge { /// Epoch count. pub count: Option, /// Total possible count for a gauge to run. Will automatially disable itself when reaching this epoch count. - /// If `None`, gauge epoch cycles may run indefinetly. + /// If `None`, gauge epoch cycles may run indefinetly. pub total_epoch: Option, /// Minimum percentage of votes needed by a given option to be in the selected set pub min_percent_selected: Option, From c7850d448314a9cc78e177b9277be72732d44c77 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 15:15:23 -0400 Subject: [PATCH 09/15] total_epochs comment --- contracts/gauges/gauge/README.md | 37 +++++++++++++++++++------------ contracts/gauges/gauge/src/msg.rs | 2 +- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/contracts/gauges/gauge/README.md b/contracts/gauges/gauge/README.md index ef87daf0c..096779309 100644 --- a/contracts/gauges/gauge/README.md +++ b/contracts/gauges/gauge/README.md @@ -5,17 +5,15 @@ when we need to select a weighted group out of a larger group of options. ## Orchestrator -To work properly, the gauge must be informed every time that the voting power of a member changes. -It does so by listening to "update hooks" on the underlying staking contract and if an address's -voting power changes, updating their vote weight in the gauge, and the tally for the option they -had voted for (if any). +### Why is there an Gauge Orchestrator +To work properly, **a gauge must be informed every time that the voting power of a member changes.** It does so by listening for a "update hooks" msg from the underlying staking contract and if an address's voting power changes, updating their vote weight in the gauge, and the tally for the option they had voted for (if any) is done by the gauge. +#### Staking Hooks Gas Cost Every contract call has some overhead, which is silently added to the basic staking action. - If we have 5 gauges in a DAO, we would likely have a minimum of 5 x 65k or 325k gas per staking action, just to update gauges. This is a lot of overhead, and we want to avoid it. -To do so, we make one "Gauge Orchestrator", which can manage many different gauges. They all have the -same voting logic and rules to update when the voting power changes. The Orchestrator is the only +#### Gauge Orchestrator & Adapters +To do so, we make use of one "Gauge Orchestrator", which will manage many different "Gauge Adapters". Each gauge has its own gauge-adapter, but will use the same voting logic and rules to update when the voting power changes and ever. The Orchestrator is the only contract that must be called by the staking contract, and doing a few writes for each gauge is a lot cheaper gas-wise than calling a separate contract. @@ -31,17 +29,26 @@ that before adding to the new one. When an "update hook" is triggered, it update Every epoch (eg 1/week), the current tally of the gauge is sampled, and some cut-off applies (top 20, min 0.5% of votes, etc). The resulting set is the "selected set" and the options along with their relative vote counts (normalized to 1.0 = total votes within this set) is used to initiate some -action (eg. distribute reward tokens). +action (eg. distribute reward tokens). A Gauge may have a maximum number of epochs set to operator for until it no loger will operate. + -## Extensibility +Gauge Config | Description | Type | +--- | --- | --- | +Title | Title of gauge | String | +Adapter | Contract address of gauge adapter | String | +Epoch | Seconds between gauge processing messages | u64 | +Minimum % Selected | Optional, minimum percentages of votes needed for an option to be in the selected set| Decimal | +Max Options Selected | Maximum options able to make the selected set| u64 | +Max Available % | Optional,maximim % Threshold for each options| Decimal | +Reset Epoch | Optional, seconds between gauge being reset | u64| +Total Epochs | Optional, number of times gauge will run | u64 | -We will be using one Orchestrator for many different gauges that update many different contracts. -To make it more extensible, we define option as an arbitrary string that makes sense to that contract. +## Gauge Adapter Requirements +We will be using one Orchestrator for many different gauges, making use of many different `GaugeAdapters`. -We also store the integration logic in an external contract, called a `GaugeAdapter` that must provide -3 queries to the Orchestrator: +A `GaugeAdapter` must provide the following 3 queries in order to be supported with the Gauge Orchestrator: -* Provide set of all options: maybe expensive, iterate over all and return them. This is used for initialization. +* A set of all options: maybe expensive, iterate over all and return them. This is used for initialization. * Check an option: Allow anyone to propose one, and this confirms if it is valid (eg is this a valid address of a registered AMM pool?) * Create update messages: Accepts "selected set" as argument, returns `Vec` to be executed by the @@ -58,6 +65,8 @@ and instantiates a properly configured Adapter. Then, it votes to create a new Gauge that uses this adapter. Upon creating the gauge, it will query the adapter for the current set of options to initialize state. +The voting module of that DAO must be registered to reflect voting weight changes in the DAO. + After one epoch has passed, anyone can trigger `Execute` on this gauge ID, and the Orchestrator will apply the logic to determine the "selected set". It will then query the adapter for the messages needed to convert that selection into the appropriate action, and it will send those to the diff --git a/contracts/gauges/gauge/src/msg.rs b/contracts/gauges/gauge/src/msg.rs index 498570935..30df54b8c 100644 --- a/contracts/gauges/gauge/src/msg.rs +++ b/contracts/gauges/gauge/src/msg.rs @@ -40,7 +40,7 @@ pub struct GaugeConfig { pub max_available_percentage: Option, /// If set, the gauge can be reset periodically, every `reset_epoch` seconds. pub reset_epoch: Option, - /// If set, the gauge will disable itself after this many epochs + /// If set, the gauge will disable itself after this many epochs. This count will not be reset if `reset_epoch` is set. pub total_epochs: Option, } From a5f3b5392cc8b6aad32edfeadb405a340c33597c Mon Sep 17 00:00:00 2001 From: Hard-Nett <123711748+hard-nett@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:25:20 +0000 Subject: [PATCH 10/15] Update contracts/gauges/gauge-adapter/src/msg.rs Co-authored-by: Jake Hartnell --- contracts/gauges/gauge-adapter/src/msg.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/gauges/gauge-adapter/src/msg.rs b/contracts/gauges/gauge-adapter/src/msg.rs index 90b2c6b0f..5e91840b0 100644 --- a/contracts/gauges/gauge-adapter/src/msg.rs +++ b/contracts/gauges/gauge-adapter/src/msg.rs @@ -61,7 +61,8 @@ pub enum AdapterQueryMsg { /// Checks if a provided option is included in the available options. Returns a boolean. #[returns(CheckOptionResponse)] CheckOption { option: String }, - /// Returns the messages determined by the current voting results for options.Used by the gauge orchestrator to pass messages for DAO to execute. + /// Returns the messages determined by the current voting results for options. + /// Used by the gauge orchestrator to pass messages for DAO to execute. #[returns(SampleGaugeMsgsResponse)] SampleGaugeMsgs { /// Option along with weight. From a2587a1a9ad7357a4ffb9410d43487465d8d4f62 Mon Sep 17 00:00:00 2001 From: Hard-Nett <123711748+hard-nett@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:25:28 +0000 Subject: [PATCH 11/15] Update contracts/gauges/gauge/src/contract.rs Co-authored-by: Jake Hartnell --- contracts/gauges/gauge/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/gauges/gauge/src/contract.rs b/contracts/gauges/gauge/src/contract.rs index a55b7aaa7..4013bbc84 100644 --- a/contracts/gauges/gauge/src/contract.rs +++ b/contracts/gauges/gauge/src/contract.rs @@ -473,7 +473,7 @@ mod execute { let e = gauge.gauge_epoch()?; ensure!(e < epoch_limit, ContractError::EpochLimitTooShort {}) } - // min_perfect_selected percent must be less than 100%. None if 0. + // min_percent_selected percent must be less than 100%. None if 0. if let Some(min_percent_selected) = min_percent_selected { if min_percent_selected.is_zero() { gauge.min_percent_selected = None From f347ed8804e18ffe23e20beb878b0ee2f85ea665 Mon Sep 17 00:00:00 2001 From: Hard-Nett <123711748+hard-nett@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:26:16 +0000 Subject: [PATCH 12/15] Update contracts/gauges/gauge/src/state.rs Co-authored-by: Jake Hartnell --- contracts/gauges/gauge/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/gauges/gauge/src/state.rs b/contracts/gauges/gauge/src/state.rs index de427e5fa..352c1a571 100644 --- a/contracts/gauges/gauge/src/state.rs +++ b/contracts/gauges/gauge/src/state.rs @@ -89,7 +89,7 @@ impl Gauge { .map(|r| r.last == Some(r.next)) .unwrap_or_default() } - // Helper checks if the epoch count equals the total # of epochs + /// Helper checks if the epoch count equals the total # of epochs pub fn will_reach_epoch_limit(&self) -> bool { if let Some(total) = self.total_epoch { total == self.count.unwrap_or_default() From ba236de2c9c71dc3d2cef526457d0e4994aae1cb Mon Sep 17 00:00:00 2001 From: Hard-Nett <123711748+hard-nett@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:26:22 +0000 Subject: [PATCH 13/15] Update contracts/gauges/gauge/src/state.rs Co-authored-by: Jake Hartnell --- contracts/gauges/gauge/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/gauges/gauge/src/state.rs b/contracts/gauges/gauge/src/state.rs index 352c1a571..5ecef7d2f 100644 --- a/contracts/gauges/gauge/src/state.rs +++ b/contracts/gauges/gauge/src/state.rs @@ -101,7 +101,7 @@ impl Gauge { pub fn increment_gauge_count(&self) -> StdResult> { Ok(self.count.map_or(Some(0), |o| Some(o + 1))) } - // returns the current epoch of a single gauge + /// returns the current epoch of a single gauge pub fn gauge_epoch(&self) -> StdResult { Ok(self.count.map_or(Some(0), Some).unwrap_or_default()) } From 429d2b010488adf113a5089869bae67360e1685e Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 15 Aug 2024 17:44:23 -0400 Subject: [PATCH 14/15] total_epoch & reset_epoch test --- contracts/gauges/gauge/src/multitest/reset.rs | 108 +++++++++++++++++- contracts/gauges/gauge/src/multitest/suite.rs | 4 +- 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/contracts/gauges/gauge/src/multitest/reset.rs b/contracts/gauges/gauge/src/multitest/reset.rs index 53380ab07..6cebc5c60 100644 --- a/contracts/gauges/gauge/src/multitest/reset.rs +++ b/contracts/gauges/gauge/src/multitest/reset.rs @@ -431,16 +431,15 @@ fn test_epoch_limit() -> anyhow::Result<()> { .with_core_balance((4000, "ujuno")) .build(); suite.next_block(); - let mut gauge_config = suite + let gauge_config = suite .instantiate_adapter_and_return_config( &[voter1, voter2], reward_to_distribute, None, - RESET_EPOCH, None, + Some(2), ) .unwrap(); - gauge_config.total_epochs = Some(2); suite .propose_update_proposal_module(voter1.to_string(), vec![gauge_config]) @@ -497,3 +496,106 @@ fn test_epoch_limit() -> anyhow::Result<()> { assert!(res.is_stopped); Ok(()) } + +#[test] +fn test_epoch_limit_and_reset_epoch() -> anyhow::Result<()> { + let voter1 = "voter1"; + let voter2 = "voter2"; + let reward_to_distribute = (2000, "ujuno"); + let mut suite = SuiteBuilder::new() + .with_voting_members(&[(voter1, 100), (voter2, 100)]) + .with_core_balance((4000, "ujuno")) + .build(); + suite.next_block(); + let gauge_config = suite + .instantiate_adapter_and_return_config( + &[voter1, voter2], + reward_to_distribute, + None, + RESET_EPOCH, + Some(4), + ) + .unwrap(); + + suite + .propose_update_proposal_module(voter1.to_string(), vec![gauge_config]) + .unwrap(); + + suite.next_block(); + let proposal = suite.list_proposals().unwrap()[0]; + suite + .place_vote_single(voter1, proposal, Vote::Yes) + .unwrap(); + suite + .place_vote_single(voter2, proposal, Vote::Yes) + .unwrap(); + + suite.next_block(); + suite + .execute_single_proposal(voter1.to_string(), proposal) + .unwrap(); + let proposal_modules = suite.query_proposal_modules().unwrap(); + let gauge_contract = proposal_modules[1].clone(); + + let gauge_id = 0; + + // vote for the gauge options + suite + .place_vote( + &gauge_contract, + voter1.to_owned(), + gauge_id, + Some(voter1.to_owned()), + ) + .unwrap(); + suite + .place_vote( + &gauge_contract, + voter2.to_owned(), + gauge_id, + Some(voter2.to_owned()), + ) + .unwrap(); + + // advance to 1st epoch time + suite.advance_time(EPOCH); + suite + .execute_options(&gauge_contract, voter1, gauge_id) + .unwrap(); + // advance to 2nd epoch time + suite.advance_time(EPOCH); + suite + .execute_options(&gauge_contract, voter1, gauge_id) + .unwrap(); + + // move forward in time for epoch limit + suite.advance_time(RESET_EPOCH); + + // finish resetting + suite + .reset_gauge("someone", &gauge_contract, gauge_id, 10) + .unwrap(); + + // advance to 3rd epoch time + suite.advance_time(EPOCH); + suite + .execute_options(&gauge_contract, voter1, gauge_id) + .unwrap(); + // advance to 4th epoch time + suite.advance_time(EPOCH); + suite + .execute_options(&gauge_contract, voter1, gauge_id) + .unwrap(); + + // confirm gauge is now turned off + let res = suite.query_gauge(gauge_contract.clone(), gauge_id)?; + assert!(res.is_stopped); + + // advance to 5th epoch time. Error. + suite.advance_time(EPOCH); + suite + .execute_options(&gauge_contract, voter1, gauge_id) + .unwrap_err(); + + Ok(()) +} diff --git a/contracts/gauges/gauge/src/multitest/suite.rs b/contracts/gauges/gauge/src/multitest/suite.rs index 3440728fc..70e2125b0 100644 --- a/contracts/gauges/gauge/src/multitest/suite.rs +++ b/contracts/gauges/gauge/src/multitest/suite.rs @@ -641,7 +641,7 @@ impl Suite { to_distribute: (u128, &str), max_available_percentage: impl Into>, reset_epoch: impl Into>, - epoch_limit: impl Into>, + total_epochs: impl Into>, ) -> AnyResult { let gauge_adapter = self.app.instantiate_contract( self.gauge_adapter_code_id, @@ -663,7 +663,7 @@ impl Suite { max_options_selected: 10, max_available_percentage: max_available_percentage.into(), reset_epoch: reset_epoch.into(), - total_epochs: epoch_limit.into(), + total_epochs: total_epochs.into(), }) } From 9767d0c46ee60786c8d896e5ffe36e10a1ae797a Mon Sep 17 00:00:00 2001 From: Hard-Nett <123711748+hard-nett@users.noreply.github.com> Date: Mon, 11 Nov 2024 02:20:06 -0500 Subject: [PATCH 15/15] Update contracts/gauges/gauge/src/state.rs Co-authored-by: Jake Hartnell --- contracts/gauges/gauge/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/gauges/gauge/src/state.rs b/contracts/gauges/gauge/src/state.rs index 5ecef7d2f..4a8eb64c1 100644 --- a/contracts/gauges/gauge/src/state.rs +++ b/contracts/gauges/gauge/src/state.rs @@ -97,7 +97,7 @@ impl Gauge { false } } - // Increments the contracts global gauge count + /// Increments the contracts global gauge count pub fn increment_gauge_count(&self) -> StdResult> { Ok(self.count.map_or(Some(0), |o| Some(o + 1))) }