diff --git a/Cargo.lock b/Cargo.lock index 10447cb4..67563f3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1089,9 +1089,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd6da19f25979c7270e70fa95ab371ec3b701cd0eefc47667a09785b3c59155" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" dependencies = [ "hermit-abi", "libc", @@ -1690,9 +1690,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rfc6979" @@ -1716,9 +1716,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.10" +version = "0.36.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" dependencies = [ "bitflags", "errno", @@ -2173,6 +2173,21 @@ dependencies = [ "vesting-base", ] +[[package]] +name = "vesting-managed" +version = "1.1.0" +dependencies = [ + "astroport 2.0.0", + "astroport-token", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 0.15.1", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw20 0.15.1", + "vesting-base", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 06a6b206..eff149a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["contracts/auction", "contracts/lockdrop", "contracts/credits", "contracts/vesting-lp", "contracts/cw20-merkle-airdrop", "contracts/price-feed", "contracts/astroport/*"] +members = ["contracts/auction", "contracts/lockdrop", "contracts/credits", "contracts/vesting-lp", "contracts/vesting-managed", "contracts/cw20-merkle-airdrop", "contracts/price-feed", "contracts/astroport/*"] [profile.release] rpath = false diff --git a/contracts/vesting-managed/.cargo/config b/contracts/vesting-managed/.cargo/config new file mode 100644 index 00000000..a79b8fdb --- /dev/null +++ b/contracts/vesting-managed/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example vesting_schema" diff --git a/contracts/vesting-managed/Cargo.toml b/contracts/vesting-managed/Cargo.toml new file mode 100644 index 00000000..fb387899 --- /dev/null +++ b/contracts/vesting-managed/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "vesting-managed" +version = "1.1.0" +authors = ["andrei.z@p2p.org"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +vesting-base = {path = "../../packages/vesting-base"} +astroport = { path = "../../packages/astroport", default-features = false } +cosmwasm-schema = { version = "1.1", default-features = false } +cosmwasm-std = { version = "1.1" } +cw-storage-plus = "0.15" +cw-utils = "0.15" +cw20 = { version = "0.15" } + +[dev-dependencies] +cw-multi-test = "0.15" +astroport-token = {path = "../astroport/token"} +cw20 = { version = "0.15" } +cw-utils = "0.15" \ No newline at end of file diff --git a/contracts/vesting-managed/examples/vesting_schema.rs b/contracts/vesting-managed/examples/vesting_schema.rs new file mode 100644 index 00000000..b1cdebf1 --- /dev/null +++ b/contracts/vesting-managed/examples/vesting_schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +use astroport::vesting::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/vesting-managed/schema/raw/execute.json b/contracts/vesting-managed/schema/raw/execute.json new file mode 100644 index 00000000..a8544d4e --- /dev/null +++ b/contracts/vesting-managed/schema/raw/execute.json @@ -0,0 +1,244 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute messages available in the contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", + "type": "object", + "required": [ + "register_vesting_accounts" + ], + "properties": { + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VestingAccount": { + "description": "This structure stores vesting information for a specific address that is getting tokens.", + "type": "object", + "required": [ + "address", + "schedules" + ], + "properties": { + "address": { + "description": "The address that is getting tokens", + "type": "string" + }, + "schedules": { + "description": "The vesting schedules targeted at the `address`", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting-managed/schema/raw/instantiate.json b/contracts/vesting-managed/schema/raw/instantiate.json new file mode 100644 index 00000000..094d61f3 --- /dev/null +++ b/contracts/vesting-managed/schema/raw/instantiate.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the parameters used for creating a contract.", + "type": "object", + "required": [ + "owner", + "vesting_token" + ], + "properties": { + "owner": { + "description": "Address allowed to change contract parameters", + "type": "string" + }, + "vesting_token": { + "description": "[`AssetInfo`] of the token that's being vested", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false, + "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" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/vesting-managed/schema/raw/migrate.json b/contracts/vesting-managed/schema/raw/migrate.json new file mode 100644 index 00000000..1b9dcecf --- /dev/null +++ b/contracts/vesting-managed/schema/raw/migrate.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "description": "This structure describes a migration message. We currently take no arguments for migrations.", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/vesting-managed/schema/raw/query.json b/contracts/vesting-managed/schema/raw/query.json new file mode 100644 index 00000000..c7c1852c --- /dev/null +++ b/contracts/vesting-managed/schema/raw/query.json @@ -0,0 +1,129 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Returns the configuration for the contract using a [`ConfigResponse`] object.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about an address vesting tokens using a [`VestingAccountResponse`] object.", + "type": "object", + "required": [ + "vesting_account" + ], + "properties": { + "vesting_account": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "order_by": { + "anyOf": [ + { + "$ref": "#/definitions/OrderBy" + }, + { + "type": "null" + } + ] + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unvested amount of tokens for a specific address.", + "type": "object", + "required": [ + "available_amount" + ], + "properties": { + "available_amount": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Timestamp returns the current timestamp", + "type": "object", + "required": [ + "timestamp" + ], + "properties": { + "timestamp": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "OrderBy": { + "description": "This enum describes the types of sorting that can be applied to some piece of data", + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + } +} diff --git a/contracts/vesting-managed/schema/raw/response_to_available_amount.json b/contracts/vesting-managed/schema/raw/response_to_available_amount.json new file mode 100644 index 00000000..25b73e8f --- /dev/null +++ b/contracts/vesting-managed/schema/raw/response_to_available_amount.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/vesting-managed/schema/raw/response_to_config.json b/contracts/vesting-managed/schema/raw/response_to_config.json new file mode 100644 index 00000000..9ab74329 --- /dev/null +++ b/contracts/vesting-managed/schema/raw/response_to_config.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "This structure describes a custom struct used to return the contract configuration.", + "type": "object", + "required": [ + "owner", + "vesting_token" + ], + "properties": { + "owner": { + "description": "Address allowed to set contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "vesting_token": { + "description": "[`AssetInfo`] of the token that's being vested", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false, + "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" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/vesting-managed/schema/raw/response_to_timestamp.json b/contracts/vesting-managed/schema/raw/response_to_timestamp.json new file mode 100644 index 00000000..7b729a7b --- /dev/null +++ b/contracts/vesting-managed/schema/raw/response_to_timestamp.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 +} diff --git a/contracts/vesting-managed/schema/raw/response_to_vesting_account.json b/contracts/vesting-managed/schema/raw/response_to_vesting_account.json new file mode 100644 index 00000000..d5bb1f36 --- /dev/null +++ b/contracts/vesting-managed/schema/raw/response_to_vesting_account.json @@ -0,0 +1,119 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountResponse", + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false, + "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" + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of ASTRO already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting-managed/schema/raw/response_to_vesting_accounts.json b/contracts/vesting-managed/schema/raw/response_to_vesting_accounts.json new file mode 100644 index 00000000..3eda33d1 --- /dev/null +++ b/contracts/vesting-managed/schema/raw/response_to_vesting_accounts.json @@ -0,0 +1,136 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountsResponse", + "description": "This structure describes a custom struct used to return vesting data for multiple vesting targets.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "description": "A list of accounts that are vesting tokens", + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccountResponse" + } + } + }, + "additionalProperties": false, + "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" + }, + "VestingAccountResponse": { + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of ASTRO already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting-managed/schema/vesting-lp.json b/contracts/vesting-managed/schema/vesting-lp.json new file mode 100644 index 00000000..904f8029 --- /dev/null +++ b/contracts/vesting-managed/schema/vesting-lp.json @@ -0,0 +1,820 @@ +{ + "contract_name": "vesting-managed", + "contract_version": "1.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the parameters used for creating a contract.", + "type": "object", + "required": [ + "owner", + "vesting_token" + ], + "properties": { + "owner": { + "description": "Address allowed to change contract parameters", + "type": "string" + }, + "vesting_token": { + "description": "[`AssetInfo`] of the token that's being vested", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false, + "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" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute messages available in the contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", + "type": "object", + "required": [ + "register_vesting_accounts" + ], + "properties": { + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VestingAccount": { + "description": "This structure stores vesting information for a specific address that is getting tokens.", + "type": "object", + "required": [ + "address", + "schedules" + ], + "properties": { + "address": { + "description": "The address that is getting tokens", + "type": "string" + }, + "schedules": { + "description": "The vesting schedules targeted at the `address`", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Returns the configuration for the contract using a [`ConfigResponse`] object.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about an address vesting tokens using a [`VestingAccountResponse`] object.", + "type": "object", + "required": [ + "vesting_account" + ], + "properties": { + "vesting_account": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "order_by": { + "anyOf": [ + { + "$ref": "#/definitions/OrderBy" + }, + { + "type": "null" + } + ] + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unvested amount of tokens for a specific address.", + "type": "object", + "required": [ + "available_amount" + ], + "properties": { + "available_amount": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Timestamp returns the current timestamp", + "type": "object", + "required": [ + "timestamp" + ], + "properties": { + "timestamp": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "OrderBy": { + "description": "This enum describes the types of sorting that can be applied to some piece of data", + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "description": "This structure describes a migration message. We currently take no arguments for migrations.", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "available_amount": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "This structure describes a custom struct used to return the contract configuration.", + "type": "object", + "required": [ + "owner", + "vesting_token" + ], + "properties": { + "owner": { + "description": "Address allowed to set contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "vesting_token": { + "description": "[`AssetInfo`] of the token that's being vested", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false, + "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" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } + }, + "timestamp": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vesting_account": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountResponse", + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false, + "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" + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of ASTRO already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "vesting_accounts": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountsResponse", + "description": "This structure describes a custom struct used to return vesting data for multiple vesting targets.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "description": "A list of accounts that are vesting tokens", + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccountResponse" + } + } + }, + "additionalProperties": false, + "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" + }, + "VestingAccountResponse": { + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of ASTRO already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/contracts/vesting-managed/src/contract.rs b/contracts/vesting-managed/src/contract.rs new file mode 100644 index 00000000..363b8392 --- /dev/null +++ b/contracts/vesting-managed/src/contract.rs @@ -0,0 +1,220 @@ +use cosmwasm_std::{ + attr, entry_point, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, SubMsg, Uint128, +}; +use cw_storage_plus::{SnapshotItem, SnapshotMap, Strategy}; +use cw_utils::must_pay; + +use astroport::asset::AssetInfo; +use astroport::asset::AssetInfoExt; +use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; +use astroport::vesting::{InstantiateMsg, QueryMsg, VestingInfo, VestingState}; +use vesting_base::state::Config; +use vesting_base::{error::ContractError, state::BaseVesting}; + +use crate::msg::ExecuteMsg; + +/// Creates a new contract with the specified parameters packed in the `msg` variable. +/// Returns a [`Response`] with the specified attributes if the operation was successful, or a [`ContractError`] if the contract was not created +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **msg** is a message of type [`InstantiateMsg`] which contains the parameters used for creating the contract. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let vest_app = BaseVesting::new(Strategy::Never); + vest_app.instantiate(deps, env, info, msg) +} + +/// Exposes execute functions available in the contract. +/// +/// ## Variants +/// * **ExecuteMsg::Claim { recipient, amount }** Claims vested tokens and transfers them to the vesting recipient. +/// +/// * **ExecuteMsg::Receive(msg)** Receives a message of type [`Cw20ReceiveMsg`] and processes it +/// depending on the received template. +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + let vest_app = BaseVesting::new(Strategy::Never); + + match msg { + ExecuteMsg::Claim { recipient, amount } => { + vest_app.claim(deps, env, info, recipient, amount) + } + ExecuteMsg::Receive(msg) => vest_app.receive_cw20(deps, env, info, msg), + ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { + let config = vest_app.config.load(deps.storage)?; + + match &config.vesting_token { + AssetInfo::NativeToken { denom } if info.sender == config.owner => { + let amount = must_pay(&info, denom)?; + vest_app.register_vesting_accounts( + deps, + vesting_accounts, + amount, + env.block.height, + ) + } + _ => Err(ContractError::Unauthorized {}), + } + } + ExecuteMsg::ProposeNewOwner { owner, expires_in } => { + let config: Config = vest_app.config.load(deps.storage)?; + + propose_new_owner( + deps, + info, + env, + owner, + expires_in, + config.owner, + &vest_app.ownership_proposal, + ) + .map_err(Into::into) + } + ExecuteMsg::DropOwnershipProposal {} => { + let config: Config = vest_app.config.load(deps.storage)?; + + drop_ownership_proposal(deps, info, config.owner, &vest_app.ownership_proposal) + .map_err(Into::into) + } + ExecuteMsg::ClaimOwnership {} => claim_ownership( + deps, + info, + env, + &vest_app.ownership_proposal, + |deps, new_owner| { + vest_app + .config + .update::<_, StdError>(deps.storage, |mut v| { + v.owner = new_owner; + Ok(v) + })?; + + Ok(()) + }, + ) + .map_err(Into::into), + ExecuteMsg::RemoveVestingAccounts { + vesting_accounts, + clawback_account, + } => { + let config = vest_app.config.load(deps.storage)?; + remove_vesting_accounts( + deps, + info, + env, + config, + vesting_accounts, + vest_app.vesting_state, + vest_app.vesting_info, + clawback_account, + ) + } + } +} + +#[allow(clippy::too_many_arguments)] +fn remove_vesting_accounts( + deps: DepsMut, + info: MessageInfo, + env: Env, + config: Config, + vesting_accounts: Vec, + vesting_state: SnapshotItem<'static, VestingState>, + vesting_info: SnapshotMap<'static, &'static Addr, VestingInfo>, + clawback_account: String, +) -> Result { + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + + let mut response = Response::new(); + + let clawback_address = deps.api.addr_validate(&clawback_account)?; + + // For each vesting account, calculate the amount of tokens to claw back (unclaimed + still + // vesting), transfer the required amount to the owner, remove the vesting information + // from the storage, and decrease the total granted metric. + for vesting_account in vesting_accounts { + let account_address = deps.api.addr_validate(&vesting_account)?; + + if let Some(account_info) = vesting_info.may_load(deps.storage, &account_address)? { + let mut total_granted_for_user = Uint128::zero(); + for sch in account_info.schedules { + if let Some(end_point) = sch.end_point { + total_granted_for_user = + total_granted_for_user.checked_add(end_point.amount)?; + } else { + total_granted_for_user = + total_granted_for_user.checked_add(sch.start_point.amount)?; + } + } + + let amount_to_claw_back = + total_granted_for_user.checked_sub(account_info.released_amount)?; + + let transfer_msg = config + .vesting_token + .with_balance(amount_to_claw_back) + .into_msg(&deps.querier, clawback_address.clone())?; + response = response.add_submessage(SubMsg::new(transfer_msg)); + + vesting_state.update::<_, ContractError>(deps.storage, env.block.height, |s| { + // Here we choose the "forget about everything" strategy. E.g., if we granted a user + // 300 tokens, and they claimed 150 tokens, the vesting state is + // { total_granted: 300, total_released: 150 }. + // If after that we remove the user's vesting account, we set the vesting state to + // { total_granted: 0, total_released: 0 }. + // + // If we decided to set it to { total_granted: 150, total_released: 150 }., the + // .total_released value of the vesting state would not be equal to the sum of the + // .released_amount values of all registered accounts. + let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; + state.total_granted = state.total_granted.checked_sub(total_granted_for_user)?; + state.total_released = state + .total_released + .checked_sub(account_info.released_amount)?; + Ok(state) + })?; + vesting_info.remove(deps.storage, &account_address.clone(), env.block.height)?; + } + } + + Ok(response.add_attributes(vec![ + attr("action", "remove_vesting_accounts"), + attr("sender", &info.sender), + ])) +} + +/// Exposes all the queries available in the contract. +/// +/// ## Queries +/// * **QueryMsg::Config {}** Returns the contract configuration in an object of type [`Config`]. +/// +/// * **QueryMsg::VestingAccount { address }** Returns information about the vesting schedules that have a specific vesting recipient. +/// +/// * **QueryMsg::VestingAccounts { +/// start_after, +/// limit, +/// order_by, +/// }** Returns a list of vesting schedules together with their vesting recipients. +/// +/// * **QueryMsg::AvailableAmount { address }** Returns the available amount of tokens that can be claimed by a specific vesting recipient. +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + let vest_app = BaseVesting::new(Strategy::Never); + vest_app.query(deps, env, msg) +} diff --git a/contracts/vesting-managed/src/lib.rs b/contracts/vesting-managed/src/lib.rs new file mode 100644 index 00000000..ac1b5fd2 --- /dev/null +++ b/contracts/vesting-managed/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +mod msg; + +#[cfg(test)] +mod tests; diff --git a/contracts/vesting-managed/src/msg.rs b/contracts/vesting-managed/src/msg.rs new file mode 100644 index 00000000..efbb1c5e --- /dev/null +++ b/contracts/vesting-managed/src/msg.rs @@ -0,0 +1,47 @@ +use astroport::vesting::VestingAccount; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; +use cw20::Cw20ReceiveMsg; + +/// This structure describes the execute messages available in the contract. +#[cw_serde] +pub enum ExecuteMsg { + /// Claim claims vested tokens and sends them to a recipient + Claim { + /// The address that receives the vested tokens + recipient: Option, + /// The amount of tokens to claim + amount: Option, + }, + /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template + Receive(Cw20ReceiveMsg), + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, + /// Creates a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + ProposeNewOwner { + /// The newly proposed owner + owner: String, + /// The validity period of the offer to change the owner + expires_in: u64, + }, + /// Removes a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + DropOwnershipProposal {}, + /// Claims contract ownership + /// ## Executor + /// Only the newly proposed owner can execute this + ClaimOwnership {}, + /// Removes vesting targets/accounts. + /// ## Executor + /// Only the current owner can execute this + RemoveVestingAccounts { + vesting_accounts: Vec, + /// Specifies the account that will receive the funds taken from the vesting accounts. + clawback_account: String, + }, +} diff --git a/contracts/vesting-managed/src/tests/integration.rs b/contracts/vesting-managed/src/tests/integration.rs new file mode 100644 index 00000000..a84e2de0 --- /dev/null +++ b/contracts/vesting-managed/src/tests/integration.rs @@ -0,0 +1,1261 @@ +use cosmwasm_std::{coin, coins, to_binary, Addr, StdResult, Timestamp, Uint128}; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; +use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_utils::PaymentError; + +use astroport::asset::{native_asset_info, token_asset_info}; +use astroport::querier::query_balance; +use astroport::vesting::{QueryMsg, VestingAccountResponse, VestingState}; +use astroport::{ + token::InstantiateMsg as TokenInstantiateMsg, + vesting::{ + Cw20HookMsg, ExecuteMsg, InstantiateMsg, VestingAccount, VestingSchedule, + VestingSchedulePoint, + }, +}; +use vesting_base::error::ContractError; +use vesting_base::state::Config; + +use crate::msg::ExecuteMsg as ManagedExecuteMsg; + +const OWNER1: &str = "owner1"; +const USER1: &str = "user1"; +const USER2: &str = "user2"; +const TOKEN_INITIAL_AMOUNT: u128 = 1_000_000_000_000_000; +const IBC_ASTRO: &str = "ibc/ASTRO_TOKEN"; + +#[test] +fn claim() { + let user1 = Addr::unchecked(USER1); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let astro_token_instance = instantiate_token( + &mut app, + token_code_id, + "ASTRO", + Some(1_000_000_000_000_000), + ); + + let vesting_instance = instantiate_vesting(&mut app, &astro_token_instance); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(200), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(110).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }) + .unwrap(), + amount: Uint128::from(300u128), + }; + + let res = app + .execute_contract(owner.clone(), astro_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Vesting schedule amount error. The total amount should be equal to the CW20 receive amount."); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(110).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }) + .unwrap(), + amount: Uint128::from(300u128), + }; + + app.execute_contract(owner.clone(), astro_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.clone(), Uint128::new(300u128)); + + // Check owner balance + check_token_balance( + &mut app, + &astro_token_instance, + &owner, + TOKEN_INITIAL_AMOUNT - 300u128, + ); + + // Check vesting balance + check_token_balance(&mut app, &astro_token_instance, &vesting_instance, 300u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + let _res = app + .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::VestingAccount { + address: user1.to_string(), + }; + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(300u128)); + + // Check vesting balance + check_token_balance(&mut app, &astro_token_instance, &vesting_instance, 0u128); + + // Check user balance + check_token_balance(&mut app, &astro_token_instance, &user1, 300u128); + + // Owner balance mustn't change after claim + check_token_balance( + &mut app, + &astro_token_instance, + &owner, + TOKEN_INITIAL_AMOUNT - 300u128, + ); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + // Check user balance after claim + let user1_vesting_amount: Uint128 = + app.wrap().query_wasm_smart(vesting_instance, &msg).unwrap(); + + assert_eq!(user1_vesting_amount.clone(), Uint128::new(0u128)); +} + +#[test] +fn query_unclaimed() {} + +#[test] +fn claim_native() { + let user1 = Addr::unchecked(USER1); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let random_token_instance = + instantiate_token(&mut app, token_code_id, "RND", Some(1_000_000_000)); + + mint_tokens(&mut app, &random_token_instance, &owner, 1_000_000_000); + + let vesting_instance = instantiate_vesting_remote_chain(&mut app); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(200), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(300u128), + }; + + let err = app + .execute_contract(owner.clone(), random_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(110).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &msg, + &coins(300, IBC_ASTRO), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.clone(), Uint128::new(300u128)); + + // Check owner balance + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 300u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + app.execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart( + vesting_instance.clone(), + &QueryMsg::VestingAccount { + address: user1.to_string(), + }, + ) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(300u128)); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 0); + + // Check user balance + let bal = query_balance(&app.wrap(), &user1, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 300); + + // Owner balance mustn't change after claim + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + // Check user balance after claim + let user1_vesting_amount: Uint128 = + app.wrap().query_wasm_smart(vesting_instance, &msg).unwrap(); + + assert_eq!(user1_vesting_amount.clone(), Uint128::new(0u128)); +} + +#[test] +fn register_vesting_accounts() { + let user1 = Addr::unchecked(USER1); + let user2 = Addr::unchecked(USER2); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let astro_token_instance = instantiate_token( + &mut app, + token_code_id, + "ASTRO", + Some(1_000_000_000_000_000), + ); + + let noname_token_instance = instantiate_token( + &mut app, + token_code_id, + "NONAME", + Some(1_000_000_000_000_000), + ); + + mint_tokens( + &mut app, + &noname_token_instance, + &owner, + TOKEN_INITIAL_AMOUNT, + ); + + let vesting_instance = instantiate_vesting(&mut app, &astro_token_instance); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(100u128), + }; + + let res = app + .execute_contract(owner.clone(), astro_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Vesting schedule error on addr: user1. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)"); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(100u128), + }; + + let res = app + .execute_contract( + user1.clone(), + astro_token_instance.clone(), + &msg.clone(), + &[], + ) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Cannot Sub with 0 and 100"); + + let res = app + .execute_contract(owner.clone(), noname_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Unauthorized"); + + // Checking that execute endpoint with native coin is unreachable if ASTRO is a cw20 token + let native_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }; + + let err = app + .execute_contract( + owner.clone(), + vesting_instance.clone(), + &native_msg, + &coins(100u128, "random_coin"), + ) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let _res = app + .execute_contract(owner.clone(), astro_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + assert_eq!(user1_vesting_amount.clone(), Uint128::new(100u128)); + check_token_balance( + &mut app, + &astro_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 100u128, + ); + check_token_balance(&mut app, &astro_token_instance, &vesting_instance, 100u128); + + // Let's check user1's final vesting amount after add schedule for a new one + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user2.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(200), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(200u128), + }; + + let _res = app + .execute_contract(owner.clone(), astro_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user2.to_string(), + }; + + let user2_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + check_token_balance( + &mut app, + &astro_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 300u128, + ); + check_token_balance(&mut app, &astro_token_instance, &vesting_instance, 300u128); + // A new schedule has been added successfully and an old one hasn't changed. + // The new schedule doesn't have the same value as the old one. + assert_eq!(user2_vesting_amount, Uint128::new(200u128)); + assert_eq!(user1_vesting_amount, Uint128::from(100u128)); + + // Add one more vesting schedule; final amount to vest must increase + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(10), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(10u128), + }; + + let _res = app + .execute_contract(owner.clone(), astro_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + assert_eq!(vesting_res, Uint128::new(110u128)); + check_token_balance( + &mut app, + &astro_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 310u128, + ); + check_token_balance(&mut app, &astro_token_instance, &vesting_instance, 310u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + let _res = app + .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::VestingAccount { + address: user1.to_string(), + }; + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(110u128)); + check_token_balance(&mut app, &astro_token_instance, &vesting_instance, 200u128); + check_token_balance(&mut app, &astro_token_instance, &user1, 110u128); + + // Owner balance mustn't change after claim + check_token_balance( + &mut app, + &astro_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 310u128, + ); +} + +#[test] +fn register_vesting_accounts_native() { + let user1 = Addr::unchecked(USER1); + let user2 = Addr::unchecked(USER2); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let random_token_instance = + instantiate_token(&mut app, token_code_id, "RND", Some(1_000_000_000_000_000)); + + mint_tokens( + &mut app, + &random_token_instance, + &owner, + TOKEN_INITIAL_AMOUNT, + ); + + let vesting_instance = instantiate_vesting_remote_chain(&mut app); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(100u128), + }; + + let err = app + .execute_contract(owner.clone(), random_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + // Checking that execute endpoint with random native coin is unreachable + let native_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }; + + let err = app + .execute_contract( + owner.clone(), + vesting_instance.clone(), + &native_msg, + &coins(100u128, "random_coin"), + ) + .unwrap_err(); + assert_eq!( + ContractError::PaymentError(PaymentError::MissingDenom("ibc/ASTRO_TOKEN".to_string())), + err.downcast().unwrap() + ); + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &native_msg, + &coins(100u128, IBC_ASTRO), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(&vesting_instance, &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.u128(), 100u128); + + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 100u128); + + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 100); + + // Let's check user1's final vesting amount after add schedule for a new one + let msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user2.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(200), + }), + }], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &msg, + &coins(200, IBC_ASTRO), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user2.to_string(), + }; + + let user2_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 300u128); + + // A new schedule has been added successfully and an old one hasn't changed. + // The new schedule doesn't have the same value as the old one. + assert_eq!(user2_vesting_amount, Uint128::new(200u128)); + assert_eq!(user1_vesting_amount, Uint128::from(100u128)); + + // Add one more vesting schedule; final amount to vest must increase + let msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(10), + }), + }], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &msg, + &coins(10, IBC_ASTRO), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(110u128)); + + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 310u128); + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 310u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + let _res = app + .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::VestingAccount { + address: user1.to_string(), + }; + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(110u128)); + + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 200); + let bal = query_balance(&app.wrap(), &user1, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 110u128); + + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 310u128); +} + +#[test] +fn remove_vesting_accounts() { + let user1 = Addr::unchecked(USER1); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let random_token_instance = + instantiate_token(&mut app, token_code_id, "RND", Some(1_000_000_000)); + + mint_tokens(&mut app, &random_token_instance, &owner, 1_000_000_000); + + let vesting_instance = instantiate_vesting_remote_chain(&mut app); + + //////////////////////////////////////////////////////////////////////////////// + // + // Scenario #1: + // 1. Create vesting schedules + // 2. Check that the user has 400 vesting tokens, check that the owner has spent + // 400 tokens, check that the vesting contract has 400 tokens on its balance + // 3. Remove vesting schedules + // 4. Check that the user has 0 vesting tokens, check that the owner has received + // 400 tokens back, check that the vesting contract has 0 tokens on its balance + // + //////////////////////////////////////////////////////////////////////////////// + + let register_vesting_accounts_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::new(100), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(200), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::new(100), + }, + end_point: None, + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + ®ister_vesting_accounts_msg, + &coins(400, IBC_ASTRO), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.clone(), Uint128::new(400u128)); + + // Check owner balance + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 400u128); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 400u128); + + let remove_vesting_accounts_msg = ManagedExecuteMsg::RemoveVestingAccounts { + vesting_accounts: vec![user1.to_string()], + clawback_account: owner.to_string(), + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &remove_vesting_accounts_msg, + &[], + ) + .unwrap(); + + // Check that the owner received their tokens back. + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT); + + // Check that the user has no funds available anymore (will result in a VestingInfo not found + // error). + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let res: StdResult = app.wrap().query_wasm_smart(vesting_instance.clone(), &msg); + assert_eq!( + res.unwrap_err().to_string(), + "Generic error: Querier contract error: astroport::vesting::VestingInfo not found" + ); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 0u128); + + //////////////////////////////////////////////////////////////////////////////// + // + // Scenario #2: + // 1. Create vesting schedules + // 2. Check that the user has 400 vesting tokens, check that the owner has spent + // 400 tokens, check that the vesting contract has 400 tokens on its balance + // 3. Claim 400/2 tokens; + // 4. Check that vesting_state is { total_granted: 400, total_released: 200 } + // 5. Remove vesting schedules + // 6. Check that the user has 0 vesting tokens, check that the owner has received + // 400/2 tokens back, check that the vesting contract has 0 tokens on its balance, + // and check that the user 400/2 tokens on their balance. + // 7. Check that vesting_state is { total_granted: 0, total_released: 0 } + // + //////////////////////////////////////////////////////////////////////////////// + + let register_vesting_accounts_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::new(100), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(200), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::new(100), + }, + end_point: None, + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + ®ister_vesting_accounts_msg, + &coins(400, IBC_ASTRO), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.clone(), Uint128::new(400u128)); + + // Check owner balance + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 400u128); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 400u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: Some(Uint128::from(200u128)), + }; + app.execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let vesting_state: VestingState = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &QueryMsg::VestingState {}) + .unwrap(); + assert_eq!(vesting_state.total_granted.clone(), Uint128::new(400u128)); + assert_eq!(vesting_state.total_released.clone(), Uint128::new(200u128)); + + let remove_vesting_accounts_msg = ManagedExecuteMsg::RemoveVestingAccounts { + vesting_accounts: vec![user1.to_string()], + clawback_account: owner.to_string(), + }; + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &remove_vesting_accounts_msg, + &[], + ) + .unwrap(); + + // Check that the owner received their tokens back. + let bal = query_balance(&app.wrap(), &owner, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 200u128); + + // Check that the user has the tokens that they claimed. + let bal = query_balance(&app.wrap(), &user1, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 200u128); + + // Check that the user has no funds available anymore (will result in a VestingInfo not found + // error). + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let res: StdResult = app.wrap().query_wasm_smart(vesting_instance.clone(), &msg); + assert_eq!( + res.unwrap_err().to_string(), + "Generic error: Querier contract error: astroport::vesting::VestingInfo not found" + ); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, IBC_ASTRO) + .unwrap() + .u128(); + assert_eq!(bal, 0u128); + + let vesting_state: VestingState = app + .wrap() + .query_wasm_smart(vesting_instance, &QueryMsg::VestingState {}) + .unwrap(); + assert_eq!(vesting_state.total_granted.clone(), Uint128::new(0u128)); + assert_eq!(vesting_state.total_released.clone(), Uint128::new(0u128)); +} + +fn mock_app(owner: &Addr) -> App { + App::new(|app, _, storage| { + app.bank + .init_balance( + storage, + owner, + vec![ + coin(TOKEN_INITIAL_AMOUNT, IBC_ASTRO), + coin(10_000_000_000u128, "random_coin"), + ], + ) + .unwrap() + }) +} + +fn store_token_code(app: &mut App) -> u64 { + let astro_token_contract = Box::new(ContractWrapper::new_with_empty( + astroport_token::contract::execute, + astroport_token::contract::instantiate, + astroport_token::contract::query, + )); + + app.store_code(astro_token_contract) +} + +fn instantiate_token(app: &mut App, token_code_id: u64, name: &str, cap: Option) -> Addr { + let name = String::from(name); + + let msg = TokenInstantiateMsg { + name: name.clone(), + symbol: name.clone(), + decimals: 6, + initial_balances: vec![], + mint: Some(MinterResponse { + minter: String::from(OWNER1), + cap: cap.map(Uint128::from), + }), + marketing: None, + }; + + app.instantiate_contract( + token_code_id, + Addr::unchecked(OWNER1), + &msg, + &[], + name, + None, + ) + .unwrap() +} + +fn instantiate_vesting(app: &mut App, astro_token_instance: &Addr) -> Addr { + let vesting_contract = Box::new(ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + )); + let owner = Addr::unchecked(OWNER1); + let vesting_code_id = app.store_code(vesting_contract); + + let init_msg = InstantiateMsg { + owner: OWNER1.to_string(), + vesting_token: token_asset_info(astro_token_instance.clone()), + vesting_managers: Vec::new(), + }; + + let vesting_instance = app + .instantiate_contract( + vesting_code_id, + owner.clone(), + &init_msg, + &[], + "Vesting", + None, + ) + .unwrap(); + + let res: Config = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &QueryMsg::Config {}) + .unwrap(); + assert_eq!( + astro_token_instance.to_string(), + res.vesting_token.to_string() + ); + + mint_tokens(app, astro_token_instance, &owner, TOKEN_INITIAL_AMOUNT); + + check_token_balance(app, astro_token_instance, &owner, TOKEN_INITIAL_AMOUNT); + + vesting_instance +} + +fn instantiate_vesting_remote_chain(app: &mut App) -> Addr { + let vesting_contract = Box::new(ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + )); + let owner = Addr::unchecked(OWNER1); + let vesting_code_id = app.store_code(vesting_contract); + + let init_msg = InstantiateMsg { + owner: OWNER1.to_string(), + vesting_token: native_asset_info(IBC_ASTRO.to_string()), + vesting_managers: Vec::new(), + }; + + app.instantiate_contract(vesting_code_id, owner, &init_msg, &[], "Vesting", None) + .unwrap() +} + +fn mint_tokens(app: &mut App, token: &Addr, recipient: &Addr, amount: u128) { + let msg = Cw20ExecuteMsg::Mint { + recipient: recipient.to_string(), + amount: Uint128::from(amount), + }; + + app.execute_contract(Addr::unchecked(OWNER1), token.to_owned(), &msg, &[]) + .unwrap(); +} + +fn check_token_balance(app: &mut App, token: &Addr, address: &Addr, expected: u128) { + let msg = Cw20QueryMsg::Balance { + address: address.to_string(), + }; + let res: StdResult = app.wrap().query_wasm_smart(token, &msg); + assert_eq!(res.unwrap().balance, Uint128::from(expected)); +} diff --git a/contracts/vesting-managed/src/tests/mod.rs b/contracts/vesting-managed/src/tests/mod.rs new file mode 100644 index 00000000..6d3bbe60 --- /dev/null +++ b/contracts/vesting-managed/src/tests/mod.rs @@ -0,0 +1 @@ +mod integration; diff --git a/packages/astroport/src/vesting.rs b/packages/astroport/src/vesting.rs index f1d897bd..eb713eaa 100644 --- a/packages/astroport/src/vesting.rs +++ b/packages/astroport/src/vesting.rs @@ -128,6 +128,9 @@ pub enum QueryMsg { /// Timestamp returns the current timestamp #[returns(u64)] Timestamp {}, + /// VestingState returns the current vesting state. + #[returns(VestingState)] + VestingState {}, /// Returns list of vesting managers /// (the persons who are able to add/remove vesting schedules) #[returns(Vec)] diff --git a/packages/vesting-base/src/contract.rs b/packages/vesting-base/src/contract.rs index 4def6212..c058239b 100644 --- a/packages/vesting-base/src/contract.rs +++ b/packages/vesting-base/src/contract.rs @@ -144,7 +144,7 @@ impl BaseVesting { /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. /// /// * **cw20_msg** CW20 message to process. - fn receive_cw20( + pub fn receive_cw20( &self, deps: DepsMut, env: Env, @@ -386,6 +386,7 @@ impl BaseVesting { &self.query_vesting_available_amount(deps, env, address)?, )?), QueryMsg::Timestamp {} => Ok(to_binary(&self.query_timestamp(env)?)?), + QueryMsg::VestingState {} => Ok(to_binary(&self.vesting_state.load(deps.storage)?)?), QueryMsg::VestingManagers {} => Ok(to_binary(&self.query_vesting_managers(deps)?)?), } }