From 9616c9dab4d372873901154072f779be8da02b0e Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 18 Apr 2023 15:09:57 +0300 Subject: [PATCH 1/5] Investors Vesting Vault --- Cargo.lock | 56 +- contracts/dao/cwd-core/schema/query_msg.json | 21 + .../investors-vesting-vault/.cargo/config | 4 + .../voting/investors-vesting-vault/.gitignore | 15 + .../voting/investors-vesting-vault/Cargo.toml | 37 ++ .../voting/investors-vesting-vault/README.md | 3 + .../examples/schema.rs | 34 ++ .../schema/claims_response.json | 98 ++++ .../schema/dao_response.json | 6 + .../schema/execute_msg.json | 44 ++ .../schema/get_config_response.json | 37 ++ .../schema/info_response.json | 32 ++ .../schema/instantiate_msg.json | 68 +++ .../schema/is_active_response.json | 13 + .../schema/migrate_msg.json | 5 + .../schema/query_msg.json | 104 ++++ .../total_power_at_height_response.json | 25 + .../voting_power_at_height_response.json | 25 + .../investors-vesting-vault/src/contract.rs | 216 ++++++++ .../investors-vesting-vault/src/error.rs | 14 + .../voting/investors-vesting-vault/src/lib.rs | 9 + .../voting/investors-vesting-vault/src/msg.rs | 40 ++ .../investors-vesting-vault/src/state.rs | 16 + .../investors-vesting-vault/src/tests.rs | 486 ++++++++++++++++++ .../lockdrop-vault/schema/execute_msg.json | 8 +- .../schema/get_config_response.json | 8 +- .../schema/instantiate_msg.json | 11 +- .../schema/proposal_list_response.json | 11 +- 28 files changed, 1428 insertions(+), 18 deletions(-) create mode 100644 contracts/dao/voting/investors-vesting-vault/.cargo/config create mode 100644 contracts/dao/voting/investors-vesting-vault/.gitignore create mode 100644 contracts/dao/voting/investors-vesting-vault/Cargo.toml create mode 100644 contracts/dao/voting/investors-vesting-vault/README.md create mode 100644 contracts/dao/voting/investors-vesting-vault/examples/schema.rs create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/claims_response.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/dao_response.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/info_response.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/is_active_response.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/migrate_msg.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/query_msg.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/total_power_at_height_response.json create mode 100644 contracts/dao/voting/investors-vesting-vault/schema/voting_power_at_height_response.json create mode 100644 contracts/dao/voting/investors-vesting-vault/src/contract.rs create mode 100644 contracts/dao/voting/investors-vesting-vault/src/error.rs create mode 100644 contracts/dao/voting/investors-vesting-vault/src/lib.rs create mode 100644 contracts/dao/voting/investors-vesting-vault/src/msg.rs create mode 100644 contracts/dao/voting/investors-vesting-vault/src/state.rs create mode 100644 contracts/dao/voting/investors-vesting-vault/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 6b83d661..9425e0bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,19 @@ dependencies = [ "uint", ] +[[package]] +name = "astroport" +version = "2.0.0" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git#f4827b3ca1c56b203623c92b6b244040fc81b05e" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw20 0.15.1", + "itertools", + "uint", +] + [[package]] name = "astroport" version = "2.7.1" @@ -1330,6 +1343,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "investors-vesting-vault" +version = "0.2.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers 1.0.1", + "cw-multi-test 0.16.2", + "cw-paginate 0.2.0", + "cw-storage-plus 1.0.1", + "cw-utils 1.0.1", + "cw2 0.13.4", + "cw20 1.0.1", + "cwd-interface", + "cwd-macros", + "schemars", + "serde", + "thiserror", + "vesting-base", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1368,7 +1404,7 @@ name = "lockdrop-vault" version = "0.1.0" dependencies = [ "anyhow", - "astroport 2.0.0", + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "astroport-periphery", "cosmwasm-schema", "cosmwasm-std", @@ -1412,7 +1448,7 @@ dependencies = [ name = "neutron-lockdrop-vault" version = "0.1.0" dependencies = [ - "astroport 2.0.0", + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "astroport-periphery", "cosmwasm-std", "cwd-interface", @@ -2078,6 +2114,22 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vesting-base" +version = "1.1.0" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git#f4827b3ca1c56b203623c92b6b244040fc81b05e" +dependencies = [ + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "cw20 0.15.1", + "serde", + "thiserror", +] + [[package]] name = "vote-hooks" version = "0.1.0" diff --git a/contracts/dao/cwd-core/schema/query_msg.json b/contracts/dao/cwd-core/schema/query_msg.json index 2692c274..7e410b64 100644 --- a/contracts/dao/cwd-core/schema/query_msg.json +++ b/contracts/dao/cwd-core/schema/query_msg.json @@ -191,6 +191,27 @@ }, "additionalProperties": false }, + { + "description": "Returns the SubDAO for a specific address if it in the list", + "type": "object", + "required": [ + "get_sub_dao" + ], + "properties": { + "get_sub_dao": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, { "description": "Implements the DAO Star standard: https://daostar.one/EIP", "type": "object", diff --git a/contracts/dao/voting/investors-vesting-vault/.cargo/config b/contracts/dao/voting/investors-vesting-vault/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/dao/voting/investors-vesting-vault/.gitignore b/contracts/dao/voting/investors-vesting-vault/.gitignore new file mode 100644 index 00000000..dfdaaa6b --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/.gitignore @@ -0,0 +1,15 @@ +# Build results +/target + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/dao/voting/investors-vesting-vault/Cargo.toml b/contracts/dao/voting/investors-vesting-vault/Cargo.toml new file mode 100644 index 00000000..ca1fcc92 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "investors-vesting-vault" +version = "0.2.0" +authors = ["Callum Anderson ", "Mikhail Mozhaev "] +edition = "2021" +repository = "https://github.com/neutron-org/neutron-dao" +description = "A DAO vault contract." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { version = "1.1.3" } +cosmwasm-std = { version = "1.1.3" } +cosmwasm-storage = { version = "1.1.3" } +cw-storage-plus = "1.0.1" +cw2 = "0.13" +cw20 = { version = "1.0.1" } +cw-utils = "1.0.1" +cw-controllers = "1.0.1" +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cwd-macros = { path = "../../../../packages/cwd-macros" } +cwd-interface = { path = "../../../../packages/cwd-interface" } +cw-paginate = { path = "../../../../packages/cw-paginate" } +vesting-base = { git = "https://github.com/neutron-org/neutron-tge-contracts.git" } + +[dev-dependencies] +cw-multi-test = "0.16.2" +anyhow = "1.0.57" diff --git a/contracts/dao/voting/investors-vesting-vault/README.md b/contracts/dao/voting/investors-vesting-vault/README.md new file mode 100644 index 00000000..b7003d7f --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/README.md @@ -0,0 +1,3 @@ +### Neutron Vesting Vault + +This vault will allow its users to query voting power represented by NTRN tokens in the vesting contracts. Just as with normal DAO DAO voting modules, for each specific proposal, you can only use the voting power that was available to you at the time of proposal submission. No additional restrictions are imposed on the vault funds. diff --git a/contracts/dao/voting/investors-vesting-vault/examples/schema.rs b/contracts/dao/voting/investors-vesting-vault/examples/schema.rs new file mode 100644 index 00000000..323dddfa --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/examples/schema.rs @@ -0,0 +1,34 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Addr; +use investors_vesting_vault::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use investors_vesting_vault::state::Config; +use cw_controllers::ClaimsResponse; +use cwd_interface::voting::{ + InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); + + export_schema(&schema_for!(InfoResponse), &out_dir); + export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(VotingPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(IsActiveResponse), &out_dir); + export_schema(&schema_for!(ClaimsResponse), &out_dir); + + // Auto TS code generation expects the query return type as QueryNameResponse + // Here we map query resonses to the correct name + export_schema_with_title(&schema_for!(Addr), &out_dir, "DaoResponse"); + export_schema_with_title(&schema_for!(Config), &out_dir, "GetConfigResponse"); +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/claims_response.json b/contracts/dao/voting/investors-vesting-vault/schema/claims_response.json new file mode 100644 index 00000000..2f6bdb96 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/claims_response.json @@ -0,0 +1,98 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ClaimsResponse", + "type": "object", + "required": [ + "claims" + ], + "properties": { + "claims": { + "type": "array", + "items": { + "$ref": "#/definitions/Claim" + } + } + }, + "additionalProperties": false, + "definitions": { + "Claim": { + "type": "object", + "required": [ + "amount", + "release_at" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "release_at": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/dao_response.json b/contracts/dao/voting/investors-vesting-vault/schema/dao_response.json new file mode 100644 index 00000000..9518ba3b --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/dao_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoResponse", + "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" +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json b/contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json new file mode 100644 index 00000000..0d286a5c --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "manager": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "type": "string" + }, + "vesting_contract_address": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json b/contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json new file mode 100644 index 00000000..803fc4e8 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetConfigResponse", + "type": "object", + "required": [ + "description", + "owner", + "vesting_contract_address" + ], + "properties": { + "description": { + "type": "string" + }, + "manager": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "vesting_contract_address": { + "$ref": "#/definitions/Addr" + } + }, + "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" + } + } +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/info_response.json b/contracts/dao/voting/investors-vesting-vault/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json b/contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json new file mode 100644 index 00000000..b8ebf93f --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "description", + "owner", + "vesting_contract_address" + ], + "properties": { + "description": { + "type": "string" + }, + "manager": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "$ref": "#/definitions/Admin" + }, + "vesting_contract_address": { + "type": "string" + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/is_active_response.json b/contracts/dao/voting/investors-vesting-vault/schema/is_active_response.json new file mode 100644 index 00000000..5275d891 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/is_active_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsActiveResponse", + "type": "object", + "required": [ + "active" + ], + "properties": { + "active": { + "type": "boolean" + } + } +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/migrate_msg.json b/contracts/dao/voting/investors-vesting-vault/schema/migrate_msg.json new file mode 100644 index 00000000..87b18ea7 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/migrate_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object" +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/query_msg.json b/contracts/dao/voting/investors-vesting-vault/schema/query_msg.json new file mode 100644 index 00000000..a1371f36 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/query_msg.json @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_config" + ], + "properties": { + "get_config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/total_power_at_height_response.json b/contracts/dao/voting/investors-vesting-vault/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/investors-vesting-vault/schema/voting_power_at_height_response.json b/contracts/dao/voting/investors-vesting-vault/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/investors-vesting-vault/src/contract.rs b/contracts/dao/voting/investors-vesting-vault/src/contract.rs new file mode 100644 index 00000000..32ae41f1 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/src/contract.rs @@ -0,0 +1,216 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, +}; +use cw2::set_contract_version; +use cwd_interface::voting::{TotalPowerAtHeightResponse, VotingPowerAtHeightResponse}; +use cwd_interface::Admin; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::{Config, CONFIG, DAO, DESCRIPTION}; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-vesting-vault"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let owner = match msg.owner { + Admin::Address { addr } => deps.api.addr_validate(addr.as_str())?, + Admin::CoreModule {} => info.sender.clone(), + }; + let manager = msg + .manager + .map(|manager| deps.api.addr_validate(&manager)) + .transpose()?; + + let vesting_contract_address = deps.api.addr_validate(&msg.vesting_contract_address)?; + + let config = Config { + vesting_contract_address, + description: msg.description, + owner, + manager, + }; + + CONFIG.save(deps.storage, &config)?; + DAO.save(deps.storage, &info.sender)?; + + Ok(Response::new() + .add_attribute("action", "instantiate") + .add_attribute("description", config.description) + .add_attribute("vesting_contract_address", config.vesting_contract_address) + .add_attribute("owner", config.owner) + .add_attribute( + "manager", + config + .manager + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + )) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::UpdateConfig { + vesting_contract_address, + owner, + manager, + description, + } => execute_update_config( + deps, + info, + vesting_contract_address, + owner, + manager, + description, + ), + } +} + +pub fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + new_vesting_contract_address: Option, + new_owner: String, + new_manager: Option, + new_description: Option, +) -> Result { + let mut config: Config = CONFIG.load(deps.storage)?; + if info.sender != config.owner && Some(info.sender.clone()) != config.manager { + return Err(ContractError::Unauthorized {}); + } + + let new_vesting_contract_address = new_vesting_contract_address + .map(|new_vesting_contract_address| deps.api.addr_validate(&new_vesting_contract_address)) + .transpose()?; + + let new_owner = deps.api.addr_validate(&new_owner)?; + let new_manager = new_manager + .map(|new_manager| deps.api.addr_validate(&new_manager)) + .transpose()?; + + if info.sender != config.owner && new_owner != config.owner { + return Err(ContractError::OnlyOwnerCanChangeOwner {}); + }; + + config.owner = new_owner; + config.manager = new_manager; + if let Some(description) = new_description { + config.description = description; + } + if let Some(new_vesting_contract_address) = new_vesting_contract_address { + config.vesting_contract_address = new_vesting_contract_address; + } + + CONFIG.save(deps.storage, &config)?; + Ok(Response::new() + .add_attribute("action", "update_config") + .add_attribute("description", config.description) + .add_attribute("vesting_contract_address", config.vesting_contract_address) + .add_attribute("owner", config.owner) + .add_attribute( + "manager", + config + .manager + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + )) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::VotingPowerAtHeight { address, height } => { + to_binary(&query_voting_power_at_height(deps, env, address, height)?) + } + QueryMsg::TotalPowerAtHeight { height } => { + to_binary(&query_total_power_at_height(deps, env, height)?) + } + QueryMsg::Info {} => query_info(deps), + QueryMsg::Dao {} => query_dao(deps), + QueryMsg::Description {} => query_description(deps), + QueryMsg::GetConfig {} => to_binary(&CONFIG.load(deps.storage)?), + } +} + +pub fn query_voting_power_at_height( + deps: Deps, + env: Env, + address: String, + height: Option, +) -> StdResult { + let height = height.unwrap_or(env.block.height); + + let config = CONFIG.load(deps.storage)?; + + let unclaimed_amount: Uint128 = deps.querier.query_wasm_smart( + config.vesting_contract_address, + &vesting_base::msg::QueryMsg::HistoricalExtension { + msg: vesting_base::msg::QueryMsgHistorical::UnclaimedAmountAtHeight { address, height }, + }, + )?; + + Ok(VotingPowerAtHeightResponse { + power: unclaimed_amount, + height, + }) +} + +pub fn query_total_power_at_height( + deps: Deps, + env: Env, + height: Option, +) -> StdResult { + let height = height.unwrap_or(env.block.height); + + let config = CONFIG.load(deps.storage)?; + + let unclaimed_amount_total: Uint128 = deps.querier.query_wasm_smart( + config.vesting_contract_address, + &vesting_base::msg::QueryMsg::HistoricalExtension { + msg: vesting_base::msg::QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height }, + }, + )?; + + Ok(TotalPowerAtHeightResponse { + power: unclaimed_amount_total, + height, + }) +} + +pub fn query_info(deps: Deps) -> StdResult { + let info = cw2::get_contract_version(deps.storage)?; + to_binary(&cwd_interface::voting::InfoResponse { info }) +} + +pub fn query_dao(deps: Deps) -> StdResult { + let dao = DAO.load(deps.storage)?; + to_binary(&dao) +} + +pub fn query_description(deps: Deps) -> StdResult { + let description = DESCRIPTION.load(deps.storage)?; + to_binary(&description) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) +} diff --git a/contracts/dao/voting/investors-vesting-vault/src/error.rs b/contracts/dao/voting/investors-vesting-vault/src/error.rs new file mode 100644 index 00000000..81f3393f --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/src/error.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Only owner can change owner")] + OnlyOwnerCanChangeOwner {}, +} diff --git a/contracts/dao/voting/investors-vesting-vault/src/lib.rs b/contracts/dao/voting/investors-vesting-vault/src/lib.rs new file mode 100644 index 00000000..4f75f883 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/src/lib.rs @@ -0,0 +1,9 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +#[cfg(test)] +pub mod tests; + +pub use crate::error::ContractError; diff --git a/contracts/dao/voting/investors-vesting-vault/src/msg.rs b/contracts/dao/voting/investors-vesting-vault/src/msg.rs new file mode 100644 index 00000000..8253361b --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/src/msg.rs @@ -0,0 +1,40 @@ +use cwd_interface::Admin; +use cwd_macros::{info_query, voting_query}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +pub struct InstantiateMsg { + // Vesting contract address. + pub vesting_contract_address: String, + // Description contains information that characterizes the vault. + pub description: String, + // Owner can update all configs including changing the owner. This will generally be a DAO. + pub owner: Admin, + // Manager can update all configs except changing the owner. This will generally be an operations multisig for a DAO. + pub manager: Option, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + UpdateConfig { + vesting_contract_address: Option, + owner: String, + manager: Option, + description: Option, + }, +} + +#[voting_query] +#[info_query] +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Dao {}, + Description {}, + GetConfig {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MigrateMsg {} diff --git a/contracts/dao/voting/investors-vesting-vault/src/state.rs b/contracts/dao/voting/investors-vesting-vault/src/state.rs new file mode 100644 index 00000000..966b5167 --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/src/state.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct Config { + pub vesting_contract_address: Addr, + pub description: String, + pub owner: Addr, + pub manager: Option, +} + +pub const CONFIG: Item = Item::new("config"); +pub const DAO: Item = Item::new("dao"); +pub const DESCRIPTION: Item = Item::new("description"); diff --git a/contracts/dao/voting/investors-vesting-vault/src/tests.rs b/contracts/dao/voting/investors-vesting-vault/src/tests.rs new file mode 100644 index 00000000..e89c580c --- /dev/null +++ b/contracts/dao/voting/investors-vesting-vault/src/tests.rs @@ -0,0 +1,486 @@ +use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::Config; +use crate::ContractError; +use cosmwasm_std::testing::{mock_dependencies, mock_env}; +use cosmwasm_std::{to_binary, Addr, Binary, Deps, Empty, Env, Response, StdResult, Uint128}; +use cw_multi_test::{custom_app, App, AppResponse, Contract, ContractWrapper, Executor}; +use cwd_interface::voting::{ + InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use cwd_interface::Admin; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +const DAO_ADDR: &str = "dao"; +const DESCRIPTION: &str = "description"; +const NEW_DESCRIPTION: &str = "new description"; +const ADDR1: &str = "addr1"; +const ADDR2: &str = "addr2"; + +fn vault_contract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) +} + +fn vesting_query(_deps: Deps, _env: Env, msg: vesting_base::msg::QueryMsg) -> StdResult { + match msg { + vesting_base::msg::QueryMsg::HistoricalExtension { + msg: + vesting_base::msg::QueryMsgHistorical::UnclaimedAmountAtHeight { + address: _, + height: _, + }, + } => { + let response = Uint128::from(10000u64); + to_binary(&response) + } + vesting_base::msg::QueryMsg::HistoricalExtension { + msg: vesting_base::msg::QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: _ }, + } => { + let response = Uint128::from(10000u64); + to_binary(&response) + } + _ => unimplemented!(), + } +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub struct EmptyMsg {} + +fn vesting_contract() -> Box> { + let contract: ContractWrapper< + EmptyMsg, + EmptyMsg, + vesting_base::msg::QueryMsg, + ContractError, + ContractError, + cosmwasm_std::StdError, + > = ContractWrapper::new( + |_, _, _, _: EmptyMsg| Ok(Response::new()), + |_, _, _, _: EmptyMsg| Ok(Response::new()), + vesting_query, + ); + Box::new(contract) +} + +fn instantiate_vesting_contract(app: &mut App) -> Addr { + let contract_id = app.store_code(vesting_contract()); + app.instantiate_contract( + contract_id, + Addr::unchecked(DAO_ADDR), + &EmptyMsg {}, + &[], + "vesting contract", + None, + ) + .unwrap() +} + +fn mock_app() -> App { + custom_app(|_r, _a, _s| {}) +} + +fn instantiate_vault(app: &mut App, id: u64, msg: InstantiateMsg) -> Addr { + app.instantiate_contract(id, Addr::unchecked(DAO_ADDR), &msg, &[], "vault", None) + .unwrap() +} + +fn update_config( + app: &mut App, + contract_addr: Addr, + sender: &str, + vesting_contract_address: Option, + owner: String, + manager: Option, + description: Option, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::UpdateConfig { + vesting_contract_address, + owner, + manager, + description, + }, + &[], + ) +} + +fn get_voting_power_at_height( + app: &mut App, + contract_addr: Addr, + address: String, + height: Option, +) -> VotingPowerAtHeightResponse { + app.wrap() + .query_wasm_smart( + contract_addr, + &QueryMsg::VotingPowerAtHeight { address, height }, + ) + .unwrap() +} + +fn get_total_power_at_height( + app: &mut App, + contract_addr: Addr, + height: Option, +) -> TotalPowerAtHeightResponse { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::TotalPowerAtHeight { height }) + .unwrap() +} + +fn get_config(app: &mut App, contract_addr: Addr) -> Config { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::GetConfig {}) + .unwrap() +} + +#[test] +fn test_instantiate() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + // Populated fields + let _addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::Address { + addr: DAO_ADDR.to_string(), + }, + manager: Some(ADDR1.to_string()), + }, + ); + + // Non populated fields + let _addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::Address { + addr: DAO_ADDR.to_string(), + }, + manager: None, + }, + ); +} + +#[test] +fn test_instantiate_dao_owner() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + // Populated fields + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + let config = get_config(&mut app, addr); + + assert_eq!(config.owner, Addr::unchecked(DAO_ADDR)) +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_update_config_invalid_sender() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + // From ADDR2, so not owner or manager + update_config( + &mut app, + addr, + ADDR2, + Some(vesting_contract.to_string()), + ADDR1.to_string(), + Some(DAO_ADDR.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Only owner can change owner")] +fn test_update_config_non_owner_changes_owner() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + // ADDR1 is the manager so cannot change the owner + update_config( + &mut app, + addr, + ADDR1, + Some(vesting_contract.to_string()), + ADDR2.to_string(), + None, + None, + ) + .unwrap(); +} + +#[test] +fn test_update_config_as_owner() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + // Swap owner and manager, change description + update_config( + &mut app, + addr.clone(), + DAO_ADDR, + Some(vesting_contract.to_string()), + ADDR1.to_string(), + Some(DAO_ADDR.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); + + let config = get_config(&mut app, addr); + assert_eq!( + Config { + vesting_contract_address: Addr::unchecked(vesting_contract), + description: NEW_DESCRIPTION.to_string(), + owner: Addr::unchecked(ADDR1), + manager: Some(Addr::unchecked(DAO_ADDR)), + }, + config + ); +} + +#[test] +fn test_update_config_as_manager() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + // Change description and manager as manager cannot change owner + update_config( + &mut app, + addr.clone(), + ADDR1, + Some(vesting_contract.to_string()), + DAO_ADDR.to_string(), + Some(ADDR2.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); + + let config = get_config(&mut app, addr); + assert_eq!( + Config { + vesting_contract_address: Addr::unchecked(vesting_contract), + description: NEW_DESCRIPTION.to_string(), + owner: Addr::unchecked(DAO_ADDR), + manager: Some(Addr::unchecked(ADDR2)), + }, + config + ); +} + +#[test] +#[should_panic(expected = "Empty attribute value. Key: description")] +fn test_update_config_invalid_description() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + // Change duration and manager as manager cannot change owner + update_config( + &mut app, + addr, + ADDR1, + Some(vesting_contract.to_string()), + DAO_ADDR.to_string(), + Some(ADDR2.to_string()), + Some(String::from("")), + ) + .unwrap(); +} + +#[test] +fn test_query_dao() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + let msg = QueryMsg::Dao {}; + let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(dao, Addr::unchecked(DAO_ADDR)); +} + +#[test] +fn test_query_info() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + let msg = QueryMsg::Info {}; + let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(resp.info.contract, "crates.io:neutron-vesting-vault"); +} + +#[test] +fn test_query_get_config() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + let config = get_config(&mut app, addr); + assert_eq!( + config, + Config { + vesting_contract_address: Addr::unchecked(vesting_contract), + description: DESCRIPTION.to_string(), + owner: Addr::unchecked(DAO_ADDR), + manager: Some(Addr::unchecked(ADDR1)), + } + ) +} + +#[test] +fn test_voting_power_queries() { + let mut app = mock_app(); + let vesting_contract = instantiate_vesting_contract(&mut app); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + vesting_contract_address: vesting_contract.to_string(), + description: DESCRIPTION.to_string(), + owner: Admin::CoreModule {}, + manager: Some(ADDR1.to_string()), + }, + ); + + // Total power is 0 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert_eq!(Uint128::from(10000u64), resp.power); + + // ADDR1 has no power, none bonded + let resp = get_voting_power_at_height(&mut app, addr, ADDR1.to_string(), None); + assert_eq!(Uint128::from(10000u64), resp.power); +} + +#[test] +pub fn test_migrate_update_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); + let version = cw2::get_contract_version(&deps.storage).unwrap(); + assert_eq!(version.version, CONTRACT_VERSION); + assert_eq!(version.contract, CONTRACT_NAME); +} diff --git a/contracts/dao/voting/lockdrop-vault/schema/execute_msg.json b/contracts/dao/voting/lockdrop-vault/schema/execute_msg.json index 3c5ea8bb..5be9b706 100644 --- a/contracts/dao/voting/lockdrop-vault/schema/execute_msg.json +++ b/contracts/dao/voting/lockdrop-vault/schema/execute_msg.json @@ -35,7 +35,13 @@ "null" ] }, - "oracle_contract": { + "oracle_atom_contract": { + "type": [ + "string", + "null" + ] + }, + "oracle_usdc_contract": { "type": [ "string", "null" diff --git a/contracts/dao/voting/lockdrop-vault/schema/get_config_response.json b/contracts/dao/voting/lockdrop-vault/schema/get_config_response.json index 86fc6a48..066f660f 100644 --- a/contracts/dao/voting/lockdrop-vault/schema/get_config_response.json +++ b/contracts/dao/voting/lockdrop-vault/schema/get_config_response.json @@ -6,7 +6,8 @@ "description", "lockdrop_contract", "name", - "oracle_contract", + "oracle_atom_contract", + "oracle_usdc_contract", "owner" ], "properties": { @@ -29,7 +30,10 @@ "name": { "type": "string" }, - "oracle_contract": { + "oracle_atom_contract": { + "$ref": "#/definitions/Addr" + }, + "oracle_usdc_contract": { "$ref": "#/definitions/Addr" }, "owner": { diff --git a/contracts/dao/voting/lockdrop-vault/schema/instantiate_msg.json b/contracts/dao/voting/lockdrop-vault/schema/instantiate_msg.json index 7593de2e..1eb4db48 100644 --- a/contracts/dao/voting/lockdrop-vault/schema/instantiate_msg.json +++ b/contracts/dao/voting/lockdrop-vault/schema/instantiate_msg.json @@ -6,7 +6,8 @@ "description", "lockdrop_contract", "name", - "oracle_contract", + "oracle_atom_contract", + "oracle_usdc_contract", "owner" ], "properties": { @@ -29,8 +30,12 @@ "description": "Name contains the vault name which is used to ease the vault's recognition.", "type": "string" }, - "oracle_contract": { - "description": "The oracle contract behind the vault.", + "oracle_atom_contract": { + "description": "The oracle ATOM/NTRN contract behind the vault.", + "type": "string" + }, + "oracle_usdc_contract": { + "description": "The oracle USDC/NTRN contract behind the vault.", "type": "string" }, "owner": { diff --git a/contracts/subdaos/cwd-subdao-timelock-single/schema/proposal_list_response.json b/contracts/subdaos/cwd-subdao-timelock-single/schema/proposal_list_response.json index 6e17fd24..b2284c86 100644 --- a/contracts/subdaos/cwd-subdao-timelock-single/schema/proposal_list_response.json +++ b/contracts/subdaos/cwd-subdao-timelock-single/schema/proposal_list_response.json @@ -1227,8 +1227,7 @@ "required": [ "id", "msgs", - "status", - "timelock_ts" + "status" ], "properties": { "id": { @@ -1246,14 +1245,6 @@ }, "status": { "$ref": "#/definitions/ProposalStatus" - }, - "timelock_ts": { - "description": "The timestamp at which the proposal was submitted to the timelock contract.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] } } }, From 57bc9375899443cf0c073b3cc960d31b9400a9b7 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 18 Apr 2023 15:13:22 +0300 Subject: [PATCH 2/5] fmt --- .../dao/voting/investors-vesting-vault/examples/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/dao/voting/investors-vesting-vault/examples/schema.rs b/contracts/dao/voting/investors-vesting-vault/examples/schema.rs index 323dddfa..ec547e81 100644 --- a/contracts/dao/voting/investors-vesting-vault/examples/schema.rs +++ b/contracts/dao/voting/investors-vesting-vault/examples/schema.rs @@ -3,12 +3,12 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; use cosmwasm_std::Addr; -use investors_vesting_vault::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use investors_vesting_vault::state::Config; use cw_controllers::ClaimsResponse; use cwd_interface::voting::{ InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; +use investors_vesting_vault::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use investors_vesting_vault::state::Config; fn main() { let mut out_dir = current_dir().unwrap(); From 4437ef95b3fcc53c6948188fda4ca7dbe008d6da Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 18 Apr 2023 15:17:43 +0300 Subject: [PATCH 3/5] updated README.md --- contracts/dao/voting/investors-vesting-vault/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/dao/voting/investors-vesting-vault/README.md b/contracts/dao/voting/investors-vesting-vault/README.md index b7003d7f..5b39a87b 100644 --- a/contracts/dao/voting/investors-vesting-vault/README.md +++ b/contracts/dao/voting/investors-vesting-vault/README.md @@ -1,3 +1,3 @@ ### Neutron Vesting Vault -This vault will allow its users to query voting power represented by NTRN tokens in the vesting contracts. Just as with normal DAO DAO voting modules, for each specific proposal, you can only use the voting power that was available to you at the time of proposal submission. No additional restrictions are imposed on the vault funds. +This vault will allow its users to query voting power represented by NTRN tokens in the vesting contracts. Just as with normal DAO DAO voting modules, for each specific proposal, you can only use the voting power that was available to you at the time of proposal submission. No additional restrictions are imposed on the vault funds. You can not directly add money to this vault or get it out of this vault. This is a proxy-vault that only queries information from the investors vesting contract. \ No newline at end of file From 7a70bc878750c37cdbc193a49a24fff33f2f0667 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 18 Apr 2023 16:02:44 +0300 Subject: [PATCH 4/5] review fixes --- Cargo.lock | 1 + .../voting/investors-vesting-vault/Cargo.toml | 1 + .../investors-vesting-vault/src/contract.rs | 104 ++++++++++++++++-- .../investors-vesting-vault/src/error.rs | 8 ++ .../voting/investors-vesting-vault/src/lib.rs | 2 +- .../voting/investors-vesting-vault/src/msg.rs | 10 +- .../investors-vesting-vault/src/state.rs | 16 ++- .../investors-vesting-vault/src/tests.rs | 27 ++++- 8 files changed, 149 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9425e0bc..97d5388e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1360,6 +1360,7 @@ dependencies = [ "cw20 1.0.1", "cwd-interface", "cwd-macros", + "cwd-voting", "schemars", "serde", "thiserror", diff --git a/contracts/dao/voting/investors-vesting-vault/Cargo.toml b/contracts/dao/voting/investors-vesting-vault/Cargo.toml index ca1fcc92..b1e579e8 100644 --- a/contracts/dao/voting/investors-vesting-vault/Cargo.toml +++ b/contracts/dao/voting/investors-vesting-vault/Cargo.toml @@ -30,6 +30,7 @@ thiserror = { version = "1.0" } cwd-macros = { path = "../../../../packages/cwd-macros" } cwd-interface = { path = "../../../../packages/cwd-interface" } cw-paginate = { path = "../../../../packages/cw-paginate" } +cwd-voting = { path = "../../../../packages/cwd-voting" } vesting-base = { git = "https://github.com/neutron-org/neutron-tge-contracts.git" } [dev-dependencies] diff --git a/contracts/dao/voting/investors-vesting-vault/src/contract.rs b/contracts/dao/voting/investors-vesting-vault/src/contract.rs index 32ae41f1..72a03d68 100644 --- a/contracts/dao/voting/investors-vesting-vault/src/contract.rs +++ b/contracts/dao/voting/investors-vesting-vault/src/contract.rs @@ -4,14 +4,17 @@ use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, }; use cw2::set_contract_version; -use cwd_interface::voting::{TotalPowerAtHeightResponse, VotingPowerAtHeightResponse}; +use cwd_interface::voting::{ + BondingStatusResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; use cwd_interface::Admin; +use cwd_voting::vault::{BonderBalanceResponse, ListBondersResponse}; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use crate::state::{Config, CONFIG, DAO, DESCRIPTION}; +use crate::state::{Config, CONFIG, DAO}; -pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-vesting-vault"; +pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-investors-vesting-vault"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] @@ -39,8 +42,11 @@ pub fn instantiate( description: msg.description, owner, manager, + name: msg.name, }; + config.validate()?; + CONFIG.save(deps.storage, &config)?; DAO.save(deps.storage, &info.sender)?; @@ -71,6 +77,7 @@ pub fn execute( owner, manager, description, + name, } => execute_update_config( deps, info, @@ -78,6 +85,7 @@ pub fn execute( owner, manager, description, + name, ), } } @@ -86,9 +94,10 @@ pub fn execute_update_config( deps: DepsMut, info: MessageInfo, new_vesting_contract_address: Option, - new_owner: String, + new_owner: Option, new_manager: Option, new_description: Option, + new_name: Option, ) -> Result { let mut config: Config = CONFIG.load(deps.storage)?; if info.sender != config.owner && Some(info.sender.clone()) != config.manager { @@ -99,16 +108,26 @@ pub fn execute_update_config( .map(|new_vesting_contract_address| deps.api.addr_validate(&new_vesting_contract_address)) .transpose()?; - let new_owner = deps.api.addr_validate(&new_owner)?; + let new_owner = new_owner + .map(|new_owner| deps.api.addr_validate(&new_owner)) + .transpose()?; + let new_manager = new_manager .map(|new_manager| deps.api.addr_validate(&new_manager)) .transpose()?; - if info.sender != config.owner && new_owner != config.owner { + if info.sender != config.owner && new_owner != Some(config.owner.clone()) { return Err(ContractError::OnlyOwnerCanChangeOwner {}); }; - config.owner = new_owner; + if let Some(owner) = new_owner { + config.owner = owner; + } + + if let Some(name) = new_name { + config.name = name; + } + config.manager = new_manager; if let Some(description) = new_description { config.description = description; @@ -117,6 +136,8 @@ pub fn execute_update_config( config.vesting_contract_address = new_vesting_contract_address; } + config.validate()?; + CONFIG.save(deps.storage, &config)?; Ok(Response::new() .add_attribute("action", "update_config") @@ -143,9 +164,69 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } QueryMsg::Info {} => query_info(deps), QueryMsg::Dao {} => query_dao(deps), + QueryMsg::Name {} => query_name(deps), QueryMsg::Description {} => query_description(deps), QueryMsg::GetConfig {} => to_binary(&CONFIG.load(deps.storage)?), + QueryMsg::BondingStatus { height, address } => { + to_binary(&query_bonding_status(deps, env, height, address)?) + } + QueryMsg::ListBonders { start_after, limit } => { + query_list_bonders(deps, env, start_after, limit) + } + } +} + +pub fn query_list_bonders( + deps: Deps, + env: Env, + start_after: Option, + limit: Option, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + let vesting_accounts: vesting_base::types::VestingAccountsResponse = + deps.querier.query_wasm_smart( + config.vesting_contract_address.clone(), + &vesting_base::msg::QueryMsg::VestingAccounts { + start_after, + limit, + order_by: None, + }, + )?; + + let mut bonders: Vec = vec![]; + + for va in vesting_accounts.vesting_accounts { + let unclaimed_amount: Uint128 = deps.querier.query_wasm_smart( + config.vesting_contract_address.clone(), + &vesting_base::msg::QueryMsg::HistoricalExtension { + msg: vesting_base::msg::QueryMsgHistorical::UnclaimedAmountAtHeight { + address: va.address.to_string(), + height: env.block.height, + }, + }, + )?; + bonders.push(BonderBalanceResponse { + address: va.address.to_string(), + balance: unclaimed_amount, + }) } + + to_binary(&ListBondersResponse { bonders }) +} + +pub fn query_bonding_status( + _deps: Deps, + env: Env, + height: Option, + _address: String, +) -> StdResult { + let height = height.unwrap_or(env.block.height); + Ok(BondingStatusResponse { + unbondable_abount: Uint128::zero(), + bonding_enabled: false, + height, + }) } pub fn query_voting_power_at_height( @@ -203,9 +284,14 @@ pub fn query_dao(deps: Deps) -> StdResult { to_binary(&dao) } +pub fn query_name(deps: Deps) -> StdResult { + let config: Config = CONFIG.load(deps.storage)?; + to_binary(&config.name) +} + pub fn query_description(deps: Deps) -> StdResult { - let description = DESCRIPTION.load(deps.storage)?; - to_binary(&description) + let config: Config = CONFIG.load(deps.storage)?; + to_binary(&config.description) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/dao/voting/investors-vesting-vault/src/error.rs b/contracts/dao/voting/investors-vesting-vault/src/error.rs index 81f3393f..66decb4d 100644 --- a/contracts/dao/voting/investors-vesting-vault/src/error.rs +++ b/contracts/dao/voting/investors-vesting-vault/src/error.rs @@ -11,4 +11,12 @@ pub enum ContractError { #[error("Only owner can change owner")] OnlyOwnerCanChangeOwner {}, + + #[error("config name cannot be empty.")] + NameIsEmpty {}, + + #[error("config description cannot be empty.")] + DescriptionIsEmpty {}, } + +pub type ContractResult = Result; diff --git a/contracts/dao/voting/investors-vesting-vault/src/lib.rs b/contracts/dao/voting/investors-vesting-vault/src/lib.rs index 4f75f883..7da6ab3e 100644 --- a/contracts/dao/voting/investors-vesting-vault/src/lib.rs +++ b/contracts/dao/voting/investors-vesting-vault/src/lib.rs @@ -6,4 +6,4 @@ pub mod state; #[cfg(test)] pub mod tests; -pub use crate::error::ContractError; +pub use crate::error::{ContractError, ContractResult}; diff --git a/contracts/dao/voting/investors-vesting-vault/src/msg.rs b/contracts/dao/voting/investors-vesting-vault/src/msg.rs index 8253361b..786344b3 100644 --- a/contracts/dao/voting/investors-vesting-vault/src/msg.rs +++ b/contracts/dao/voting/investors-vesting-vault/src/msg.rs @@ -1,5 +1,5 @@ use cwd_interface::Admin; -use cwd_macros::{info_query, voting_query}; +use cwd_macros::{info_query, voting_query, voting_vault_query}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -11,6 +11,8 @@ pub struct InstantiateMsg { pub description: String, // Owner can update all configs including changing the owner. This will generally be a DAO. pub owner: Admin, + // Name of the vault + pub name: String, // Manager can update all configs except changing the owner. This will generally be an operations multisig for a DAO. pub manager: Option, } @@ -20,19 +22,19 @@ pub struct InstantiateMsg { pub enum ExecuteMsg { UpdateConfig { vesting_contract_address: Option, - owner: String, + owner: Option, manager: Option, description: Option, + name: Option, }, } #[voting_query] +#[voting_vault_query] #[info_query] #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - Dao {}, - Description {}, GetConfig {}, } diff --git a/contracts/dao/voting/investors-vesting-vault/src/state.rs b/contracts/dao/voting/investors-vesting-vault/src/state.rs index 966b5167..ec14d81a 100644 --- a/contracts/dao/voting/investors-vesting-vault/src/state.rs +++ b/contracts/dao/voting/investors-vesting-vault/src/state.rs @@ -1,3 +1,4 @@ +use crate::{ContractError, ContractResult}; use cosmwasm_std::Addr; use cw_storage_plus::Item; use schemars::JsonSchema; @@ -9,8 +10,21 @@ pub struct Config { pub description: String, pub owner: Addr, pub manager: Option, + pub name: String, +} + +impl Config { + /// checks whether the config fields are valid. + pub fn validate(&self) -> ContractResult<()> { + if self.name.is_empty() { + return Err(ContractError::NameIsEmpty {}); + } + if self.description.is_empty() { + return Err(ContractError::DescriptionIsEmpty {}); + } + Ok(()) + } } pub const CONFIG: Item = Item::new("config"); pub const DAO: Item = Item::new("dao"); -pub const DESCRIPTION: Item = Item::new("description"); diff --git a/contracts/dao/voting/investors-vesting-vault/src/tests.rs b/contracts/dao/voting/investors-vesting-vault/src/tests.rs index e89c580c..100d844d 100644 --- a/contracts/dao/voting/investors-vesting-vault/src/tests.rs +++ b/contracts/dao/voting/investors-vesting-vault/src/tests.rs @@ -105,9 +105,10 @@ fn update_config( contract_addr, &ExecuteMsg::UpdateConfig { vesting_contract_address, - owner, + owner: Some(owner), manager, description, + name: None, }, &[], ) @@ -160,6 +161,7 @@ fn test_instantiate() { addr: DAO_ADDR.to_string(), }, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -174,6 +176,7 @@ fn test_instantiate() { addr: DAO_ADDR.to_string(), }, manager: None, + name: "vesting vault".to_string(), }, ); } @@ -193,6 +196,7 @@ fn test_instantiate_dao_owner() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -216,6 +220,7 @@ fn test_update_config_invalid_sender() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -247,6 +252,7 @@ fn test_update_config_non_owner_changes_owner() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -277,6 +283,7 @@ fn test_update_config_as_owner() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -299,6 +306,7 @@ fn test_update_config_as_owner() { description: NEW_DESCRIPTION.to_string(), owner: Addr::unchecked(ADDR1), manager: Some(Addr::unchecked(DAO_ADDR)), + name: "vesting vault".to_string(), }, config ); @@ -318,6 +326,7 @@ fn test_update_config_as_manager() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -340,13 +349,14 @@ fn test_update_config_as_manager() { description: NEW_DESCRIPTION.to_string(), owner: Addr::unchecked(DAO_ADDR), manager: Some(Addr::unchecked(ADDR2)), + name: "vesting vault".to_string(), }, config ); } #[test] -#[should_panic(expected = "Empty attribute value. Key: description")] +#[should_panic(expected = "config description cannot be empty.")] fn test_update_config_invalid_description() { let mut app = mock_app(); let vesting_contract = instantiate_vesting_contract(&mut app); @@ -360,6 +370,7 @@ fn test_update_config_invalid_description() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -390,6 +401,7 @@ fn test_query_dao() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -412,12 +424,16 @@ fn test_query_info() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); let msg = QueryMsg::Info {}; let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(resp.info.contract, "crates.io:neutron-vesting-vault"); + assert_eq!( + resp.info.contract, + "crates.io:neutron-investors-vesting-vault" + ); } #[test] @@ -434,6 +450,7 @@ fn test_query_get_config() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); @@ -445,6 +462,7 @@ fn test_query_get_config() { description: DESCRIPTION.to_string(), owner: Addr::unchecked(DAO_ADDR), manager: Some(Addr::unchecked(ADDR1)), + name: "vesting vault".to_string(), } ) } @@ -463,14 +481,13 @@ fn test_voting_power_queries() { description: DESCRIPTION.to_string(), owner: Admin::CoreModule {}, manager: Some(ADDR1.to_string()), + name: "vesting vault".to_string(), }, ); - // Total power is 0 let resp = get_total_power_at_height(&mut app, addr.clone(), None); assert_eq!(Uint128::from(10000u64), resp.power); - // ADDR1 has no power, none bonded let resp = get_voting_power_at_height(&mut app, addr, ADDR1.to_string(), None); assert_eq!(Uint128::from(10000u64), resp.power); } From 2aa265a4b46078ce74508548c0a99880844d665c Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 18 Apr 2023 16:03:30 +0300 Subject: [PATCH 5/5] schema --- .../schema/execute_msg.json | 14 ++- .../schema/get_config_response.json | 4 + .../schema/instantiate_msg.json | 4 + .../schema/query_msg.json | 96 ++++++++++++++++--- 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json b/contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json index 0d286a5c..88a7fb6c 100644 --- a/contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json +++ b/contracts/dao/voting/investors-vesting-vault/schema/execute_msg.json @@ -10,9 +10,6 @@ "properties": { "update_config": { "type": "object", - "required": [ - "owner" - ], "properties": { "description": { "type": [ @@ -26,8 +23,17 @@ "null" ] }, + "name": { + "type": [ + "string", + "null" + ] + }, "owner": { - "type": "string" + "type": [ + "string", + "null" + ] }, "vesting_contract_address": { "type": [ diff --git a/contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json b/contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json index 803fc4e8..8d11f777 100644 --- a/contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json +++ b/contracts/dao/voting/investors-vesting-vault/schema/get_config_response.json @@ -4,6 +4,7 @@ "type": "object", "required": [ "description", + "name", "owner", "vesting_contract_address" ], @@ -21,6 +22,9 @@ } ] }, + "name": { + "type": "string" + }, "owner": { "$ref": "#/definitions/Addr" }, diff --git a/contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json b/contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json index b8ebf93f..6ebbda97 100644 --- a/contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json +++ b/contracts/dao/voting/investors-vesting-vault/schema/instantiate_msg.json @@ -4,6 +4,7 @@ "type": "object", "required": [ "description", + "name", "owner", "vesting_contract_address" ], @@ -17,6 +18,9 @@ "null" ] }, + "name": { + "type": "string" + }, "owner": { "$ref": "#/definitions/Admin" }, diff --git a/contracts/dao/voting/investors-vesting-vault/schema/query_msg.json b/contracts/dao/voting/investors-vesting-vault/schema/query_msg.json index a1371f36..f2c982eb 100644 --- a/contracts/dao/voting/investors-vesting-vault/schema/query_msg.json +++ b/contracts/dao/voting/investors-vesting-vault/schema/query_msg.json @@ -5,10 +5,10 @@ { "type": "object", "required": [ - "dao" + "get_config" ], "properties": { - "dao": { + "get_config": { "type": "object" } }, @@ -17,11 +17,27 @@ { "type": "object", "required": [ - "description" + "voting_power_at_height" ], "properties": { - "description": { - "type": "object" + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } } }, "additionalProperties": false @@ -29,11 +45,21 @@ { "type": "object", "required": [ - "get_config" + "total_power_at_height" ], "properties": { - "get_config": { - "type": "object" + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } } }, "additionalProperties": false @@ -41,10 +67,10 @@ { "type": "object", "required": [ - "voting_power_at_height" + "bonding_status" ], "properties": { - "voting_power_at_height": { + "bonding_status": { "type": "object", "required": [ "address" @@ -69,19 +95,61 @@ { "type": "object", "required": [ - "total_power_at_height" + "dao" ], "properties": { - "total_power_at_height": { + "dao": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_bonders" + ], + "properties": { + "list_bonders": { "type": "object", "properties": { - "height": { + "limit": { "type": [ "integer", "null" ], - "format": "uint64", + "format": "uint32", "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] } } }