diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 1d03a5679..7702114f6 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -12,17 +12,18 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - - name: Install stable toolchain + - name: Install latest nightly toolchain uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.51.0 + toolchain: nightly target: wasm32-unknown-unknown override: true - name: Run tests uses: actions-rs/cargo@v1 with: + toolchain: nightly command: test args: --locked env: @@ -31,6 +32,7 @@ jobs: - name: Compile WASM contract uses: actions-rs/cargo@v1 with: + toolchain: nightly command: wasm args: --locked env: @@ -47,19 +49,21 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.51.0 + toolchain: nightly override: true components: rustfmt, clippy - name: Run cargo fmt uses: actions-rs/cargo@v1 with: + toolchain: nightly command: fmt args: --all -- --check - name: Run cargo clippy uses: actions-rs/cargo@v1 with: + toolchain: nightly command: clippy args: --all-targets -- -D warnings diff --git a/Cargo.lock b/Cargo.lock index 134099ec8..83cdb39ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.51" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "assert_matches" @@ -464,9 +464,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -485,9 +485,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if", "libc", @@ -550,9 +550,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.112" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" [[package]] name = "opaque-debug" @@ -572,9 +572,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.34" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] @@ -604,9 +604,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.10" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] @@ -626,7 +626,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", ] [[package]] @@ -661,9 +661,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.132" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.132" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -701,9 +701,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ "itoa", "ryu", @@ -712,9 +712,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer", "cfg-if", @@ -771,6 +771,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw-storage-plus", + "cw-utils", "cw2", "cw20", "cw20-base", @@ -794,9 +795,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", @@ -825,15 +826,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "uint" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +checksum = "1b1b413ebfe8c2c74a69ff124699dd156a7fa41cb1d09ba6df94aa2f2b0a4a3a" dependencies = [ "byteorder", "crunchy", @@ -849,9 +850,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" diff --git a/contracts/cw3-dao/src/contract.rs b/contracts/cw3-dao/src/contract.rs index 329e1260a..714615d9e 100644 --- a/contracts/cw3-dao/src/contract.rs +++ b/contracts/cw3-dao/src/contract.rs @@ -140,6 +140,7 @@ pub fn instantiate( admin: Some(env.contract.address.to_string()), label, msg: to_binary(&stake_cw20_gov::msg::InstantiateMsg { + admin: env.contract.address, unstaking_duration, token_address: cw20_addr.addr(), })?, @@ -665,6 +666,7 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result 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", "type": "string" @@ -92,6 +126,40 @@ } } }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/contracts/stake-cw20-gov/schema/query_msg.json b/contracts/stake-cw20-gov/schema/query_msg.json index 99318c730..77f190153 100644 --- a/contracts/stake-cw20-gov/schema/query_msg.json +++ b/contracts/stake-cw20-gov/schema/query_msg.json @@ -104,13 +104,13 @@ "additionalProperties": false }, { - "description": "Returns the unstaking duration for the contract.", + "description": "Returns the config for the contract.", "type": "object", "required": [ - "unstaking_duration" + "get_config" ], "properties": { - "unstaking_duration": { + "get_config": { "type": "object" } }, diff --git a/contracts/stake-cw20-gov/src/contract.rs b/contracts/stake-cw20-gov/src/contract.rs index 9ce19ac02..7f84b3f8f 100644 --- a/contracts/stake-cw20-gov/src/contract.rs +++ b/contracts/stake-cw20-gov/src/contract.rs @@ -35,6 +35,9 @@ pub fn execute( ExecuteMsg::Receive(msg) => execute_receive(deps, env, info, msg), ExecuteMsg::Unstake { amount } => execute_unstake(deps, env, info, amount), ExecuteMsg::Claim {} => stake_cw20::contract::execute_claim(deps, env, info), + ExecuteMsg::UpdateConfig { admin, duration } => { + stake_cw20::contract::execute_update_config(info, deps, admin, duration) + } ExecuteMsg::DelegateVotes { recipient } => { execute_delegate_votes(deps, env, info, recipient) } @@ -148,9 +151,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::StakedBalanceAtHeight { address, height } => to_binary( &stake_cw20::contract::query_staked_balance_at_height(deps, env, address, height)?, ), - QueryMsg::UnstakingDuration {} => { - to_binary(&stake_cw20::contract::query_unstaking_duration(deps)?) - } + QueryMsg::GetConfig {} => to_binary(&stake_cw20::contract::query_config(deps)?), QueryMsg::Claims { address } => { to_binary(&stake_cw20::contract::query_claims(deps, address)?) } @@ -238,6 +239,7 @@ mod tests { fn instantiate_staking(app: &mut App, cw20: Addr) -> Addr { let staking_code_id = app.store_code(contract_staking_gov()); let msg = crate::msg::InstantiateMsg { + admin: Addr::unchecked("owner"), token_address: cw20, unstaking_duration: None, }; diff --git a/contracts/stake-cw20-gov/src/msg.rs b/contracts/stake-cw20-gov/src/msg.rs index ebf0e6a3f..8f10df4f7 100644 --- a/contracts/stake-cw20-gov/src/msg.rs +++ b/contracts/stake-cw20-gov/src/msg.rs @@ -1,5 +1,6 @@ -use cosmwasm_std::Uint128; +use cosmwasm_std::{Addr, Uint128}; use cw20::Cw20ReceiveMsg; +use cw_utils::Duration; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub use stake_cw20::msg::{ @@ -10,9 +11,17 @@ pub use stake_cw20::msg::{ #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { Receive(Cw20ReceiveMsg), - Unstake { amount: Uint128 }, + Unstake { + amount: Uint128, + }, Claim {}, - DelegateVotes { recipient: String }, + DelegateVotes { + recipient: String, + }, + UpdateConfig { + admin: Addr, + duration: Option, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -40,8 +49,8 @@ pub enum QueryMsg { /// Returns the total staked amount of tokens at a given height, if no height is provided /// defaults to current block height. TotalStakedAtHeight { height: Option }, - /// Returns the unstaking duration for the contract. - UnstakingDuration {}, + /// Returns the config for the contract. + GetConfig {}, /// Returns existing claims for tokens currently unstaking for a given address. Claims { address: String }, } diff --git a/contracts/stake-cw20/examples/schema.rs b/contracts/stake-cw20/examples/schema.rs index 3e8b1c589..65fdd74d7 100644 --- a/contracts/stake-cw20/examples/schema.rs +++ b/contracts/stake-cw20/examples/schema.rs @@ -8,9 +8,9 @@ use cw20::{ TokenInfoResponse, }; use stake_cw20::msg::{ - ClaimsResponse, ExecuteMsg, InstantiateMsg, QueryMsg, StakedBalanceAtHeightResponse, - StakedValueResponse, TotalStakedAtHeightResponse, TotalValueResponse, - UnstakingDurationResponse, + ClaimsResponse, ExecuteMsg, GetConfigResponse, InstantiateMsg, QueryMsg, + StakedBalanceAtHeightResponse, StakedValueResponse, TotalStakedAtHeightResponse, + TotalValueResponse, }; fn main() { @@ -26,7 +26,7 @@ fn main() { export_schema(&schema_for!(TotalStakedAtHeightResponse), &out_dir); export_schema(&schema_for!(StakedValueResponse), &out_dir); export_schema(&schema_for!(TotalValueResponse), &out_dir); - export_schema(&schema_for!(UnstakingDurationResponse), &out_dir); + export_schema(&schema_for!(GetConfigResponse), &out_dir); export_schema(&schema_for!(ClaimsResponse), &out_dir); export_schema(&schema_for!(AllowanceResponse), &out_dir); export_schema(&schema_for!(BalanceResponse), &out_dir); diff --git a/contracts/stake-cw20/schema/execute_msg.json b/contracts/stake-cw20/schema/execute_msg.json index b6b8b4621..1674920f5 100644 --- a/contracts/stake-cw20/schema/execute_msg.json +++ b/contracts/stake-cw20/schema/execute_msg.json @@ -45,9 +45,43 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "$ref": "#/definitions/Addr" + }, + "duration": { + "anyOf": [ + { + "$ref": "#/definitions/Duration" + }, + { + "type": "null" + } + ] + } + } + } + }, + "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" + }, "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", "type": "string" @@ -72,6 +106,40 @@ } } }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/contracts/stake-cw20/schema/unstaking_duration_response.json b/contracts/stake-cw20/schema/get_config_response.json similarity index 51% rename from contracts/stake-cw20/schema/unstaking_duration_response.json rename to contracts/stake-cw20/schema/get_config_response.json index 251214f98..431689c91 100644 --- a/contracts/stake-cw20/schema/unstaking_duration_response.json +++ b/contracts/stake-cw20/schema/get_config_response.json @@ -1,9 +1,19 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "UnstakingDurationResponse", + "title": "GetConfigResponse", "type": "object", + "required": [ + "admin", + "token_address" + ], "properties": { - "duration": { + "admin": { + "$ref": "#/definitions/Addr" + }, + "token_address": { + "$ref": "#/definitions/Addr" + }, + "unstaking_duration": { "anyOf": [ { "$ref": "#/definitions/Duration" @@ -15,6 +25,10 @@ } }, "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" + }, "Duration": { "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", "oneOf": [ diff --git a/contracts/stake-cw20/schema/instantiate_msg.json b/contracts/stake-cw20/schema/instantiate_msg.json index af4607528..10025e52e 100644 --- a/contracts/stake-cw20/schema/instantiate_msg.json +++ b/contracts/stake-cw20/schema/instantiate_msg.json @@ -3,9 +3,13 @@ "title": "InstantiateMsg", "type": "object", "required": [ + "admin", "token_address" ], "properties": { + "admin": { + "$ref": "#/definitions/Addr" + }, "token_address": { "$ref": "#/definitions/Addr" }, diff --git a/contracts/stake-cw20/schema/query_msg.json b/contracts/stake-cw20/schema/query_msg.json index 30a8276e4..b735b7f09 100644 --- a/contracts/stake-cw20/schema/query_msg.json +++ b/contracts/stake-cw20/schema/query_msg.json @@ -87,10 +87,10 @@ { "type": "object", "required": [ - "unstaking_duration" + "get_config" ], "properties": { - "unstaking_duration": { + "get_config": { "type": "object" } }, diff --git a/contracts/stake-cw20/src/contract.rs b/contracts/stake-cw20/src/contract.rs index 1adc924b5..d35d94ea3 100644 --- a/contracts/stake-cw20/src/contract.rs +++ b/contracts/stake-cw20/src/contract.rs @@ -9,9 +9,9 @@ use cosmwasm_std::{ use cw20::Cw20ReceiveMsg; use crate::msg::{ - ExecuteMsg, InstantiateMsg, QueryMsg, ReceiveMsg, StakedBalanceAtHeightResponse, - StakedValueResponse, TotalStakedAtHeightResponse, TotalValueResponse, - UnstakingDurationResponse, + ExecuteMsg, GetConfigResponse, InstantiateMsg, QueryMsg, ReceiveMsg, + StakedBalanceAtHeightResponse, StakedValueResponse, TotalStakedAtHeightResponse, + TotalValueResponse, }; use crate::state::{Config, BALANCE, CLAIMS, CONFIG, STAKED_BALANCES, STAKED_TOTAL}; use crate::ContractError; @@ -26,6 +26,7 @@ pub use cw20_base::contract::{ }; pub use cw20_base::enumerable::{query_all_accounts, query_all_allowances}; use cw_controllers::ClaimsResponse; +use cw_utils::Duration; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -34,7 +35,10 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> Result, ContractError> { + let admin = deps.api.addr_validate(msg.admin.as_str())?; + let config = Config { + admin, token_address: msg.token_address, unstaking_duration: msg.unstaking_duration, }; @@ -53,8 +57,33 @@ pub fn execute( ExecuteMsg::Receive(msg) => execute_receive(deps, env, info, msg), ExecuteMsg::Unstake { amount } => execute_unstake(deps, env, info, amount), ExecuteMsg::Claim {} => execute_claim(deps, env, info), + ExecuteMsg::UpdateConfig { admin, duration } => { + execute_update_config(info, deps, admin, duration) + } + } +} + +pub fn execute_update_config( + info: MessageInfo, + deps: DepsMut, + admin: Addr, + duration: Option, +) -> Result { + let mut config: Config = CONFIG.load(deps.storage)?; + if info.sender != config.admin { + return Err(ContractError::Unauthorized { + expected: config.admin, + received: info.sender, + }); } + + config.admin = admin; + config.unstaking_duration = duration; + + CONFIG.save(deps.storage, &config)?; + Ok(Response::new().add_attribute("owner", config.admin.to_string())) } + pub fn execute_receive( deps: DepsMut, env: Env, @@ -227,6 +256,7 @@ pub fn execute_fund( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { + QueryMsg::GetConfig {} => to_binary(&query_config(deps)?), QueryMsg::StakedBalanceAtHeight { address, height } => { to_binary(&query_staked_balance_at_height(deps, env, address, height)?) } @@ -235,7 +265,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } QueryMsg::StakedValue { address } => to_binary(&query_staked_value(deps, env, address)?), QueryMsg::TotalValue {} => to_binary(&query_total_value(deps, env)?), - QueryMsg::UnstakingDuration {} => to_binary(&query_unstaking_duration(deps)?), QueryMsg::Claims { address } => to_binary(&query_claims(deps, address)?), } } @@ -296,10 +325,12 @@ pub fn query_total_value(deps: Deps, _env: Env) -> StdResult Ok(TotalValueResponse { total: balance }) } -pub fn query_unstaking_duration(deps: Deps) -> StdResult { +pub fn query_config(deps: Deps) -> StdResult { let config = CONFIG.load(deps.storage)?; - Ok(UnstakingDurationResponse { - duration: config.unstaking_duration, + Ok(GetConfigResponse { + admin: config.admin, + unstaking_duration: config.unstaking_duration, + token_address: config.token_address, }) } @@ -312,8 +343,8 @@ mod tests { use std::borrow::BorrowMut; use crate::msg::{ - ExecuteMsg, QueryMsg, ReceiveMsg, StakedBalanceAtHeightResponse, StakedValueResponse, - TotalStakedAtHeightResponse, TotalValueResponse, UnstakingDurationResponse, + ExecuteMsg, GetConfigResponse, QueryMsg, ReceiveMsg, StakedBalanceAtHeightResponse, + StakedValueResponse, TotalStakedAtHeightResponse, TotalValueResponse, }; use crate::ContractError; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; @@ -389,6 +420,7 @@ mod tests { ) -> Addr { let staking_code_id = app.store_code(contract_staking()); let msg = crate::msg::InstantiateMsg { + admin: Addr::unchecked("owner"), token_address: cw20, unstaking_duration, }; @@ -431,6 +463,11 @@ mod tests { result.balance } + fn query_config>(app: &App, contract_addr: T) -> GetConfigResponse { + let msg = QueryMsg::GetConfig {}; + app.wrap().query_wasm_smart(contract_addr, &msg).unwrap() + } + fn query_total_staked>(app: &App, contract_addr: T) -> Uint128 { let msg = QueryMsg::TotalStakedAtHeight { height: None }; let result: TotalStakedAtHeightResponse = @@ -483,6 +520,17 @@ mod tests { app.execute_contract(info.sender, cw20_addr.clone(), &msg, &[]) } + fn update_config( + app: &mut App, + staking_addr: &Addr, + info: MessageInfo, + admin: Addr, + duration: Option, + ) -> AnyResult { + let msg = ExecuteMsg::UpdateConfig { admin, duration }; + app.execute_contract(info.sender, staking_addr.clone(), &msg, &[]) + } + fn unstake_tokens( app: &mut App, staking_addr: &Addr, @@ -502,6 +550,47 @@ mod tests { app.execute_contract(info.sender, staking_addr.clone(), &msg, &[]) } + #[test] + fn test_update_config() { + let _deps = mock_dependencies(); + + let mut app = mock_app(); + let amount1 = Uint128::from(100u128); + let _token_address = Addr::unchecked("token_address"); + let initial_balances = vec![Cw20Coin { + address: ADDR1.to_string(), + amount: amount1, + }]; + let (staking_addr, _cw20_addr) = setup_test_case(&mut app, initial_balances, None); + + let info = mock_info("owner", &[]); + let _env = mock_env(); + // Test update admin + update_config( + &mut app, + &staking_addr, + info, + Addr::unchecked("owner2"), + Some(Duration::Height(100)), + ) + .unwrap(); + + let config = query_config(&app, &staking_addr); + assert_eq!(config.admin, Addr::unchecked("owner2")); + assert_eq!(config.unstaking_duration, Some(Duration::Height(100))); + + // Try updating admin with original owner, which is now invalid + let info = mock_info("owner", &[]); + let _err = update_config( + &mut app, + &staking_addr, + info, + Addr::unchecked("owner3"), + Some(Duration::Height(100)), + ) + .unwrap_err(); + } + #[test] fn test_staking() { let _deps = mock_dependencies(); @@ -707,28 +796,6 @@ mod tests { assert_eq!(get_balance(&app, &cw20_addr, ADDR1), Uint128::from(70u128)); } - #[test] - fn unstaking_duration_query() { - let mut app = mock_app(); - let amount1 = Uint128::from(100u128); - let unstaking_duration = Some(Duration::Height(10)); - let _token_address = Addr::unchecked("token_address"); - let initial_balances = vec![Cw20Coin { - address: ADDR1.to_string(), - amount: amount1, - }]; - let (staking_addr, _cw20_addr) = - setup_test_case(&mut app, initial_balances, unstaking_duration); - - let msg = QueryMsg::UnstakingDuration {}; - let res: UnstakingDurationResponse = app - .borrow_mut() - .wrap() - .query_wasm_smart(&staking_addr, &msg) - .unwrap(); - assert_eq!(res.duration, unstaking_duration); - } - #[test] fn multiple_address_staking() { let amount1 = Uint128::from(100u128); diff --git a/contracts/stake-cw20/src/error.rs b/contracts/stake-cw20/src/error.rs index 6e2465149..52e80141f 100644 --- a/contracts/stake-cw20/src/error.rs +++ b/contracts/stake-cw20/src/error.rs @@ -11,4 +11,6 @@ pub enum ContractError { NothingToClaim {}, #[error("Invalid token")] InvalidToken { received: Addr, expected: Addr }, + #[error("Unauthorized")] + Unauthorized { received: Addr, expected: Addr }, } diff --git a/contracts/stake-cw20/src/msg.rs b/contracts/stake-cw20/src/msg.rs index 7fa02a0b2..4ff6cef03 100644 --- a/contracts/stake-cw20/src/msg.rs +++ b/contracts/stake-cw20/src/msg.rs @@ -9,6 +9,7 @@ pub use cw_controllers::ClaimsResponse; #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)] pub struct InstantiateMsg { + pub admin: Addr, pub token_address: Addr, pub unstaking_duration: Option, } @@ -17,8 +18,14 @@ pub struct InstantiateMsg { #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { Receive(Cw20ReceiveMsg), - Unstake { amount: Uint128 }, + Unstake { + amount: Uint128, + }, Claim {}, + UpdateConfig { + admin: Addr, + duration: Option, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -41,7 +48,7 @@ pub enum QueryMsg { address: String, }, TotalValue {}, - UnstakingDuration {}, + GetConfig {}, Claims { address: String, }, @@ -75,6 +82,8 @@ pub struct TotalValueResponse { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct UnstakingDurationResponse { - pub duration: Option, +pub struct GetConfigResponse { + pub admin: Addr, + pub token_address: Addr, + pub unstaking_duration: Option, } diff --git a/contracts/stake-cw20/src/state.rs b/contracts/stake-cw20/src/state.rs index 305053610..cf6bdf308 100644 --- a/contracts/stake-cw20/src/state.rs +++ b/contracts/stake-cw20/src/state.rs @@ -8,6 +8,7 @@ use cw_utils::Duration; #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct Config { + pub admin: Addr, pub token_address: Addr, pub unstaking_duration: Option, }