diff --git a/Cargo.lock b/Cargo.lock index dea17b838..d54f83fed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,7 +181,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core", + "cw-core 0.1.0", "cw-multi-test 0.13.4", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", @@ -213,8 +213,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core-interface", - "cw-core-macros", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.14.0", "cw-paginate", "cw-proposal-sudo", @@ -231,13 +231,47 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "468b8f2696f625c8e15b5468f9420c8eabfaf23cb4fd7e6c660fc7e0cc8d77b8" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-core-interface 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-core-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-paginate-storage", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", + "cw2 0.13.4", + "cw20 0.13.4", + "cw721", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-core-interface" version = "0.1.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-core-macros", + "cw-core-macros 0.1.0", + "cw2 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw-core-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c93e684945473777ebed2bcaf9f0af2291653f79d5c81774c6826350ba6d88de" +dependencies = [ + "cosmwasm-std", + "cw-core-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cw2 0.13.4", "schemars", "serde", @@ -255,6 +289,17 @@ dependencies = [ "syn", ] +[[package]] +name = "cw-core-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f20a77489d2dc8a1c12cb0b9671b6cbdca88f12fe65e1a4ee9899490f7669dcc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cw-multi-test" version = "0.13.4" @@ -334,8 +379,8 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-controllers", - "cw-core-interface", - "cw-core-macros", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", @@ -356,15 +401,27 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-paginate-storage" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b854833e07c557dee02d1b61a21bb0731743bb2e3bbdc3e446a0d8a38af40ec4" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.13.4", + "serde", +] + [[package]] name = "cw-proposal-multiple" version = "0.1.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-core", - "cw-core-interface", - "cw-core-macros", + "cw-core 0.1.0", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", @@ -377,29 +434,54 @@ dependencies = [ "cw4", "cw4-group", "cw4-voting", - "indexable-hooks", - "proposal-hooks", + "indexable-hooks 0.1.0", + "proposal-hooks 0.1.0", "rand", "schemars", "serde", "testing", "thiserror", - "vote-hooks", + "vote-hooks 0.1.0", "voting", ] [[package]] name = "cw-proposal-single" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6408483e1ac17a7e2b98ef6fa1379776964353bcbf501942d22ee1c1323117" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-core-interface 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-core-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", + "cw2 0.13.4", + "cw20 0.13.4", + "cw3", + "dao-voting", + "indexable-hooks 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proposal-hooks 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "schemars", + "serde", + "thiserror", + "vote-hooks 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cw-proposal-single" +version = "0.1.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core", - "cw-core-interface", - "cw-core-macros", + "cw-core 0.1.0", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", - "cw-native-staked-balance-voting", + "cw-proposal-single 0.1.0", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", "cw2 0.13.4", @@ -412,15 +494,14 @@ dependencies = [ "cw4", "cw4-group", "cw4-voting", - "cw721-base", - "cw721-stake", - "indexable-hooks", - "proposal-hooks", + "dao-voting", + "indexable-hooks 0.1.0", + "proposal-hooks 0.1.0", "schemars", "serde", "testing", "thiserror", - "vote-hooks", + "vote-hooks 0.1.0", "voting", ] @@ -431,8 +512,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core-interface", - "cw-core-macros", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", "cw-storage-plus 0.13.4", "cw2 0.13.4", @@ -582,8 +663,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core-interface", - "cw-core-macros", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", @@ -638,8 +719,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core-interface", - "cw-core-macros", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", @@ -700,8 +781,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core-interface", - "cw-core-macros", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", @@ -761,8 +842,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers", - "cw-core-interface", - "cw-core-macros", + "cw-core-interface 0.1.0", + "cw-core-macros 0.1.0", "cw-multi-test 0.13.4", "cw-paginate", "cw-storage-plus 0.13.4", @@ -777,6 +858,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "dao-voting" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442d770933e3b3ecab4cfb4d6e9d054082b007d35fda3cf0c3d3ddd1cfa91782" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "der" version = "0.5.1" @@ -955,6 +1048,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "indexable-hooks" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d70922e1e0e68d99ec1a24446c70756cc3e56deaddb505b1f4b43914522d809" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1037,7 +1143,19 @@ name = "proposal-hooks" version = "0.1.0" dependencies = [ "cosmwasm-std", - "indexable-hooks", + "indexable-hooks 0.1.0", + "schemars", + "serde", +] + +[[package]] +name = "proposal-hooks" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a2f15b848398bad689771b35313c7e7095e772d444e299dbdb54b906691f8a" +dependencies = [ + "cosmwasm-std", + "indexable-hooks 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", ] @@ -1049,21 +1167,21 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-core", + "cw-core 0.1.0", "cw-multi-test 0.13.4", - "cw-proposal-single", + "cw-proposal-single 0.1.1", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", "cw2 0.13.4", "cw20 0.13.4", "cw20-balance-voting", "cw20-base", - "indexable-hooks", - "proposal-hooks", + "indexable-hooks 0.1.0", + "proposal-hooks 0.1.0", "schemars", "serde", "thiserror", - "vote-hooks", + "vote-hooks 0.1.0", "voting", ] @@ -1351,8 +1469,8 @@ version = "0.1.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-core", - "cw-core-interface", + "cw-core 0.1.0", + "cw-core-interface 0.1.0", "cw-multi-test 0.13.4", "cw-utils 0.13.4", "cw2 0.13.4", @@ -1364,7 +1482,7 @@ dependencies = [ "cw4", "cw4-group", "cw4-voting", - "indexable-hooks", + "indexable-hooks 0.1.0", "rand", "voting", ] @@ -1424,7 +1542,19 @@ name = "vote-hooks" version = "0.1.0" dependencies = [ "cosmwasm-std", - "indexable-hooks", + "indexable-hooks 0.1.0", + "schemars", + "serde", +] + +[[package]] +name = "vote-hooks" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef617ad17edd195f8a3bce72498bfcc406a27cecfc23828f562fa91a3e2fb141" +dependencies = [ + "cosmwasm-std", + "indexable-hooks 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", ] @@ -1434,8 +1564,8 @@ name = "voting" version = "0.1.0" dependencies = [ "cosmwasm-std", - "cw-core", - "cw-core-interface", + "cw-core 0.1.0", + "cw-core-interface 0.1.0", "cw-storage-plus 0.12.1", "cw-utils 0.13.4", "cw20 0.12.1", diff --git a/contracts/cw-proposal-single/Cargo.toml b/contracts/cw-proposal-single/Cargo.toml index a0ddb4a81..5b1ad90fe 100644 --- a/contracts/cw-proposal-single/Cargo.toml +++ b/contracts/cw-proposal-single/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-proposal-single" -version = "0.1.0" +version = "0.1.1" authors = ["Zeke Medley "] edition = "2018" @@ -47,15 +47,16 @@ indexable-hooks = { version = "*", path = "../../packages/indexable-hooks" } proposal-hooks = { version = "*", path = "../../packages/proposal-hooks" } vote-hooks = { version = "*", path = "../../packages/vote-hooks" } +# neta dependencies. used to migrate back to v1 +cw-proposal-single-v1 = { package = "cw-proposal-single", version = "0.1.0" } +voting-v1 = { package = "dao-voting", version = "0.1.0" } + [dev-dependencies] cosmwasm-schema = { version = "1.0.0" } cw-multi-test = "0.13" cw4-voting = { path = "../cw4-voting", version = "*" } cw20-balance-voting = { path = "../../debug/cw20-balance-voting", version = "*" } cw20-staked-balance-voting = { path = "../cw20-staked-balance-voting", version = "*" } -cw-native-staked-balance-voting = { path = "../cw-native-staked-balance-voting", version = "*" } -cw721-stake = { path = "../cw721-stake", version = "*" } -cw721-base = "0.13" testing = { version = "*", path = "../../packages/testing" } cw20-stake = { path= "../cw20-stake", version = "*" } cw20-base = "0.13" diff --git a/contracts/cw-proposal-single/examples/schema.rs b/contracts/cw-proposal-single/examples/schema.rs index 3683b7836..b065a931e 100644 --- a/contracts/cw-proposal-single/examples/schema.rs +++ b/contracts/cw-proposal-single/examples/schema.rs @@ -56,5 +56,4 @@ fn main() { "ProposalHooksResponse", ); export_schema_with_title(&schema_for!(HooksResponse), &out_dir, "VoteHooksResponse"); - export_schema_with_title(&schema_for!(VoteResponse), &out_dir, "GetVoteResponse"); } diff --git a/contracts/cw-proposal-single/schema/execute_msg.json b/contracts/cw-proposal-single/schema/execute_msg.json index b8479e1f9..cd45e3940 100644 --- a/contracts/cw-proposal-single/schema/execute_msg.json +++ b/contracts/cw-proposal-single/schema/execute_msg.json @@ -191,7 +191,6 @@ "additionalProperties": false }, { - "description": "Adds an address as a consumer of proposal hooks. Consumers of proposal hooks have hook messages executed on them whenever the status of a proposal changes or a proposal is created. If a consumer contract errors when handling a hook message it will be removed from the list of consumers.", "type": "object", "required": [ "add_proposal_hook" @@ -212,7 +211,6 @@ "additionalProperties": false }, { - "description": "Removes a consumer of proposal hooks.", "type": "object", "required": [ "remove_proposal_hook" @@ -233,7 +231,6 @@ "additionalProperties": false }, { - "description": "Adds an address as a consumer of vote hooks. Consumers of vote hooks have hook messages executed on them whenever the a vote is cast. If a consumer contract errors when handling a hook message it will be removed from the list of consumers.", "type": "object", "required": [ "add_vote_hook" @@ -254,7 +251,6 @@ "additionalProperties": false }, { - "description": "Removed a consumer of vote hooks.", "type": "object", "required": [ "remove_vote_hook" diff --git a/contracts/cw-proposal-single/schema/get_vote_response.json b/contracts/cw-proposal-single/schema/get_vote_response.json deleted file mode 100644 index 9c534d5b0..000000000 --- a/contracts/cw-proposal-single/schema/get_vote_response.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "GetVoteResponse", - "description": "Information about a vote.", - "type": "object", - "properties": { - "vote": { - "description": "None if no such vote, Some otherwise.", - "anyOf": [ - { - "$ref": "#/definitions/VoteInfo" - }, - { - "type": "null" - } - ] - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Vote": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain" - ] - }, - "VoteInfo": { - "description": "Information about a vote that was cast.", - "type": "object", - "required": [ - "power", - "vote", - "voter" - ], - "properties": { - "power": { - "description": "The voting power behind the vote.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "vote": { - "description": "Position on the vote.", - "allOf": [ - { - "$ref": "#/definitions/Vote" - } - ] - }, - "voter": { - "description": "The address that voted.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - } - } - } - } -} diff --git a/contracts/cw-proposal-single/schema/list_proposals_response.json b/contracts/cw-proposal-single/schema/list_proposals_response.json index 974a52718..acc5ab412 100644 --- a/contracts/cw-proposal-single/schema/list_proposals_response.json +++ b/contracts/cw-proposal-single/schema/list_proposals_response.json @@ -557,33 +557,12 @@ } ] }, - "ProposalResponse": { - "description": "Information about a proposal returned by proposal queries.", - "type": "object", - "required": [ - "id", - "proposal" - ], - "properties": { - "id": { - "description": "The ID of the proposal being returned.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal": { - "$ref": "#/definitions/SingleChoiceProposal" - } - } - }, - "SingleChoiceProposal": { + "Proposal": { "type": "object", "required": [ "allow_revoting", - "created", "description", "expiration", - "last_updated", "msgs", "proposer", "start_height", @@ -597,14 +576,6 @@ "allow_revoting": { "type": "boolean" }, - "created": { - "description": "The timestamp at which this proposal was created.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, "deposit_info": { "description": "Information about the deposit that was sent as part of this proposal. None if no deposit.", "anyOf": [ @@ -627,14 +598,6 @@ } ] }, - "last_updated": { - "description": "The timestamp at which this proposal's status last changed (e.g. Passed, Executed).", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, "min_voting_period": { "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", "anyOf": [ @@ -694,6 +657,24 @@ } } }, + "ProposalResponse": { + "description": "Information about a proposal returned by proposal queries.", + "type": "object", + "required": [ + "id", + "proposal" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal": { + "$ref": "#/definitions/Proposal" + } + } + }, "StakingMsg": { "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", "oneOf": [ diff --git a/contracts/cw-proposal-single/schema/proposal_response.json b/contracts/cw-proposal-single/schema/proposal_response.json index b6debac14..c2c773900 100644 --- a/contracts/cw-proposal-single/schema/proposal_response.json +++ b/contracts/cw-proposal-single/schema/proposal_response.json @@ -9,13 +9,12 @@ ], "properties": { "id": { - "description": "The ID of the proposal being returned.", "type": "integer", "format": "uint64", "minimum": 0.0 }, "proposal": { - "$ref": "#/definitions/SingleChoiceProposal" + "$ref": "#/definitions/Proposal" } }, "definitions": { @@ -561,14 +560,12 @@ } ] }, - "SingleChoiceProposal": { + "Proposal": { "type": "object", "required": [ "allow_revoting", - "created", "description", "expiration", - "last_updated", "msgs", "proposer", "start_height", @@ -582,14 +579,6 @@ "allow_revoting": { "type": "boolean" }, - "created": { - "description": "The timestamp at which this proposal was created.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, "deposit_info": { "description": "Information about the deposit that was sent as part of this proposal. None if no deposit.", "anyOf": [ @@ -612,14 +601,6 @@ } ] }, - "last_updated": { - "description": "The timestamp at which this proposal's status last changed (e.g. Passed, Executed).", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, "min_voting_period": { "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", "anyOf": [ diff --git a/contracts/cw-proposal-single/schema/query_msg.json b/contracts/cw-proposal-single/schema/query_msg.json index e1cbc7fa2..c88c6dd38 100644 --- a/contracts/cw-proposal-single/schema/query_msg.json +++ b/contracts/cw-proposal-single/schema/query_msg.json @@ -39,7 +39,6 @@ "additionalProperties": false }, { - "description": "Lists all the proposals that have been cast in this module. Returns `query::ProposalListResponse`.", "type": "object", "required": [ "list_proposals" @@ -49,7 +48,6 @@ "type": "object", "properties": { "limit": { - "description": "The maximum number of proposals to return as part of this query. If no limit is set a max of 30 proposals will be returned.", "type": [ "integer", "null" @@ -58,7 +56,6 @@ "minimum": 0.0 }, "start_after": { - "description": "The proposal ID to start listing proposals after. For example, if this is set to 2 proposals with IDs 3 and higher will be returned.", "type": [ "integer", "null" @@ -72,7 +69,6 @@ "additionalProperties": false }, { - "description": "Lists all of the proposals that have been cast in this module in decending order of proposal ID. Returns `query::ProposalListResponse`.", "type": "object", "required": [ "reverse_proposals" @@ -82,7 +78,6 @@ "type": "object", "properties": { "limit": { - "description": "The maximum number of proposals to return as part of this query. If no limit is set a max of 30 proposals will be returned.", "type": [ "integer", "null" @@ -91,7 +86,6 @@ "minimum": 0.0 }, "start_before": { - "description": "The proposal ID to start listing proposals before. For example, if this is set to 6 proposals with IDs 5 and lower will be returned.", "type": [ "integer", "null" @@ -105,7 +99,6 @@ "additionalProperties": false }, { - "description": "Returns the number of proposals that have been created in this module.", "type": "object", "required": [ "proposal_count" @@ -118,13 +111,12 @@ "additionalProperties": false }, { - "description": "Returns a voters position on a propsal. Returns `query::VoteResponse`.", "type": "object", "required": [ - "get_vote" + "vote" ], "properties": { - "get_vote": { + "vote": { "type": "object", "required": [ "proposal_id", @@ -145,7 +137,6 @@ "additionalProperties": false }, { - "description": "Lists all of the votes that have been cast on a proposal. Returns `VoteListResponse`.", "type": "object", "required": [ "list_votes" @@ -158,7 +149,6 @@ ], "properties": { "limit": { - "description": "The maximum number of votes to return in response to this query. If no limit is specified a max of 30 are returned.", "type": [ "integer", "null" @@ -167,13 +157,11 @@ "minimum": 0.0 }, "proposal_id": { - "description": "The proposal to list the votes of.", "type": "integer", "format": "uint64", "minimum": 0.0 }, "start_after": { - "description": "The voter to start listing votes after. Ordering is done alphabetically.", "type": [ "string", "null" @@ -185,7 +173,6 @@ "additionalProperties": false }, { - "description": "Lists all of the consumers of proposal hooks for this module.", "type": "object", "required": [ "proposal_hooks" @@ -198,7 +185,6 @@ "additionalProperties": false }, { - "description": "Lists all of the consumers of vote hooks for this module. Returns indexable_hooks::HooksResponse.", "type": "object", "required": [ "vote_hooks" diff --git a/contracts/cw-proposal-single/schema/reverse_proposals_response.json b/contracts/cw-proposal-single/schema/reverse_proposals_response.json index a979df053..3c2e3daf1 100644 --- a/contracts/cw-proposal-single/schema/reverse_proposals_response.json +++ b/contracts/cw-proposal-single/schema/reverse_proposals_response.json @@ -557,33 +557,12 @@ } ] }, - "ProposalResponse": { - "description": "Information about a proposal returned by proposal queries.", - "type": "object", - "required": [ - "id", - "proposal" - ], - "properties": { - "id": { - "description": "The ID of the proposal being returned.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal": { - "$ref": "#/definitions/SingleChoiceProposal" - } - } - }, - "SingleChoiceProposal": { + "Proposal": { "type": "object", "required": [ "allow_revoting", - "created", "description", "expiration", - "last_updated", "msgs", "proposer", "start_height", @@ -597,14 +576,6 @@ "allow_revoting": { "type": "boolean" }, - "created": { - "description": "The timestamp at which this proposal was created.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, "deposit_info": { "description": "Information about the deposit that was sent as part of this proposal. None if no deposit.", "anyOf": [ @@ -627,14 +598,6 @@ } ] }, - "last_updated": { - "description": "The timestamp at which this proposal's status last changed (e.g. Passed, Executed).", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, "min_voting_period": { "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", "anyOf": [ @@ -694,6 +657,24 @@ } } }, + "ProposalResponse": { + "description": "Information about a proposal returned by proposal queries.", + "type": "object", + "required": [ + "id", + "proposal" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal": { + "$ref": "#/definitions/Proposal" + } + } + }, "StakingMsg": { "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", "oneOf": [ diff --git a/contracts/cw-proposal-single/src/contract.rs b/contracts/cw-proposal-single/src/contract.rs index fa768d76d..49d2ed72d 100644 --- a/contracts/cw-proposal-single/src/contract.rs +++ b/contracts/cw-proposal-single/src/contract.rs @@ -1,38 +1,47 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, - StdResult, Storage, WasmMsg, + to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order, Reply, + Response, StdResult, Storage, WasmMsg, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_core_interface::voting::IsActiveResponse; +use cw_proposal_single_v1 as neta; use cw_storage_plus::Bound; use cw_utils::Duration; use indexable_hooks::Hooks; use proposal_hooks::{new_proposal_hooks, proposal_status_changed_hooks}; use vote_hooks::new_vote_hooks; -use voting::deposit::{get_deposit_msg, get_return_deposit_msg, DepositInfo}; -use voting::proposal::{DEFAULT_LIMIT, MAX_PROPOSAL_SIZE}; -use voting::status::Status; -use voting::threshold::Threshold; -use voting::voting::{get_total_power, get_voting_power, validate_voting_period, Vote, Votes}; +use voting::{ + status::Status, + threshold::Threshold, + voting::{Vote, Votes}, +}; -use crate::msg::MigrateMsg; -use crate::proposal::SingleChoiceProposal; -use crate::state::Config; use crate::{ error::ContractError, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - proposal::advance_proposal_id, - query::ProposalListResponse, - query::{ProposalResponse, VoteInfo, VoteListResponse, VoteResponse}, - state::{Ballot, BALLOTS, CONFIG, PROPOSALS, PROPOSAL_COUNT, PROPOSAL_HOOKS, VOTE_HOOKS}, + msg::{DepositInfo, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + proposal::{advance_proposal_id, next_proposal_id, Proposal}, + query::{ProposalListResponse, ProposalResponse, VoteInfo, VoteListResponse, VoteResponse}, + state::{ + get_deposit_msg, get_return_deposit_msg, Ballot, Config, BALLOTS, CONFIG, OG_CONFIG, + PROPOSALS, PROPOSAL_COUNT, PROPOSAL_HOOKS, VOTE_HOOKS, + }, + utils::{get_total_power, get_voting_power, validate_voting_period}, + v1_neta::{ + neta_duration_to_v1, neta_expiration_to_v1, neta_status_to_v1, neta_threshold_to_v1, + neta_votes_to_v1, + }, }; const CONTRACT_NAME: &str = "crates.io:cw-govmod-single"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +/// Default limit for proposal pagination. +const DEFAULT_LIMIT: u64 = 30; +const MAX_PROPOSAL_SIZE: u64 = 30_000; + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -53,11 +62,6 @@ pub fn instantiate( let (min_voting_period, max_voting_period) = validate_voting_period(msg.min_voting_period, msg.max_voting_period)?; - let executor_addr: Option = msg - .executor_addr - .map(|human| deps.api.addr_validate(&human)) - .transpose()?; - let config = Config { threshold: msg.threshold, max_voting_period, @@ -66,12 +70,11 @@ pub fn instantiate( dao: dao.clone(), deposit_info, allow_revoting: msg.allow_revoting, - executor_addr, }; - // Initialize proposal count to zero so that queries return zero + // Initialize proposal count to 0 so that queries return zero // instead of None. - PROPOSAL_COUNT.save(deps.storage, &0)?; + PROPOSAL_COUNT.save(deps.storage, &1)?; CONFIG.save(deps.storage, &config)?; Ok(Response::default() @@ -93,7 +96,6 @@ pub fn execute( msgs, } => execute_propose(deps, env, info.sender, title, description, msgs), ExecuteMsg::Vote { proposal_id, vote } => execute_vote(deps, env, info, proposal_id, vote), - ExecuteMsg::Veto { proposal_id } => execute_veto(deps, env, info, proposal_id), ExecuteMsg::Execute { proposal_id } => execute_execute(deps, env, info, proposal_id), ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id), ExecuteMsg::UpdateConfig { @@ -125,7 +127,6 @@ pub fn execute( ExecuteMsg::RemoveVoteHook { address } => { execute_remove_vote_hook(deps, env, info, address) } - ExecuteMsg::AssignExecutor { address } => execute_assign_executor(deps, env, info, address), } } @@ -174,7 +175,7 @@ pub fn execute_propose( let proposal = { // Limit mutability to this block. - let mut proposal = SingleChoiceProposal { + let mut proposal = Proposal { title, description, proposer: sender.clone(), @@ -188,9 +189,6 @@ pub fn execute_propose( votes: Votes::zero(), allow_revoting: config.allow_revoting, deposit_info: config.deposit_info.clone(), - created: env.block.time, - last_updated: env.block.time, - vetoed: false, }; // Update the proposal's status. Addresses case where proposal // expires on the same block as it is created. @@ -262,9 +260,6 @@ pub fn execute_execute( } prop.status = Status::Executed; - // Update proposal's last updated timestamp. - prop.last_updated = env.block.time; - PROPOSALS.save(deps.storage, proposal_id, &prop)?; let refund_message = match prop.deposit_info { @@ -381,7 +376,6 @@ pub fn execute_vote( info.sender.to_string(), vote.to_string(), )?; - Ok(Response::default() .add_submessages(change_hooks) .add_submessages(vote_hooks) @@ -392,50 +386,6 @@ pub fn execute_vote( .add_attribute("status", prop.status.to_string())) } -// note - not in daodao -pub fn execute_veto( - deps: DepsMut, - env: Env, - info: MessageInfo, - proposal_id: u64, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - if config.executor_addr != Some(info.sender.clone()) { - return Err(ContractError::Unauthorized {}); - } - - let mut prop = PROPOSALS - .may_load(deps.storage, proposal_id)? - .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; - - if prop.current_status(&env.block) != Status::Open { - return Err(ContractError::NotOpen { id: proposal_id }); - } - - let old_status = prop.status; - - prop.vetoed = true; - prop.status = Status::Rejected; - // Update proposal's last updated timestamp. - prop.last_updated = env.block.time; - PROPOSALS.save(deps.storage, proposal_id, &prop)?; - - let changed_hooks = proposal_status_changed_hooks( - PROPOSAL_HOOKS, - deps.storage, - proposal_id, - old_status.to_string(), - prop.status.to_string(), - )?; - - Ok(Response::default() - .add_submessages(changed_hooks) - .add_attribute("action", "veto") - .add_attribute("sender", info.sender) - .add_attribute("proposal_id", proposal_id.to_string())) -} - pub fn execute_close( deps: DepsMut, env: Env, @@ -443,7 +393,6 @@ pub fn execute_close( proposal_id: u64, ) -> Result { let mut prop = PROPOSALS.load(deps.storage, proposal_id)?; - let config = CONFIG.load(deps.storage)?; // Update status to ensure that proposals which were open and have // expired are moved to "rejected." @@ -456,21 +405,16 @@ pub fn execute_close( let refund_message = match &prop.deposit_info { Some(deposit_info) => { - let receiver = if deposit_info.refund_failed_proposals { - &prop.proposer + if deposit_info.refund_failed_proposals { + get_return_deposit_msg(deposit_info, &prop.proposer)? } else { - // If we aren't refunding failed proposals then return - // the depost to the DAO treasury on close. - &config.dao - }; - get_return_deposit_msg(deposit_info, receiver)? + vec![] + } } None => vec![], }; prop.status = Status::Closed; - // Update proposal's last updated timestamp. - prop.last_updated = env.block.time; PROPOSALS.save(deps.storage, proposal_id, &prop)?; let changed_hooks = proposal_status_changed_hooks( @@ -527,7 +471,6 @@ pub fn execute_update_config( allow_revoting, dao, deposit_info, - executor_addr: config.executor_addr, // should NOT change via update config }, )?; @@ -641,33 +584,6 @@ pub fn execute_remove_vote_hook( .add_attribute("address", address)) } -pub fn execute_assign_executor( - deps: DepsMut, - _env: Env, - info: MessageInfo, - address: Option, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - // Only the DAO via prop OR the executor directly may call this method. - if info.sender == config.dao || config.executor_addr == Some(info.sender.clone()) { - // executor can be removed or reassigned to another address - let new_executor_addr: Option = address - .map(|human| deps.api.addr_validate(&human)) - .transpose()?; - - config.executor_addr = new_executor_addr; - - CONFIG.save(deps.storage, &config)?; - } else { - return Err(ContractError::Unauthorized {}); - } - - Ok(Response::default() - .add_attribute("action", "assign_executor") - .add_attribute("sender", info.sender)) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { @@ -690,6 +606,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } => query_reverse_proposals(deps, env, start_before, limit), QueryMsg::ProposalHooks {} => to_binary(&PROPOSAL_HOOKS.query_hooks(deps)?), QueryMsg::VoteHooks {} => to_binary(&VOTE_HOOKS.query_hooks(deps)?), + QueryMsg::NextProposalId {} => query_next_proposal_id(deps), } } @@ -714,7 +631,7 @@ pub fn query_list_proposals( let props: Vec = PROPOSALS .range(deps.storage, min, None, cosmwasm_std::Order::Ascending) .take(limit as usize) - .collect::, _>>()? + .collect::, _>>()? .into_iter() .map(|(id, proposal)| proposal.into_response(&env.block, id)) .collect(); @@ -733,7 +650,7 @@ pub fn query_reverse_proposals( let props: Vec = PROPOSALS .range(deps.storage, None, max, cosmwasm_std::Order::Descending) .take(limit as usize) - .collect::, _>>()? + .collect::, _>>()? .into_iter() .map(|(id, proposal)| proposal.into_response(&env.block, id)) .collect(); @@ -746,6 +663,10 @@ pub fn query_proposal_count(deps: Deps) -> StdResult { to_binary(&proposal_count) } +pub fn query_next_proposal_id(deps: Deps) -> StdResult { + to_binary(&next_proposal_id(deps.storage)?) +} + pub fn query_vote(deps: Deps, proposal_id: u64, voter: String) -> StdResult { let voter = deps.api.addr_validate(&voter)?; let ballot = BALLOTS.may_load(deps.storage, (proposal_id, voter.clone()))?; @@ -792,9 +713,79 @@ pub fn query_info(deps: Deps) -> StdResult { } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Don't do any state migrations. - Ok(Response::default()) +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + let ContractVersion { version, .. } = get_contract_version(deps.storage)?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + if version == CONTRACT_VERSION { + return Err(ContractError::AlreadyMigrated {}); + } + + match msg { + MigrateMsg::NetaToV1 {} => { + let current_config = neta::state::CONFIG.load(deps.storage)?; + let max_voting_period = neta_duration_to_v1(current_config.max_voting_period); + + // Update the stored config to remove the customized executor stuff. + OG_CONFIG.save( + deps.storage, + &Config { + threshold: neta_threshold_to_v1(current_config.threshold), + max_voting_period, + min_voting_period: current_config.min_voting_period.map(neta_duration_to_v1), + only_members_execute: current_config.only_members_execute, + allow_revoting: current_config.allow_revoting, + dao: current_config.dao.clone(), + deposit_info: None, + }, + )?; + + let current_proposals = neta::state::PROPOSALS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + + current_proposals + .clone() + .into_iter() + .try_for_each::<_, Result<_, ContractError>>(|(id, prop)| { + if prop + .deposit_info + .map(|info| !info.deposit.is_zero()) + .unwrap_or(false) + && prop.status != voting_v1::Status::Closed + && prop.status != voting_v1::Status::Executed + { + // No migration path for outstanding + // deposits. + return Err(ContractError::PendingProposals {}); + } + + let migrated_proposal = Proposal { + title: prop.title, + description: prop.description, + proposer: prop.proposer, + start_height: prop.start_height, + min_voting_period: prop.min_voting_period.map(neta_expiration_to_v1), + expiration: neta_expiration_to_v1(prop.expiration), + threshold: neta_threshold_to_v1(prop.threshold), + total_power: prop.total_power, + msgs: prop.msgs, + status: neta_status_to_v1(prop.status), + votes: neta_votes_to_v1(prop.votes), + allow_revoting: prop.allow_revoting, + deposit_info: None, + }; + PROPOSAL_COUNT.save(deps.storage, &(id))?; + PROPOSALS + .save(deps.storage, id, &migrated_proposal) + .map_err(|e| e.into()) + })?; + Ok(Response::default() + .add_attribute("action", "migrate") + .add_attribute("from", "netadao")) + } + MigrateMsg::FromCompatible {} => Ok(Response::default()), + } } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/cw-proposal-single/src/error.rs b/contracts/cw-proposal-single/src/error.rs index 9d8075f20..d684a22a1 100644 --- a/contracts/cw-proposal-single/src/error.rs +++ b/contracts/cw-proposal-single/src/error.rs @@ -18,9 +18,6 @@ pub enum ContractError { #[error("{0}")] ThresholdError(#[from] voting::threshold::ThresholdError), - #[error("{0}")] - VotingError(#[from] voting::error::VotingError), - #[error("Suggested proposal expiration is larger than the maximum proposal duration")] InvalidExpiration {}, @@ -54,7 +51,7 @@ pub enum ContractError { #[error("Proposal is closed.")] Closed {}, - #[error("Only rejected proposals may be closed.")] + #[error("Only rejected or expired proposals may be closed.")] WrongCloseStatus {}, #[error("The DAO is currently inactive, you cannot create proposals")] @@ -66,6 +63,11 @@ pub enum ContractError { #[error("Min voting period must be less than or equal to max voting period")] InvalidMinVotingPeriod {}, - #[error("Executor role not assigned")] - NoExecutorAssigned {}, + #[error("can not migrate. current version is up to date")] + AlreadyMigrated {}, + + #[error( + "all proposals with deposits must be completed out (closed or executed) before migration" + )] + PendingProposals {}, } diff --git a/contracts/cw-proposal-single/src/lib.rs b/contracts/cw-proposal-single/src/lib.rs index a62637b7a..2bd461d7e 100644 --- a/contracts/cw-proposal-single/src/lib.rs +++ b/contracts/cw-proposal-single/src/lib.rs @@ -1,47 +1,3 @@ -//! # cw-proposal-single -//! -//! A proposal module for a DAO DAO DAO which supports simple "yes", "no", -//! "abstain" voting. Proposals may have associated messages which will be -//! executed by the core module upon the proposal being passed and -//! executed. -//! -//! For more information about how these modules fit together see -//! [this](https://!github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-v1-Contracts-Design) -//! wiki page. -//! -//! For information about how this module counts votes and handles passing -//! thresholds see -//! [this](https://!github.com/DA0-DA0/dao-contracts/wiki/A-brief-overview-of-DAO-DAO-voting#proposal-status) -//! wiki page. -//! -//! ## Proposal deposits -//! -//! This contract may optionally be configured to require a deposit for -//! proposal creation. Currently, any cw20 token may be used. -//! -//! As a convienence one may specify that the module should use the same -//! token as the DAO's voting module using the `VotingModuleToken` variant -//! when specifying information about the deposit. For this to work the -//! voting module associated with the DAO must support the `TokenContract` -//! query. This query may be derived via the `#[token_query]` -//! [macro](../../packages/cw-core-macros/src/lib.rs). -//! -//! ## Hooks -//! -//! This module supports hooks for voting and proposal status changes. One -//! may register a contract to receive these hooks with the `AddVoteHook` -//! and `AddProposalHook` methods. Upon registration the contract will -//! receive messages whenever a vote is cast and a proposal's status -//! changes (for example, when the proposal passes). -//! -//! The format for these hook messages can be located in the -//! `proposal-hooks` and `vote-hooks` packages located in -//! `packages/proposal-hooks` and `packages/vote-hooks` respectively. -//! -//! To stop an invalid hook receiver from locking the proposal module -//! receivers will be removed from the hook list if they error when -//! handling a hook. - pub mod contract; mod error; pub mod msg; @@ -55,4 +11,7 @@ pub mod state; #[cfg(test)] mod tests; +pub mod utils; +pub mod v1_neta; + pub use crate::error::ContractError; diff --git a/contracts/cw-proposal-single/src/msg.rs b/contracts/cw-proposal-single/src/msg.rs index c43ddc936..a0d1f7c87 100644 --- a/contracts/cw-proposal-single/src/msg.rs +++ b/contracts/cw-proposal-single/src/msg.rs @@ -1,10 +1,10 @@ -use cosmwasm_std::{CosmosMsg, Empty}; +use cosmwasm_std::{CosmosMsg, Empty, Uint128}; use cw_utils::Duration; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cw_core_macros::govmod_query; -use voting::{deposit::DepositInfo, threshold::Threshold, voting::Vote}; +use voting::threshold::Threshold; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { @@ -32,10 +32,6 @@ pub struct InstantiateMsg { /// proposal. None if there is no deposit requirement, Some /// otherwise. pub deposit_info: Option, - /// This address will have special permission to veto - /// any proposal they deem malicious to the goals and - /// mission of the dao. - pub executor_addr: Option, } /// Information about the token to use for proposal deposits. @@ -53,6 +49,19 @@ pub enum DepositToken { VotingModuleToken {}, } +/// Information about the deposit required to create a proposal. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct DepositInfo { + /// The address of the cw20 token to be used for proposal + /// deposits. + pub token: DepositToken, + /// The number of tokens that must be deposited to create a + /// proposal. + pub deposit: Uint128, + /// If failed proposals should have their deposits refunded. + pub refund_failed_proposals: bool, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { @@ -72,11 +81,7 @@ pub enum ExecuteMsg { /// The ID of the proposal to vote on. proposal_id: u64, /// The senders position on the proposal. - vote: Vote, - }, - /// Allows executor to veto proposals - Veto { - proposal_id: u64, + vote: voting::voting::Vote, }, /// Causes the messages associated with a passed proposal to be /// executed by the DAO. @@ -122,32 +127,18 @@ pub enum ExecuteMsg { /// proposal. None if no deposit, Some otherwise. deposit_info: Option, }, - /// Adds an address as a consumer of proposal hooks. Consumers of - /// proposal hooks have hook messages executed on them whenever - /// the status of a proposal changes or a proposal is created. If - /// a consumer contract errors when handling a hook message it - /// will be removed from the list of consumers. AddProposalHook { address: String, }, - /// Removes a consumer of proposal hooks. RemoveProposalHook { address: String, }, - /// Adds an address as a consumer of vote hooks. Consumers of vote - /// hooks have hook messages executed on them whenever the a vote - /// is cast. If a consumer contract errors when handling a hook - /// message it will be removed from the list of consumers. AddVoteHook { address: String, }, - /// Removed a consumer of vote hooks. RemoveVoteHook { address: String, }, - AssignExecutor { - address: Option, - }, } #[govmod_query] @@ -158,56 +149,34 @@ pub enum QueryMsg { Config {}, /// Gets information about a proposal. Returns /// `proposals::Proposal`. - Proposal { proposal_id: u64 }, - /// Lists all the proposals that have been cast in this - /// module. Returns `query::ProposalListResponse`. + Proposal { + proposal_id: u64, + }, + NextProposalId {}, ListProposals { - /// The proposal ID to start listing proposals after. For - /// example, if this is set to 2 proposals with IDs 3 and - /// higher will be returned. start_after: Option, - /// The maximum number of proposals to return as part of this - /// query. If no limit is set a max of 30 proposals will be - /// returned. limit: Option, }, - /// Lists all of the proposals that have been cast in this module - /// in decending order of proposal ID. Returns - /// `query::ProposalListResponse`. ReverseProposals { - /// The proposal ID to start listing proposals before. For - /// example, if this is set to 6 proposals with IDs 5 and - /// lower will be returned. start_before: Option, - /// The maximum number of proposals to return as part of this - /// query. If no limit is set a max of 30 proposals will be - /// returned. limit: Option, }, - /// Returns the number of proposals that have been created in this - /// module. ProposalCount {}, - /// Returns a voters position on a propsal. Returns - /// `query::VoteResponse`. - Vote { proposal_id: u64, voter: String }, - /// Lists all of the votes that have been cast on a - /// proposal. Returns `VoteListResponse`. + Vote { + proposal_id: u64, + voter: String, + }, ListVotes { - /// The proposal to list the votes of. proposal_id: u64, - /// The voter to start listing votes after. Ordering is done - /// alphabetically. start_after: Option, - /// The maximum number of votes to return in response to this - /// query. If no limit is specified a max of 30 are returned. limit: Option, }, - /// Lists all of the consumers of proposal hooks for this module. ProposalHooks {}, - /// Lists all of the consumers of vote hooks for this - /// module. Returns indexable_hooks::HooksResponse. VoteHooks {}, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct MigrateMsg {} +pub enum MigrateMsg { + NetaToV1 {}, + FromCompatible {}, +} diff --git a/contracts/cw-proposal-single/src/proposal.rs b/contracts/cw-proposal-single/src/proposal.rs index 2c1d4e389..a2dd604ae 100644 --- a/contracts/cw-proposal-single/src/proposal.rs +++ b/contracts/cw-proposal-single/src/proposal.rs @@ -1,19 +1,21 @@ -use crate::query::ProposalResponse; -use crate::state::PROPOSAL_COUNT; -use cosmwasm_std::{ - Addr, BlockInfo, CosmosMsg, Decimal, Empty, StdResult, Storage, Timestamp, Uint128, -}; +use cosmwasm_std::{Addr, BlockInfo, CosmosMsg, Decimal, Empty, StdResult, Storage, Uint128}; use cw_utils::Expiration; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use voting::deposit::CheckedDepositInfo; -use voting::proposal::Proposal; -use voting::status::Status; -use voting::threshold::{PercentageThreshold, Threshold}; -use voting::voting::{does_vote_count_fail, does_vote_count_pass, Votes}; +use voting::{ + status::Status, + threshold::{PercentageThreshold, Threshold}, + voting::{compare_vote_count, VoteCmp, Votes}, +}; + + +use crate::{ + query::ProposalResponse, + state::{CheckedDepositInfo, PROPOSAL_COUNT}, +}; #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct SingleChoiceProposal { +pub struct Proposal { pub title: String, pub description: String, /// The address that created this proposal. @@ -44,33 +46,58 @@ pub struct SingleChoiceProposal { /// Information about the deposit that was sent as part of this /// proposal. None if no deposit. pub deposit_info: Option, - /// The timestamp at which this proposal was created. - pub created: Timestamp, - /// The timestamp at which this proposal's status last changed (e.g. Passed, Executed). - pub last_updated: Timestamp, - /// Proposal was vetoed by the executor - pub vetoed: bool, } -impl Proposal for SingleChoiceProposal { - fn proposer(&self) -> Addr { - self.proposer.clone() - } - fn deposit_info(&self) -> Option { - self.deposit_info.clone() +pub fn advance_proposal_id(store: &mut dyn Storage) -> StdResult { + let id: u64 = PROPOSAL_COUNT.load(store)? + 1; + PROPOSAL_COUNT.save(store, &id)?; + Ok(id) +} +pub fn next_proposal_id(store: &dyn Storage) -> StdResult { + Ok(PROPOSAL_COUNT.may_load(store)?.unwrap_or_default() + 1) +} + +pub fn does_vote_count_pass( + yes_votes: Uint128, + options: Uint128, + percent: PercentageThreshold, +) -> bool { + // Don't pass proposals if all the votes are abstain. + if options.is_zero() { + return false; } - fn status(&self) -> Status { - self.status + match percent { + PercentageThreshold::Majority {} => yes_votes.full_mul(2u64) > options.into(), + PercentageThreshold::Percent(percent) => { + compare_vote_count(yes_votes, VoteCmp::Geq, options, percent) + } } } -pub fn advance_proposal_id(store: &mut dyn Storage) -> StdResult { - let id: u64 = PROPOSAL_COUNT.may_load(store)?.unwrap_or_default() + 1; - PROPOSAL_COUNT.save(store, &id)?; - Ok(id) +pub fn does_vote_count_fail( + no_votes: Uint128, + options: Uint128, + percent: PercentageThreshold, +) -> bool { + // All abstain votes should result in a rejected proposal. + if options.is_zero() { + return true; + } + match percent { + PercentageThreshold::Majority {} => { + // Fails if no votes have >= half of all votes. + no_votes.full_mul(2u64) >= options.into() + } + PercentageThreshold::Percent(percent) => compare_vote_count( + no_votes, + VoteCmp::Greater, + options, + Decimal::one() - percent, + ), + } } -impl SingleChoiceProposal { +impl Proposal { /// Consumes the proposal and returns a version which may be used /// in a query response. The difference being that proposal /// statuses are only updated on vote, execute, and close @@ -98,12 +125,7 @@ impl SingleChoiceProposal { /// Sets a proposals status to its current status. pub fn update_status(&mut self, block: &BlockInfo) { - let new_status = self.current_status(block); - // Update last_updated only if status changed. - if new_status != self.status { - self.last_updated = block.time - } - self.status = new_status + self.status = self.current_status(block); } /// Returns true iff this proposal is sure to pass (even before @@ -278,7 +300,7 @@ mod test { is_expired: bool, min_voting_period_elapsed: bool, allow_revoting: bool, - ) -> (SingleChoiceProposal, BlockInfo) { + ) -> (Proposal, BlockInfo) { let block = mock_env().block; let expiration = match is_expired { true => Expiration::AtHeight(block.height - 5), @@ -288,8 +310,7 @@ mod test { true => Expiration::AtHeight(block.height - 5), false => Expiration::AtHeight(block.height + 5), }; - - let prop = SingleChoiceProposal { + let prop = Proposal { title: "Demo".to_string(), description: "Info".to_string(), proposer: Addr::unchecked("test"), @@ -303,9 +324,6 @@ mod test { total_power, votes, deposit_info: None, - created: block.time, - last_updated: block.time, - vetoed: false, }; (prop, block) } diff --git a/contracts/cw-proposal-single/src/query.rs b/contracts/cw-proposal-single/src/query.rs index bc7effbc2..2fb09d437 100644 --- a/contracts/cw-proposal-single/src/query.rs +++ b/contracts/cw-proposal-single/src/query.rs @@ -4,14 +4,13 @@ use serde::{Deserialize, Serialize}; use voting::voting::Vote; -use crate::proposal::SingleChoiceProposal; +use crate::proposal::Proposal; /// Information about a proposal returned by proposal queries. #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct ProposalResponse { - /// The ID of the proposal being returned. pub id: u64, - pub proposal: SingleChoiceProposal, + pub proposal: Proposal, } /// Information about a vote that was cast. diff --git a/contracts/cw-proposal-single/src/staking_tests.rs b/contracts/cw-proposal-single/src/staking_tests.rs index 2b2be7e02..d8225c3e3 100644 --- a/contracts/cw-proposal-single/src/staking_tests.rs +++ b/contracts/cw-proposal-single/src/staking_tests.rs @@ -113,7 +113,6 @@ fn instantiate_with_staked_balances_voting() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }) .unwrap(), }], diff --git a/contracts/cw-proposal-single/src/state.rs b/contracts/cw-proposal-single/src/state.rs index c4b79d3e6..9e877b35d 100644 --- a/contracts/cw-proposal-single/src/state.rs +++ b/contracts/cw-proposal-single/src/state.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::{to_binary, Addr, CosmosMsg, Deps, StdResult, Uint128, WasmMsg}; use cw_storage_plus::{Item, Map}; use cw_utils::Duration; @@ -6,18 +6,26 @@ use indexable_hooks::Hooks; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use voting::{deposit::CheckedDepositInfo, threshold::Threshold, voting::Vote}; +use voting::{threshold::Threshold, voting::Vote}; -use crate::proposal::SingleChoiceProposal; +use crate::{ + msg::{DepositInfo, DepositToken}, + proposal::Proposal, +}; -/// A vote cast for a proposal. +/// Counterpart to the `DepositInfo` struct which has been processed. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Ballot { - /// The amount of voting power behind the vote. - pub power: Uint128, - /// The position. - pub vote: Vote, +pub struct CheckedDepositInfo { + /// The address of the cw20 token to be used for proposal + /// deposits. + pub token: Addr, + /// The number of tokens that must be deposited to create a + /// proposal. + pub deposit: Uint128, + /// If failed proposals should have their deposits refunded. + pub refund_failed_proposals: bool, } + /// The governance module's configuration. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Config { @@ -47,19 +55,109 @@ pub struct Config { /// Information about the depost required to create a /// proposal. None if no deposit is required, Some otherwise. pub deposit_info: Option, - /// This address will have special permissions to veto - /// any proposal they deem malicious to the goals and - /// mission of the dao. - pub executor_addr: Option, } -/// The current top level config for the module. +/// A vote cast for a proposal. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Ballot { + /// The amount of voting power behind the vote. + pub power: Uint128, + /// The position. + pub vote: Vote, +} + +/// The current top level config for the module. The "config" key was +/// previously used to store configs for netadao custom implementation. +pub const OG_CONFIG: Item = Item::new("config_v1"); + pub const CONFIG: Item = Item::new("config"); -/// The number of proposals that have been created. pub const PROPOSAL_COUNT: Item = Item::new("proposal_count"); -pub const PROPOSALS: Map = Map::new("proposals"); +pub const PROPOSALS: Map = Map::new("proposals"); pub const BALLOTS: Map<(u64, Addr), Ballot> = Map::new("ballots"); -/// Consumers of proposal state change hooks. pub const PROPOSAL_HOOKS: Hooks = Hooks::new("proposal_hooks"); -/// Consumers of vote hooks. pub const VOTE_HOOKS: Hooks = Hooks::new("vote_hooks"); + +impl DepositInfo { + /// Converts deposit info into checked deposit info. + pub fn into_checked(self, deps: Deps, dao: Addr) -> StdResult { + let Self { + token, + deposit, + refund_failed_proposals, + } = self; + let token = match token { + DepositToken::Token { address } => deps.api.addr_validate(&address)?, + DepositToken::VotingModuleToken {} => { + let voting_module: Addr = deps + .querier + .query_wasm_smart(dao, &cw_core::msg::QueryMsg::VotingModule {})?; + let token_addr: Addr = deps.querier.query_wasm_smart( + voting_module, + &cw_core_interface::voting::Query::TokenContract {}, + )?; + token_addr + } + }; + // Make an info query as a smoke test that we are indeed + // working with a token here. We can't turbofish this + // type. See . + // + // This also covers the case where a misbehaving core contract + // has returned an invalid address from the `TokenContract` + // query as this will fail if the address is bad. + let _info: cw20::TokenInfoResponse = deps + .querier + .query_wasm_smart(token.clone(), &cw20::Cw20QueryMsg::TokenInfo {})?; + Ok(CheckedDepositInfo { + token, + deposit, + refund_failed_proposals, + }) + } +} + +pub fn get_deposit_msg( + info: &Option, + contract: &Addr, + sender: &Addr, +) -> StdResult> { + match info { + Some(info) => { + if info.deposit.is_zero() { + Ok(vec![]) + } else { + let transfer_msg = WasmMsg::Execute { + contract_addr: info.token.to_string(), + funds: vec![], + msg: to_binary(&cw20::Cw20ExecuteMsg::TransferFrom { + owner: sender.to_string(), + recipient: contract.to_string(), + amount: info.deposit, + })?, + }; + let transfer_msg: CosmosMsg = transfer_msg.into(); + Ok(vec![transfer_msg]) + } + } + None => Ok(vec![]), + } +} + +pub fn get_return_deposit_msg( + deposit_info: &CheckedDepositInfo, + proposer: &Addr, +) -> StdResult> { + if deposit_info.deposit.is_zero() { + return Ok(vec![]); + } + let transfer_msg = WasmMsg::Execute { + contract_addr: deposit_info.token.to_string(), + funds: vec![], + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { + recipient: proposer.to_string(), + amount: deposit_info.deposit, + })?, + }; + let transfer_msg: CosmosMsg = transfer_msg.into(); + Ok(vec![transfer_msg]) +} diff --git a/contracts/cw-proposal-single/src/tests.rs b/contracts/cw-proposal-single/src/tests.rs index 34f64baf9..a83d1eb41 100644 --- a/contracts/cw-proposal-single/src/tests.rs +++ b/contracts/cw-proposal-single/src/tests.rs @@ -1,9 +1,9 @@ use std::u128; -use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, Empty, Timestamp, Uint128, WasmMsg}; +use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, Empty, Uint128, WasmMsg}; use cw20::Cw20Coin; use cw20_staked_balance_voting::msg::ActiveThreshold; -use cw_multi_test::{next_block, App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg}; +use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor}; use cw_core::msg::ModuleInstantiateInfo; use cw_utils::Duration; @@ -12,17 +12,16 @@ use indexable_hooks::HooksResponse; use testing::{ShouldExecute, TestSingleChoiceVote}; use voting::{ - deposit::{CheckedDepositInfo, DepositInfo, DepositToken}, status::Status, threshold::{PercentageThreshold, Threshold}, voting::{Vote, Votes}, }; use crate::{ - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, - proposal::SingleChoiceProposal, + msg::{DepositInfo, DepositToken, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + proposal::Proposal, query::{ProposalListResponse, ProposalResponse, VoteInfo, VoteResponse}, - state::Config, + state::{CheckedDepositInfo, Config}, ContractError, }; @@ -46,7 +45,7 @@ fn cw20_stake_contract() -> Box> { Box::new(contract) } -fn proposal_contract() -> Box> { +fn single_proposal_contract() -> Box> { let contract = ContractWrapper::new( crate::contract::execute, crate::contract::instantiate, @@ -56,6 +55,16 @@ fn proposal_contract() -> Box> { .with_migrate(crate::contract::migrate); Box::new(contract) } +fn neta_proposal_contract() -> Box> { + let contract = ContractWrapper::new( + cw_proposal_single_v1::contract::execute, + cw_proposal_single_v1::contract::instantiate, + cw_proposal_single_v1::contract::query, + ) + .with_reply(crate::contract::reply) + .with_migrate(crate::contract::migrate); + Box::new(contract) +} fn cw20_balances_voting() -> Box> { let contract = ContractWrapper::new( @@ -77,33 +86,6 @@ fn cw20_staked_balances_voting() -> Box> { Box::new(contract) } -fn native_staked_balances_voting() -> Box> { - let contract = ContractWrapper::new( - cw_native_staked_balance_voting::contract::execute, - cw_native_staked_balance_voting::contract::instantiate, - cw_native_staked_balance_voting::contract::query, - ); - Box::new(contract) -} - -fn cw721_base() -> Box> { - let contract = ContractWrapper::new( - cw721_base::entry::execute, - cw721_base::entry::instantiate, - cw721_base::entry::query, - ); - Box::new(contract) -} - -fn cw721_stake() -> Box> { - let contract = ContractWrapper::new( - cw721_stake::contract::execute, - cw721_stake::contract::instantiate, - cw721_stake::contract::query, - ); - Box::new(contract) -} - fn cw_gov_contract() -> Box> { let contract = ContractWrapper::new( cw_core::contract::execute, @@ -152,239 +134,10 @@ fn cw4_voting_contract() -> Box> { Box::new(contract) } -fn instantiate_with_staked_cw721_governance( - app: &mut App, - proposal_module_code_id: u64, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let cw721_id = app.store_code(cw721_base()); - let cw721_stake_id = app.store_code(cw721_stake()); - let core_contract_id = app.store_code(cw_gov_contract()); - - let nft_address = app - .instantiate_contract( - cw721_id, - Addr::unchecked("ekez"), - &cw721_base::msg::InstantiateMsg { - minter: "ekez".to_string(), - symbol: "token".to_string(), - name: "ekez token best token".to_string(), - }, - &[], - "nft-staking", - None, - ) - .unwrap(); - - let instantiate_core = cw_core::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: cw721_stake_id, - msg: to_binary(&cw721_stake::msg::InstantiateMsg { - owner: Some(cw721_stake::msg::Owner::Instantiator {}), - manager: None, - unstaking_duration: None, - nft_address: nft_address.to_string(), - }) - .unwrap(), - admin: cw_core::msg::Admin::None {}, - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - label: "DAO DAO governance module.".to_string(), - admin: cw_core::msg::Admin::CoreContract {}, - msg: to_binary(&proposal_module_instantiate).unwrap(), - }], - initial_items: None, - }; - - let core_addr = app - .instantiate_contract( - core_contract_id, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let core_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr.clone(), &cw_core::msg::QueryMsg::DumpState {}) - .unwrap(); - let staking_addr = core_state.voting_module; - - for Cw20Coin { address, amount } in initial_balances { - for i in 0..amount.u128() { - app.execute_contract( - Addr::unchecked("ekez"), - nft_address.clone(), - &cw721_base::msg::ExecuteMsg::Mint(cw721_base::msg::MintMsg::> { - token_id: format!("{}_{}", address, i), - owner: address.clone(), - token_uri: None, - extension: None, - }), - &[], - ) - .unwrap(); - app.execute_contract( - Addr::unchecked(address.clone()), - nft_address.clone(), - &cw721_base::msg::ExecuteMsg::SendNft::> { - contract: staking_addr.to_string(), - token_id: format!("{}_{}", address, i), - msg: to_binary("").unwrap(), - }, - &[], - ) - .unwrap(); - } - } - - // Update the block so that staked balances appear. - app.update_block(|block| block.height += 1); - - core_addr -} - -fn instantiate_with_native_staked_balances_governance( - app: &mut App, - proposal_module_code_id: u64, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - // Collapse balances so that we can test double votes. - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let native_stake_id = app.store_code(native_staked_balances_voting()); - let core_contract_id = app.store_code(cw_gov_contract()); - - let instantiate_core = cw_core::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: native_stake_id, - msg: to_binary(&cw_native_staked_balance_voting::msg::InstantiateMsg { - owner: Some(cw_native_staked_balance_voting::msg::Owner::Instantiator {}), - manager: None, - denom: "ujuno".to_string(), - unstaking_duration: None, - }) - .unwrap(), - admin: cw_core::msg::Admin::None {}, - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - label: "DAO DAO governance module.".to_string(), - admin: cw_core::msg::Admin::CoreContract {}, - msg: to_binary(&proposal_module_instantiate).unwrap(), - }], - initial_items: None, - }; - - let core_addr = app - .instantiate_contract( - core_contract_id, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr.clone(), &cw_core::msg::QueryMsg::DumpState {}) - .unwrap(); - let native_staking_addr = gov_state.voting_module; - - for Cw20Coin { address, amount } in initial_balances { - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: address.clone(), - amount: vec![Coin { - denom: "ujuno".to_string(), - amount, - }], - })) - .unwrap(); - app.execute_contract( - Addr::unchecked(&address), - native_staking_addr.clone(), - &cw_native_staked_balance_voting::msg::ExecuteMsg::Stake {}, - &[Coin { - amount, - denom: "ujuno".to_string(), - }], - ) - .unwrap(); - } - - app.update_block(next_block); - - core_addr -} - fn instantiate_with_staked_balances_governance( app: &mut App, - proposal_module_code_id: u64, - proposal_module_instantiate: InstantiateMsg, + governance_code_id: u64, + governance_instantiate: InstantiateMsg, initial_balances: Option>, ) -> Addr { let initial_balances = initial_balances.unwrap_or_else(|| { @@ -444,10 +197,10 @@ fn instantiate_with_staked_balances_governance( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, + code_id: governance_code_id, label: "DAO DAO governance module.".to_string(), admin: cw_core::msg::Admin::CoreContract {}, - msg: to_binary(&proposal_module_instantiate).unwrap(), + msg: to_binary(&governance_instantiate).unwrap(), }], initial_items: None, }; @@ -507,8 +260,8 @@ fn instantiate_with_staked_balances_governance( fn instantiate_with_staking_active_threshold( app: &mut App, - proposal_module_code_id: u64, - proposal_module_instantiate: InstantiateMsg, + code_id: u64, + msg: InstantiateMsg, initial_balances: Option>, active_threshold: Option, ) -> Addr { @@ -553,8 +306,8 @@ fn instantiate_with_staking_active_threshold( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![cw_core::msg::ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_binary(&proposal_module_instantiate).unwrap(), + code_id, + msg: to_binary(&msg).unwrap(), admin: cw_core::msg::Admin::CoreContract {}, label: "DAO DAO governance module".to_string(), }], @@ -574,8 +327,8 @@ fn instantiate_with_staking_active_threshold( fn instantiate_with_cw4_groups_governance( app: &mut App, - proposal_module_code_id: u64, - proposal_module_instantiate: InstantiateMsg, + governance_code_id: u64, + governance_instantiate: InstantiateMsg, initial_weights: Option>, ) -> Addr { let cw4_id = app.store_code(cw4_contract()); @@ -622,8 +375,8 @@ fn instantiate_with_cw4_groups_governance( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![cw_core::msg::ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_binary(&proposal_module_instantiate).unwrap(), + code_id: governance_code_id, + msg: to_binary(&governance_instantiate).unwrap(), admin: cw_core::msg::Admin::CoreContract {}, label: "DAO DAO governance module".to_string(), }], @@ -649,8 +402,8 @@ fn instantiate_with_cw4_groups_governance( fn instantiate_with_cw20_balances_governance( app: &mut App, - proposal_module_code_id: u64, - proposal_module_instantiate: InstantiateMsg, + governance_code_id: u64, + governance_instantiate: InstantiateMsg, initial_balances: Option>, ) -> Addr { let cw20_id = app.store_code(cw20_contract()); @@ -705,8 +458,8 @@ fn instantiate_with_cw20_balances_governance( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![cw_core::msg::ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_binary(&proposal_module_instantiate).unwrap(), + code_id: governance_code_id, + msg: to_binary(&governance_instantiate).unwrap(), admin: cw_core::msg::Admin::CoreContract {}, label: "DAO DAO governance module".to_string(), }], @@ -735,7 +488,7 @@ fn do_votes_cw20_balances( threshold, expected_status, total_supply, - None::, + None, instantiate_with_cw20_balances_governance, ); } @@ -745,38 +498,6 @@ fn do_votes_staked_balances( threshold: Threshold, expected_status: Status, total_supply: Option, -) { - do_test_votes( - votes, - threshold, - expected_status, - total_supply, - None::, - instantiate_with_staked_balances_governance, - ); -} - -fn do_votes_nft_balances( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, -) { - do_test_votes( - votes, - threshold, - expected_status, - total_supply, - None, - instantiate_with_staked_cw721_governance, - ); -} - -fn do_votes_native_staked_balances( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, ) { do_test_votes( votes, @@ -784,7 +505,7 @@ fn do_votes_native_staked_balances( expected_status, total_supply, None, - instantiate_with_native_staked_balances_governance, + instantiate_with_staked_balances_governance, ); } @@ -799,26 +520,24 @@ fn do_votes_cw4_weights( threshold, expected_status, total_supply, - None::, + None, instantiate_with_cw4_groups_governance, ); } -// cloned because lazy -fn do_test_votes_with_executor( +fn do_test_votes( votes: Vec, threshold: Threshold, expected_status: Status, total_supply: Option, deposit_info: Option, setup_governance: F, - executor_addr: Option, ) -> (App, Addr) where F: Fn(&mut App, u64, InstantiateMsg, Option>) -> Addr, { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let mut initial_balances = votes .iter() @@ -849,7 +568,6 @@ where only_members_execute: false, allow_revoting: false, deposit_info, - executor_addr, }; let governance_addr = @@ -968,221 +686,32 @@ where (app, governance_addr) } -fn do_test_votes( +// Creates a proposal and then executes a series of votes on those +// proposals. Asserts both that those votes execute as expected and +// that the final status of the proposal is what is expected. Returns +// the address of the governance contract that it has created so that +// callers may do additional inspection of the contract's state. +fn do_test_votes_cw20_balances( votes: Vec, threshold: Threshold, expected_status: Status, total_supply: Option, deposit_info: Option, - setup_governance: F, -) -> (App, Addr) -where - F: Fn(&mut App, u64, InstantiateMsg, Option>) -> Addr, -{ - let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); +) -> (App, Addr) { + do_test_votes( + votes, + threshold, + expected_status, + total_supply, + deposit_info, + instantiate_with_cw20_balances_governance, + ) +} - let mut initial_balances = votes - .iter() - .map(|TestSingleChoiceVote { voter, weight, .. }| Cw20Coin { - address: voter.to_string(), - amount: *weight, - }) - .collect::>(); - let initial_balances_supply = votes.iter().fold(Uint128::zero(), |p, n| p + n.weight); - let to_fill = total_supply.map(|total_supply| total_supply - initial_balances_supply); - if let Some(fill) = to_fill { - initial_balances.push(Cw20Coin { - address: "filler".to_string(), - amount: fill, - }) - } - - let proposer = match votes.first() { - Some(vote) => vote.voter.clone(), - None => panic!("do_test_votes must have at least one vote."), - }; - - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - threshold, - max_voting_period, - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - deposit_info, - executor_addr: None, - }; - - let governance_addr = - setup_governance(&mut app, govmod_id, instantiate, Some(initial_balances)); - - let governance_modules: Vec = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // Allow a proposal deposit as needed. - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - if let Some(CheckedDepositInfo { - ref token, deposit, .. - }) = config.deposit_info - { - app.execute_contract( - Addr::unchecked(&proposer), - token.clone(), - &cw20_base::msg::ExecuteMsg::IncreaseAllowance { - spender: govmod_single.to_string(), - amount: deposit, - expires: None, - }, - &[], - ) - .unwrap(); - } - - app.execute_contract( - Addr::unchecked(&proposer), - govmod_single.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - msgs: vec![], - }, - &[], - ) - .unwrap(); - - // Cast votes. - for vote in votes { - let TestSingleChoiceVote { - voter, - position, - weight, - should_execute, - } = vote; - // Vote on the proposal. - let res = app.execute_contract( - Addr::unchecked(voter.clone()), - govmod_single.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: position, - }, - &[], - ); - match should_execute { - ShouldExecute::Yes => { - assert!(res.is_ok()); - // Check that the vote was recorded correctly. - let vote: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod_single.clone(), - &QueryMsg::Vote { - proposal_id: 1, - voter: voter.clone(), - }, - ) - .unwrap(); - let expected = VoteResponse { - vote: Some(VoteInfo { - voter: Addr::unchecked(&voter), - vote: position, - power: match config.deposit_info { - Some(CheckedDepositInfo { deposit, .. }) => { - if proposer == voter { - weight - deposit - } else { - weight - } - } - None => weight, - }, - }), - }; - assert_eq!(vote, expected) - } - ShouldExecute::No => { - res.unwrap_err(); - } - ShouldExecute::Meh => (), - } - } - - let proposal: ProposalResponse = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 1 }) - .unwrap(); - - assert_eq!(proposal.proposal.status, expected_status); - - (app, governance_addr) -} - -// Creates a proposal and then executes a series of votes on those -// proposals. Asserts both that those votes execute as expected and -// that the final status of the proposal is what is expected. Returns -// the address of the governance contract that it has created so that -// callers may do additional inspection of the contract's state. -fn do_test_votes_cw20_balances( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, - deposit_info: Option, -) -> (App, Addr) { - do_test_votes( - votes, - threshold, - expected_status, - total_supply, - deposit_info, - instantiate_with_cw20_balances_governance, - ) -} - -// Creates a proposal and then executes a series of votes on those -// proposals. Asserts both that those votes execute as expected and -// that the final status of the proposal is what is expected. Returns -// the address of the governance contract that it has created so that -// callers may do additional inspection of the contract's state. -// whiskey cloned because lazy -fn do_test_votes_cw20_balances_with_executor( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, - deposit_info: Option, - executor_addr: Option, -) -> (App, Addr) { - do_test_votes_with_executor( - votes, - threshold, - expected_status, - total_supply, - deposit_info, - instantiate_with_cw20_balances_governance, - executor_addr, - ) -} - -#[test] -fn test_propose() { - let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); +#[test] +fn test_propose() { + let mut app = App::default(); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -1195,7 +724,6 @@ fn test_propose() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; let governance_addr = @@ -1227,7 +755,6 @@ fn test_propose() { allow_revoting: false, dao: governance_addr, deposit_info: None, - executor_addr: None, }; assert_eq!(config, expected); @@ -1249,7 +776,7 @@ fn test_propose() { .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 1 }) .unwrap(); let current_block = app.block_info(); - let expected = SingleChoiceProposal { + let expected = Proposal { title: "A simple text proposal".to_string(), description: "This is a simple text proposal".to_string(), proposer: Addr::unchecked(CREATOR_ADDR), @@ -1263,9 +790,6 @@ fn test_propose() { status: Status::Open, votes: Votes::zero(), deposit_info: None, - created: current_block.time, - last_updated: current_block.time, - vetoed: false, }; assert_eq!(created.proposal, expected); @@ -1275,7 +799,7 @@ fn test_propose() { #[test] fn test_propose_supports_stargate_message() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -1288,7 +812,6 @@ fn test_propose_supports_stargate_message() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; let governance_addr = @@ -1328,7 +851,7 @@ fn test_propose_supports_stargate_message() { .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 1 }) .unwrap(); let current_block = app.block_info(); - let expected = SingleChoiceProposal { + let expected = Proposal { title: "A simple text proposal".to_string(), description: "This is a simple text proposal".to_string(), proposer: Addr::unchecked(CREATOR_ADDR), @@ -1345,9 +868,6 @@ fn test_propose_supports_stargate_message() { status: Status::Open, votes: Votes::zero(), deposit_info: None, - created: current_block.time, - last_updated: current_block.time, - vetoed: false, }; assert_eq!(created.proposal, expected); @@ -1358,47 +878,40 @@ fn test_propose_supports_stargate_message() { fn test_vote_simple() { testing::test_simple_votes(do_votes_cw20_balances); testing::test_simple_votes(do_votes_cw4_weights); - testing::test_simple_votes(do_votes_staked_balances); - testing::test_simple_votes(do_votes_nft_balances); - testing::test_simple_votes(do_votes_native_staked_balances) + testing::test_simple_votes(do_votes_staked_balances) } #[test] fn test_simple_vote_no_overflow() { testing::test_simple_vote_no_overflow(do_votes_cw20_balances); - testing::test_simple_vote_no_overflow(do_votes_staked_balances); - testing::test_simple_vote_no_overflow(do_votes_native_staked_balances); + testing::test_simple_vote_no_overflow(do_votes_staked_balances) } #[test] fn test_vote_no_overflow() { testing::test_vote_no_overflow(do_votes_cw20_balances); - testing::test_vote_no_overflow(do_votes_staked_balances); - testing::test_vote_no_overflow(do_votes_native_staked_balances); + testing::test_vote_no_overflow(do_votes_staked_balances) } #[test] fn test_simple_early_rejection() { testing::test_simple_early_rejection(do_votes_cw20_balances); testing::test_simple_early_rejection(do_votes_cw4_weights); - testing::test_simple_early_rejection(do_votes_staked_balances); - testing::test_simple_early_rejection(do_votes_native_staked_balances); + testing::test_simple_early_rejection(do_votes_staked_balances) } #[test] fn test_vote_abstain_only() { testing::test_vote_abstain_only(do_votes_cw20_balances); testing::test_vote_abstain_only(do_votes_cw4_weights); - testing::test_vote_abstain_only(do_votes_staked_balances); - testing::test_vote_abstain_only(do_votes_native_staked_balances); + testing::test_vote_abstain_only(do_votes_staked_balances) } #[test] fn test_tricky_rounding() { testing::test_tricky_rounding(do_votes_cw20_balances); testing::test_tricky_rounding(do_votes_cw4_weights); - testing::test_tricky_rounding(do_votes_staked_balances); - testing::test_tricky_rounding(do_votes_native_staked_balances); + testing::test_tricky_rounding(do_votes_staked_balances) } #[test] @@ -1406,43 +919,33 @@ fn test_no_double_votes() { testing::test_no_double_votes(do_votes_cw20_balances); testing::test_no_double_votes(do_votes_cw4_weights); testing::test_no_double_votes(do_votes_staked_balances); - testing::test_no_double_votes(do_votes_nft_balances); - testing::test_no_double_votes(do_votes_native_staked_balances); } #[test] fn test_votes_favor_yes() { testing::test_votes_favor_yes(do_votes_cw20_balances); testing::test_votes_favor_yes(do_votes_staked_balances); - testing::test_votes_favor_yes(do_votes_nft_balances); - testing::test_votes_favor_yes(do_votes_native_staked_balances); } #[test] fn test_votes_low_threshold() { testing::test_votes_low_threshold(do_votes_cw20_balances); testing::test_votes_low_threshold(do_votes_cw4_weights); - testing::test_votes_low_threshold(do_votes_staked_balances); - testing::test_votes_low_threshold(do_votes_nft_balances); - testing::test_votes_low_threshold(do_votes_native_staked_balances); + testing::test_votes_low_threshold(do_votes_staked_balances) } #[test] fn test_majority_vs_half() { testing::test_majority_vs_half(do_votes_cw20_balances); testing::test_majority_vs_half(do_votes_cw4_weights); - testing::test_majority_vs_half(do_votes_staked_balances); - testing::test_majority_vs_half(do_votes_nft_balances); - testing::test_majority_vs_half(do_votes_native_staked_balances); + testing::test_majority_vs_half(do_votes_staked_balances) } #[test] fn test_pass_threshold_not_quorum() { testing::test_pass_threshold_not_quorum(do_votes_cw20_balances); testing::test_pass_threshold_not_quorum(do_votes_cw4_weights); - testing::test_pass_threshold_not_quorum(do_votes_staked_balances); - testing::test_pass_threshold_not_quorum(do_votes_nft_balances); - testing::test_pass_threshold_not_quorum(do_votes_native_staked_balances); + testing::test_pass_threshold_not_quorum(do_votes_staked_balances) } #[test] @@ -1450,31 +953,15 @@ fn test_pass_threshold_exactly_quorum() { testing::test_pass_exactly_quorum(do_votes_cw20_balances); testing::test_pass_exactly_quorum(do_votes_cw4_weights); testing::test_pass_exactly_quorum(do_votes_staked_balances); - testing::test_pass_exactly_quorum(do_votes_nft_balances); - testing::test_pass_exactly_quorum(do_votes_native_staked_balances); } /// Generate some random voting selections and make sure they behave -/// as expected. We split this test up as these take a while and cargo -/// can parallize tests. -#[test] -fn fuzz_voting_cw20_balances() { - testing::fuzz_voting(do_votes_cw20_balances) -} - -#[test] -fn fuzz_voting_cw4_weights() { - testing::fuzz_voting(do_votes_cw4_weights) -} - -#[test] -fn fuzz_voting_staked_balances() { - testing::fuzz_voting(do_votes_staked_balances) -} - +/// as expected. #[test] -fn fuzz_voting_native_staked_balances() { - testing::fuzz_voting(do_votes_native_staked_balances) +fn fuzz_voting() { + testing::fuzz_voting(do_votes_cw20_balances); + testing::fuzz_voting(do_votes_cw4_weights); + testing::fuzz_voting(do_votes_staked_balances); } /// Instantiate the contract and use the voting module's token @@ -1482,7 +969,7 @@ fn fuzz_voting_native_staked_balances() { #[test] fn test_voting_module_token_proposal_deposit_instantiate() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -1499,7 +986,6 @@ fn test_voting_module_token_proposal_deposit_instantiate() { deposit: Uint128::new(1), refund_failed_proposals: true, }), - executor_addr: None, }; let governance_addr = @@ -1542,7 +1028,7 @@ fn test_voting_module_token_proposal_deposit_instantiate() { #[test] fn test_different_token_proposal_deposit() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let cw20_id = app.store_code(cw20_contract()); let cw20_addr = app .instantiate_contract( @@ -1579,7 +1065,6 @@ fn test_different_token_proposal_deposit() { deposit: Uint128::new(1), refund_failed_proposals: true, }), - executor_addr: None, }; instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None); @@ -1592,7 +1077,7 @@ fn test_different_token_proposal_deposit() { #[should_panic(expected = "Error parsing into type cw20_balance_voting::msg::QueryMsg")] fn test_bad_token_proposal_deposit() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let cw20_id = app.store_code(cw20_contract()); let votemod_id = app.store_code(cw20_balances_voting()); @@ -1637,7 +1122,6 @@ fn test_bad_token_proposal_deposit() { deposit: Uint128::new(1), refund_failed_proposals: true, }), - executor_addr: None, }; instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None); @@ -1646,7 +1130,7 @@ fn test_bad_token_proposal_deposit() { #[test] fn test_take_proposal_deposit() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -1663,7 +1147,6 @@ fn test_take_proposal_deposit() { deposit: Uint128::new(1), refund_failed_proposals: true, }), - executor_addr: None, }; let governance_addr = instantiate_with_cw20_balances_governance( @@ -1894,7 +1377,7 @@ fn test_close_open_proposal() { ) .unwrap(); - // Proposal has not been closed so deposit has been + // Proposal has not been closed so deposit has not been // refunded. assert_eq!(balance.balance, Uint128::new(10)); } @@ -1996,7 +1479,7 @@ fn test_deposit_return_on_close() { #[test] fn test_execute_expired_proposal() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_staked_balances_governance( &mut app, govmod_id, @@ -2010,7 +1493,6 @@ fn test_execute_expired_proposal() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -2212,7 +1694,6 @@ fn test_update_config() { allow_revoting: false, dao: Addr::unchecked(CREATOR_ADDR), deposit_info: None, - executor_addr: None, }; assert_eq!(govmod_config, expected); @@ -2299,7 +1780,7 @@ fn test_no_return_if_no_refunds() { #[test] fn test_query_list_proposals() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let gov_addr = instantiate_with_cw20_balances_governance( &mut app, govmod_id, @@ -2313,7 +1794,6 @@ fn test_query_list_proposals() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![Cw20Coin { address: CREATOR_ADDR.to_string(), @@ -2376,7 +1856,7 @@ fn test_query_list_proposals() { let expected = ProposalResponse { id: 1, - proposal: SingleChoiceProposal { + proposal: Proposal { title: "Text proposal 1.".to_string(), description: "This is a simple text proposal".to_string(), proposer: Addr::unchecked(CREATOR_ADDR), @@ -2393,9 +1873,6 @@ fn test_query_list_proposals() { status: Status::Open, votes: Votes::zero(), deposit_info: None, - created: app.block_info().time, - last_updated: app.block_info().time, - vetoed: false, }, }; assert_eq!(proposals_forward.proposals[0], expected); @@ -2424,7 +1901,7 @@ fn test_query_list_proposals() { let expected = ProposalResponse { id: 4, - proposal: SingleChoiceProposal { + proposal: Proposal { title: "Text proposal 4.".to_string(), description: "This is a simple text proposal".to_string(), proposer: Addr::unchecked(CREATOR_ADDR), @@ -2441,9 +1918,6 @@ fn test_query_list_proposals() { status: Status::Open, votes: Votes::zero(), deposit_info: None, - created: app.block_info().time, - last_updated: app.block_info().time, - vetoed: false, }, }; assert_eq!(proposals_forward.proposals[0], expected); @@ -2456,7 +1930,7 @@ fn test_query_list_proposals() { #[test] fn test_hooks() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -2469,7 +1943,6 @@ fn test_hooks() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; let governance_addr = @@ -2631,7 +2104,7 @@ fn test_hooks() { #[test] fn test_active_threshold_absolute() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -2644,7 +2117,6 @@ fn test_active_threshold_absolute() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; let governance_addr = instantiate_with_staking_active_threshold( @@ -2758,7 +2230,7 @@ fn test_active_threshold_absolute() { #[test] fn test_active_threshold_percent() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -2771,7 +2243,6 @@ fn test_active_threshold_percent() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; // 20% needed to be active, 20% of 100000000 is 20000000 @@ -2886,7 +2357,7 @@ fn test_active_threshold_percent() { #[test] fn test_active_threshold_none() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -2899,7 +2370,6 @@ fn test_active_threshold_none() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; let governance_addr = @@ -2979,7 +2449,6 @@ fn test_active_threshold_none() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; let governance_addr = @@ -3017,7 +2486,7 @@ fn test_active_threshold_none() { #[test] fn test_revoting() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_staked_balances_governance( &mut app, proposal_id, @@ -3031,7 +2500,6 @@ fn test_revoting() { only_members_execute: true, allow_revoting: true, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -3144,7 +2612,7 @@ fn test_revoting() { #[test] fn test_allow_revoting_config_changes() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_staked_balances_governance( &mut app, proposal_id, @@ -3158,7 +2626,6 @@ fn test_allow_revoting_config_changes() { only_members_execute: true, allow_revoting: true, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -3288,7 +2755,7 @@ fn test_allow_revoting_config_changes() { #[test] fn test_revoting_same_vote_twice() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_staked_balances_governance( &mut app, proposal_id, @@ -3302,7 +2769,6 @@ fn test_revoting_same_vote_twice() { only_members_execute: true, allow_revoting: true, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -3392,7 +2858,7 @@ fn test_revoting_same_vote_twice() { #[test] fn test_three_of_five_multisig() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_cw4_groups_governance( &mut app, proposal_id, @@ -3405,7 +2871,6 @@ fn test_three_of_five_multisig() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -3519,7 +2984,7 @@ fn test_three_of_five_multisig() { #[test] fn test_three_of_five_multisig_reject() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_cw4_groups_governance( &mut app, proposal_id, @@ -3532,7 +2997,6 @@ fn test_three_of_five_multisig_reject() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -3652,7 +3116,7 @@ fn test_three_of_five_multisig_reject() { #[should_panic] fn test_voting_module_token_with_multisig_style_voting() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); instantiate_with_cw4_groups_governance( &mut app, proposal_id, @@ -3669,7 +3133,6 @@ fn test_voting_module_token_with_multisig_style_voting() { deposit: Uint128::new(1), refund_failed_proposals: true, }), - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -3692,7 +3155,7 @@ fn test_voting_module_token_with_multisig_style_voting() { #[test] fn test_three_of_five_multisig_revoting() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_cw4_groups_governance( &mut app, proposal_id, @@ -3705,7 +3168,6 @@ fn test_three_of_five_multisig_revoting() { only_members_execute: true, allow_revoting: true, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -3926,7 +3388,8 @@ fn test_large_absolute_count_threshold() { #[test] fn test_migrate() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + // load custom netadao contract + let neta_govmod_id = app.store_code(neta_proposal_contract()); let threshold = Threshold::AbsolutePercentage { percentage: PercentageThreshold::Majority {}, @@ -3939,11 +3402,10 @@ fn test_migrate() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; - + // create dao with custom netadao contract let governance_addr = - instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None); + instantiate_with_cw20_balances_governance(&mut app, neta_govmod_id, instantiate, None); let governance_modules: Vec = app .wrap() .query_wasm_smart( @@ -3958,6 +3420,9 @@ fn test_migrate() { assert_eq!(governance_modules.len(), 1); let govmod_single = governance_modules.into_iter().next().unwrap(); + // load custom v1 contract to migrate into + let govmod_id = app.store_code(single_proposal_contract()); + let config: Config = app .wrap() .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) @@ -3968,7 +3433,7 @@ fn test_migrate() { CosmosMsg::Wasm(WasmMsg::Migrate { contract_addr: govmod_single.to_string(), new_code_id: govmod_id, - msg: to_binary(&MigrateMsg {}).unwrap(), + msg: to_binary(&MigrateMsg::NetaToV1 {}).unwrap(), }), ) .unwrap(); @@ -3978,13 +3443,16 @@ fn test_migrate() { .query_wasm_smart(govmod_single, &QueryMsg::Config {}) .unwrap(); + // confirm migration was successful assert_eq!(config, new_config); + + // TODO: test migration from new v1 to v2 } #[test] fn test_proposal_count_initialized_to_zero() { let mut app = App::default(); - let proposal_id = app.store_code(proposal_contract()); + let proposal_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_staked_balances_governance( &mut app, proposal_id, @@ -3998,7 +3466,6 @@ fn test_proposal_count_initialized_to_zero() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -4031,7 +3498,7 @@ fn test_proposal_count_initialized_to_zero() { #[test] fn test_no_early_pass_with_min_duration() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_staked_balances_governance( &mut app, govmod_id, @@ -4045,7 +3512,6 @@ fn test_no_early_pass_with_min_duration() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -4120,7 +3586,7 @@ fn test_no_early_pass_with_min_duration() { )] fn test_min_duration_units_missmatch() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); instantiate_with_staked_balances_governance( &mut app, govmod_id, @@ -4134,7 +3600,6 @@ fn test_min_duration_units_missmatch() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -4153,7 +3618,7 @@ fn test_min_duration_units_missmatch() { #[should_panic(expected = "Min voting period must be less than or equal to max voting period")] fn test_min_duration_larger_than_proposal_duration() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); instantiate_with_staked_balances_governance( &mut app, govmod_id, @@ -4167,7 +3632,6 @@ fn test_min_duration_larger_than_proposal_duration() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -4185,7 +3649,7 @@ fn test_min_duration_larger_than_proposal_duration() { #[test] fn test_min_duration_same_as_proposal_duration() { let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); + let govmod_id = app.store_code(single_proposal_contract()); let core_addr = instantiate_with_staked_balances_governance( &mut app, govmod_id, @@ -4199,7 +3663,6 @@ fn test_min_duration_same_as_proposal_duration() { only_members_execute: true, allow_revoting: false, deposit_info: None, - executor_addr: None, }, Some(vec![ Cw20Coin { @@ -4279,885 +3742,3 @@ fn test_min_duration_same_as_proposal_duration() { assert_eq!(proposal.proposal.status, Status::Passed); } - -#[test] -fn test_timestamp_updated() { - let mut app = App::default(); - let govmod_id = app.store_code(proposal_contract()); - - let threshold = Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Majority {}, - }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - threshold, - max_voting_period, - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - deposit_info: None, - executor_addr: None, - }; - - let governance_addr = instantiate_with_cw20_balances_governance( - &mut app, - govmod_id, - instantiate, - Some(vec![ - Cw20Coin { - address: "voter".to_string(), - amount: Uint128::new(3), - }, - Cw20Coin { - address: "voter2".to_string(), - amount: Uint128::new(2), - }, - ]), - ); - - let governance_modules: Vec = app - .wrap() - .query_wasm_smart( - governance_addr, - &cw_core::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // Create 2 proposals. - app.execute_contract( - Addr::unchecked("voter"), - govmod_single.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - msgs: vec![], - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("voter"), - govmod_single.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - msgs: vec![], - }, - &[], - ) - .unwrap(); - - let created_1: ProposalResponse = app - .wrap() - .query_wasm_smart( - govmod_single.clone(), - &QueryMsg::Proposal { proposal_id: 1 }, - ) - .unwrap(); - let current_block = app.block_info(); - - // Verify created and last updated - assert_eq!(created_1.proposal.created, current_block.time); - assert_eq!(created_1.proposal.last_updated, current_block.time); - - let created_2: ProposalResponse = app - .wrap() - .query_wasm_smart( - govmod_single.clone(), - &QueryMsg::Proposal { proposal_id: 2 }, - ) - .unwrap(); - - // Verify created and last updated - assert_eq!(created_2.proposal.created, current_block.time); - assert_eq!(created_2.proposal.last_updated, current_block.time); - - // Update block - let timestamp = Timestamp::from_seconds(300_000_000); - app.update_block(|block| block.time = timestamp); - - // Vote on proposal - app.execute_contract( - Addr::unchecked("voter"), - govmod_single.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: Vote::Yes, - }, - &[], - ) - .unwrap(); - - // Expect that last_updated changed because of status change - let updated: ProposalResponse = app - .wrap() - .query_wasm_smart( - govmod_single.clone(), - &QueryMsg::Proposal { proposal_id: 1 }, - ) - .unwrap(); - - assert_eq!(updated.proposal.last_updated, app.block_info().time); - assert_eq!(updated.proposal.status, Status::Passed); - - // Update block - let timestamp = Timestamp::from_seconds(500_000_000); - app.update_block(|block| block.time = timestamp); - let latest_time = app.block_info().time; - - // Execute proposal - app.execute_contract( - Addr::unchecked("voter"), - govmod_single.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Status should have changed to 'Executed' - let updated: ProposalResponse = app - .wrap() - .query_wasm_smart( - govmod_single.clone(), - &QueryMsg::Proposal { proposal_id: 1 }, - ) - .unwrap(); - - assert_eq!(updated.proposal.last_updated, latest_time); - assert_eq!(updated.proposal.status, Status::Executed); - - let timestamp = Timestamp::from_seconds(700_000_000); - app.update_block(|block| block.time = timestamp); - let latest_time = app.block_info().time; - - // Vote no on second proposal - app.execute_contract( - Addr::unchecked("voter"), - govmod_single.clone(), - &ExecuteMsg::Vote { - proposal_id: 2, - vote: Vote::No, - }, - &[], - ) - .unwrap(); - - // Status should have changed to 'Rejected' - let updated: ProposalResponse = app - .wrap() - .query_wasm_smart( - govmod_single.clone(), - &QueryMsg::Proposal { proposal_id: 2 }, - ) - .unwrap(); - - assert_eq!(updated.proposal.last_updated, latest_time); - assert_eq!(updated.proposal.status, Status::Rejected); - - let timestamp = Timestamp::from_seconds(900_000_000); - app.update_block(|block| block.time = timestamp); - let latest_time = app.block_info().time; - - // Close second proposal - app.execute_contract( - Addr::unchecked("voter"), - govmod_single.clone(), - &ExecuteMsg::Close { proposal_id: 2 }, - &[], - ) - .unwrap(); - - // Status should have changed to 'Closed' - let updated: ProposalResponse = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 2 }) - .unwrap(); - - assert_eq!(updated.proposal.last_updated, latest_time); - assert_eq!(updated.proposal.status, Status::Closed); -} - -#[test] -fn test_return_deposit_to_dao_on_proposal_failure() { - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: false, - }), - ); - - let core_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr.clone(), &cw_core::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = core_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let proposal_single = proposal_modules.into_iter().next().unwrap(); - - // Make the proposal expire. It has now failed. - app.update_block(|block| block.height += 10); - - // Close the proposal, this should work as the proposal is now - // open and expired. - app.execute_contract( - Addr::unchecked("keze"), - proposal_single.clone(), - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Check that a refund was issued. - let proposal_config: Config = app - .wrap() - .query_wasm_smart(proposal_single, &QueryMsg::Config {}) - .unwrap(); - let CheckedDepositInfo { token, .. } = proposal_config.deposit_info.unwrap(); - let balance: cw20::BalanceResponse = app - .wrap() - .query_wasm_smart( - token, - &cw20::Cw20QueryMsg::Balance { - address: core_addr.into_string(), - }, - ) - .unwrap(); - - // Deposit should now belong to the DAO. - assert_eq!(balance.balance, Uint128::new(1)); -} - -/// check gov, rando cant veto -#[test] -fn test_veto_no_executor() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - None, // executor_addr - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // dao cant veto - app.execute_contract( - governance_addr, - govmod_single.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - // rando cant veto - app.execute_contract( - Addr::unchecked("keze"), - govmod_single.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, None); -} - -/// double check rando, gov cant veto, but assigned address may -#[test] -fn test_veto_has_executor() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - Some("whiskey".to_string()), // executor_addr - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, Some(Addr::unchecked("whiskey"))); - - // dao cant execute veto - app.execute_contract( - governance_addr, - govmod_single.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - // rando cant execute - app.execute_contract( - Addr::unchecked("whiskeyfakeaddress"), - govmod_single.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - // executor can execute veto - app.execute_contract( - Addr::unchecked("whiskey"), - govmod_single.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = app - .wrap() - .query_wasm_smart( - govmod_single.clone(), - &QueryMsg::Proposal { proposal_id: 1 }, - ) - .unwrap(); - - assert_eq!(proposal.proposal.status, Status::Rejected); - assert!(proposal.proposal.vetoed); - - // no longer open - app.execute_contract( - Addr::unchecked("whiskey"), - govmod_single, - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err(); -} - -/// check rando cannot assign executor, but gov may -#[test] -fn test_veto_assign_executor() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - None, // executor_addr - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // check no executor established - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, None); - - // rando cant set - app.execute_contract( - Addr::unchecked("keze"), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { - address: Some("whiskey".to_string()), - }, - &[], - ) - .unwrap_err(); - - // success - app.execute_contract( - governance_addr, - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { - address: Some("whiskey".to_string()), - }, - &[], - ) - .unwrap(); - - // check config - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, Some(Addr::unchecked("whiskey"))); - - // veto prop - app.execute_contract( - Addr::unchecked("whiskey"), - govmod_single.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 1 }) - .unwrap(); - - assert_eq!(proposal.proposal.status, Status::Rejected); - assert!(proposal.proposal.vetoed); -} - -/// numerous checks on add/remove/reassign via gov -#[test] -fn test_executor_role_add_remove_executor_via_gov() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - None, // executor_addr - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // gov establish executor - app.execute_contract( - governance_addr.clone(), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { - address: Some("whiskey".to_string()), - }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, Some(Addr::unchecked("whiskey"))); - - // gov reassign executor - app.execute_contract( - governance_addr.clone(), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { - address: Some("whiskey2".to_string()), - }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, Some(Addr::unchecked("whiskey2"))); - - // first assignment cant execute veto - app.execute_contract( - Addr::unchecked("whiskey"), - govmod_single.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - // gov can unassign executor to None - app.execute_contract( - governance_addr, - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { address: None }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, None); - - // no longer veto - app.execute_contract( - Addr::unchecked("whiskey"), - govmod_single, - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err(); -} - -/// numerous checks on add/remove/reassign via self -#[test] -fn test_executor_role_add_remove_executor_self() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - Some("whiskey4".to_string()), // executor_addr - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // gov assign - app.execute_contract( - governance_addr, - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { - address: Some("whiskey5".to_string()), - }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, Some(Addr::unchecked("whiskey5"))); - - // executor reassign to someone else - app.execute_contract( - Addr::unchecked("whiskey5"), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { - address: Some("whiskey3".to_string()), - }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, Some(Addr::unchecked("whiskey3"))); - - // self unassign - app.execute_contract( - Addr::unchecked("whiskey3"), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { address: None }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, None); -} - -/// contract init with executor, self remove -#[test] -fn test_executor_role_self_remove_quick() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - Some("whiskey4".to_string()), // executor_addr - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(governance_addr, &cw_core::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // self unassign - app.execute_contract( - Addr::unchecked("whiskey4"), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { address: None }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, None); -} - -/// contract init with executor, gov remove -#[test] -fn test_executor_role_gov_remove_quick() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - Some("whiskey4".to_string()), // executor_addr - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // gov unassign - app.execute_contract( - governance_addr, - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { address: None }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, None); -} - -/// contract init with executor, self remove, added because rewrites made me nervous -#[test] -fn test_executor_role_no_init_() { - let (mut app, governance_addr) = do_test_votes_cw20_balances_with_executor( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(90)), - }, - Status::Open, - Some(Uint128::new(100)), - Some(DepositInfo { - token: DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - None, - ); - - let gov_state: cw_core::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &cw_core::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod_single = governance_modules.into_iter().next().unwrap(); - - // self rand unassign - app.execute_contract( - Addr::unchecked("whiskey4"), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { address: None }, - &[], - ) - .unwrap_err(); - - // gov unassign - app.execute_contract( - governance_addr.clone(), - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { address: None }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, None); - - // gov assign - app.execute_contract( - governance_addr, - govmod_single.clone(), - &ExecuteMsg::AssignExecutor { - address: Some("whiskey".to_string()), - }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(govmod_single, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(config.executor_addr, Some(Addr::unchecked("whiskey"))); -} diff --git a/contracts/cw-proposal-single/src/utils.rs b/contracts/cw-proposal-single/src/utils.rs new file mode 100644 index 000000000..a31b96b81 --- /dev/null +++ b/contracts/cw-proposal-single/src/utils.rs @@ -0,0 +1,53 @@ +use cosmwasm_std::{Addr, Deps, StdResult, Uint128}; + +use cw_core_interface::voting; +use cw_utils::Duration; + +use crate::ContractError; + +pub fn get_voting_power( + deps: Deps, + address: Addr, + dao: Addr, + height: Option, +) -> StdResult { + let response: voting::VotingPowerAtHeightResponse = deps.querier.query_wasm_smart( + dao, + &voting::Query::VotingPowerAtHeight { + address: address.to_string(), + height, + }, + )?; + Ok(response.power) +} + +pub fn get_total_power(deps: Deps, dao: Addr, height: Option) -> StdResult { + let response: voting::TotalPowerAtHeightResponse = deps + .querier + .query_wasm_smart(dao, &voting::Query::TotalPowerAtHeight { height })?; + Ok(response.power) +} + +/// Validates that the min voting period is less than the max voting +/// period. Passes arguments through the function. +pub fn validate_voting_period( + min: Option, + max: Duration, +) -> Result<(Option, Duration), ContractError> { + let min = min + .map(|min| { + let valid = match (min, max) { + (Duration::Time(min), Duration::Time(max)) => min <= max, + (Duration::Height(min), Duration::Height(max)) => min <= max, + _ => return Err(ContractError::DurationUnitsConflict {}), + }; + if valid { + Ok(min) + } else { + Err(ContractError::InvalidMinVotingPeriod {}) + } + }) + .transpose()?; + + Ok((min, max)) +} diff --git a/contracts/cw-proposal-single/src/v1_neta.rs b/contracts/cw-proposal-single/src/v1_neta.rs new file mode 100644 index 000000000..7bb5612ca --- /dev/null +++ b/contracts/cw-proposal-single/src/v1_neta.rs @@ -0,0 +1,59 @@ +use cw20::Expiration; +use cw_utils::Duration; +use voting::{status::Status, threshold::{PercentageThreshold, Threshold}, voting::Votes}; + +pub fn neta_percentage_threshold_to_v1( + neta: voting_v1::PercentageThreshold, +) -> PercentageThreshold { + match neta { + voting_v1::PercentageThreshold::Majority {} => PercentageThreshold::Majority {}, + voting_v1::PercentageThreshold::Percent(p) => PercentageThreshold::Percent(p), + } +} + +pub fn neta_threshold_to_v1(neta: voting_v1::Threshold) -> Threshold { + match neta { + voting_v1::Threshold::AbsolutePercentage { percentage } => Threshold::AbsolutePercentage { + percentage: neta_percentage_threshold_to_v1(percentage), + }, + voting_v1::Threshold::ThresholdQuorum { threshold, quorum } => Threshold::ThresholdQuorum { + threshold: neta_percentage_threshold_to_v1(threshold), + quorum: neta_percentage_threshold_to_v1(quorum), + }, + voting_v1::Threshold::AbsoluteCount { threshold } => Threshold::AbsoluteCount { threshold }, + } +} + +pub fn neta_duration_to_v1(neta: cw_utils::Duration) -> Duration { + match neta { + cw_utils::Duration::Height(height) => Duration::Height(height), + cw_utils::Duration::Time(time) => Duration::Time(time), + } +} + +pub fn neta_expiration_to_v1(v1: cw_utils::Expiration) -> Expiration { + match v1 { + cw_utils::Expiration::AtHeight(height) => Expiration::AtHeight(height), + cw_utils::Expiration::AtTime(time) => Expiration::AtTime(time), + cw_utils::Expiration::Never {} => Expiration::Never {}, + } +} + + +pub fn neta_status_to_v1(v1: voting_v1::Status) -> Status { + match v1 { + voting_v1::Status::Open => Status::Open, + voting_v1::Status::Rejected => Status::Rejected, + voting_v1::Status::Passed => Status::Passed, + voting_v1::Status::Executed => Status::Executed, + voting_v1::Status::Closed => Status::Closed, + } +} + +pub fn neta_votes_to_v1(v1: voting_v1::Votes) -> Votes { + Votes { + yes: v1.yes, + no: v1.no, + abstain: v1.abstain, + } +} \ No newline at end of file diff --git a/debug/proposal-hooks-counter/src/tests.rs b/debug/proposal-hooks-counter/src/tests.rs index 7f32ec5ff..18a7ca9bd 100644 --- a/debug/proposal-hooks-counter/src/tests.rs +++ b/debug/proposal-hooks-counter/src/tests.rs @@ -143,7 +143,6 @@ fn test_counters() { only_members_execute: false, allow_revoting: false, deposit_info: None, - executor_addr: None, }; let governance_addr = diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 000000000..5959d9085 --- /dev/null +++ b/migration/README.md @@ -0,0 +1,41 @@ +# NETADAO + +## Step 1: re-upload contracts +to get the dao contracts being used: +``` +junod config node https://juno-rpc.lavenderfive.com:443 +junod q wasm code 627 ./current-contracts/cw-proposal-single.wasm +junod q wasm code 431 ./current-contracts/cw20-staked-balance-voting.wasm +junod q wasm code 432 ./current-contracts/cw-core.wasm +junod q wasm code 430 ./current-contracts/cw20-stake.wasm +junod q wasm code 1 ./current-contracts/cw20.wasm +junod q wasm code 429 ./current-contracts/cw4-voting.wasm +``` +to get the v1 contracts needed: +``` +# build custom proposal contract for migration +cd ../ +# ... + +``` +to get the v2 contracts needed: +```sh +wget https://github.com/DA0-DA0/dao-contracts/releases/download/v2.4.2/cw20_stake.wasm +wget https://github.com/DA0-DA0/dao-contracts/releases/download/v2.4.2/dao_dao_core.wasm +wget https://github.com/DA0-DA0/dao-contracts/releases/download/v2.4.2/dao_migrator.wasm +wget https://github.com/DA0-DA0/dao-contracts/releases/download/v2.4.2/dao_pre_propose_single.wasm +wget https://github.com/DA0-DA0/dao-contracts/releases/download/v2.4.2/dao_proposal_single.wasm +wget https://github.com/DA0-DA0/dao-contracts/releases/download/v2.4.2/dao_voting_cw20_staked.wasm +wget https://github.com/DA0-DA0/dao-contracts/releases/download/v2.4.2/dao_voting_cw4.wasm +``` +## Step 2: re-create Neta DAO instance +to recreate the dao, we need the dao framework as well as an external cw20 token created + +## Step 3: migrate module back to v1 +this is the migration on the custom proposal contract that restores compatiblility with the v1 -> v2 migration workflow dao-dao has implemented. This can be done through governance, or by the contract admin. + +## Step 5: migrate dao framework from v1 -> v2 + +## Syntax Bug: Incosistent internal proposal-id syntaxt for pre-propose & proposal contracts +Testing proposals post v1-> v2 migration revealed logs emitted from the pre-propose module containing a proposal id incosistent with the correct proposal id. +This syntax issue is only introduced during the first proposal after migration. All future pre-proposal logs will contain the correct value. \ No newline at end of file diff --git a/migration/current-contracts/readme.md b/migration/current-contracts/readme.md new file mode 100644 index 000000000..c16ab832d --- /dev/null +++ b/migration/current-contracts/readme.md @@ -0,0 +1 @@ +# Custom NETA DAO Contracts \ No newline at end of file diff --git a/migration/new-contracts/readme.md b/migration/new-contracts/readme.md new file mode 100644 index 000000000..3689adf90 --- /dev/null +++ b/migration/new-contracts/readme.md @@ -0,0 +1 @@ +# Original V1 Contracts \ No newline at end of file diff --git a/migration/setup.sh b/migration/setup.sh new file mode 100644 index 000000000..a58c54a30 --- /dev/null +++ b/migration/setup.sh @@ -0,0 +1,718 @@ +# Workflow +admin_key= # main key for tx +admin_addr= +bid_key= # secondary key for complexity in proposers +bid_addr= +binary=junod +denom=ujunox # primary token +tx_flag="--from $admin_key --gas auto --gas-adjustment 2 --gas-prices 0.05$denom -y -o json" # transaction flags for admin_key +tx_flag2="--from $bid_key --gas auto --gas-adjustment 2 --gas-prices 0.05$denom -y -o json" # transaction flags for bid_key + +################## Store All Contracts ################## +### First, run `sh upload.sh` to store all of the contracts required. +### Then, populate the correct ids for this scripts contract variables. +## Old NetaDAO Contracts +cw_core_code_id=4509 +cw_proposal_single_code_id=4510 +cw20_stake_code_id=4511 +cw20_staked_balance_voting_code_id=4512 +cw20_code_id=4513 +cw4_voting=4514 +## Custom Migration V1 Contracts +compatible_proposal_code_id=4515 +## New V2 Contracts +v2_cw20_stake_code_id=4516 +v2_dao_code_id=4517 +v2_migrator_code_id=4518 +v2_pre_propose_code_id=4519 +v2_proposal_single_code_id=4520 +v2_cw20_staked_balances_voting_code_id=4521 +v2_cw4_voting_code_id=4522 + +################## 1.Fund 2nd account ################## +BAL_MSG=$(cat < v1 ", + "description":"migrate proposal", + "msgs": [ + { + "wasm": { + "migrate": { + "contract_addr": "$proposal_addr", + "msg": "$binary_migrate_msg", + "new_code_id": $compatible_proposal_code_id + } + } + } + ] + } +} +EOF +) +echo "Create Neta -> V1 Proposal" +migrate_proposal_response='$binary tx wasm e $proposal_addr "$PROP_MSG" '$tx_flag2'' +migrate_prop_res=$(eval $migrate_proposal_response); +if [ -n "$migrate_prop_res" ]; then + txhash=$(echo "$migrate_prop_res" | jq -r '.txhash') + echo 'waiting for tx to process' + echo 'finished with txhash: '$txhash'' + sleep 6; + tx_response=$($binary q tx $txhash -o json) + # echo 'finished with tx_response: '$tx_response'' +else + echo "Error: Empty response" +fi + +VOTE_MSG=$(cat < V1 Proposal as Admin ##################" +vote_msg_response='$binary tx wasm e $proposal_addr "$VOTE_MSG" '$tx_flag'' +vote_res=$(eval $vote_msg_response); +if [ -n "$vote_res" ]; then + txhash=$(echo "$vote_res" | jq -r '.txhash') + echo 'waiting for tx to process' + echo 'finished with txhash: '$txhash'' + sleep 6; + tx_response=$($binary q tx $txhash -o json) + # echo 'finished with tx_response: '$tx_response'' +else + echo "Error: Empty response" +fi +echo "############ Vote On Neta -> V1 Proposal as Bid ##################" +vote_msg_response='$binary tx wasm e $proposal_addr "$VOTE_MSG" '$tx_flag2'' +vote_res=$(eval $vote_msg_response); +if [ -n "$vote_res" ]; then + txhash=$(echo "$vote_res" | jq -r '.txhash') + echo 'waiting for tx to process' + echo 'finished with txhash: '$txhash'' + sleep 6; + tx_response=$($binary q tx $txhash -o json) + # echo 'finished with tx_response: '$tx_response'' +else + echo "Error: Empty response" +fi +############ 7. Execute Neta -> V1 Migration ################## +echo "############ Execute Neta -> V1 Migration ##################" +EXECUTE_MSG=$(cat < V2 As Admin ##################" +MIGRATE=$(cat < V2 Migration Worked" +confirm_migrate_query='$binary q wasm contract-state smart $dao_addr "$DUMP_STATE" -o json' +migrate_res=$(eval $confirm_migrate_query); +version=$(echo $migrate_res | jq -r '.data.version.version') +echo 'DAO contract current version: '$version'' + +prop_query='$binary q wasm contract-state smart $proposal_addr "$CREATION_POLICY" -o json' +prop_query_res=$(eval $prop_query); +pre_propose_addr=$(echo $prop_query_res | jq -r '.data.module.addr') +echo 'pre_propose_addr: '$pre_propose_addr'' + +echo "Fund DAO" +fund_dao='$binary tx bank send $admin_addr $dao_addr 200'$denom' '$tx_flag'' +fund_res=$(eval $fund_dao); +fund_txhash=$(echo "$fund_res" | jq -r '.txhash') +echo $fund_txhash +sleep 6; + +echo "Confirm Some Balance" +confirm_empty_balance_query='$binary q bank balances $dao_addr -o json' +bal_query1=$(eval $confirm_empty_balance_query); +echo $bal_query1 + +echo "Setup Allowance Msg" +NEW_ALLOWANCE=$(cat <