diff --git a/Cargo.lock b/Cargo.lock index 25b31873b..7f4c50e03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1303,6 +1303,7 @@ dependencies = [ "cw20-base 1.1.0", "cw20-stake 0.2.6", "dao-hooks", + "dao-voting 2.2.0", "thiserror", ] diff --git a/contracts/staking/cw20-stake/Cargo.toml b/contracts/staking/cw20-stake/Cargo.toml index 10ebaadc3..20acf9375 100644 --- a/contracts/staking/cw20-stake/Cargo.toml +++ b/contracts/staking/cw20-stake/Cargo.toml @@ -30,6 +30,7 @@ thiserror = { workspace = true } cw-paginate-storage = { workspace = true } cw-ownable = { workspace = true } dao-hooks = { workspace = true } +dao-voting = { workspace = true } cw20-stake-v1 = { workspace = true, features = ["library"] } cw-utils-v1 = { workspace = true } diff --git a/contracts/staking/cw20-stake/src/contract.rs b/contracts/staking/cw20-stake/src/contract.rs index 295cc13b7..f200b3737 100644 --- a/contracts/staking/cw20-stake/src/contract.rs +++ b/contracts/staking/cw20-stake/src/contract.rs @@ -5,20 +5,8 @@ use cosmwasm_std::{ from_binary, to_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, StdResult, Uint128, }; - -use cw20::{Cw20ReceiveMsg, TokenInfoResponse}; - -use crate::math; -use crate::msg::{ - ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, - ReceiveMsg, StakedBalanceAtHeightResponse, StakedValueResponse, StakerBalanceResponse, - TotalStakedAtHeightResponse, TotalValueResponse, -}; -use crate::state::{ - Config, BALANCE, CLAIMS, CONFIG, HOOKS, MAX_CLAIMS, STAKED_BALANCES, STAKED_TOTAL, -}; -use crate::ContractError; use cw2::{get_contract_version, set_contract_version, ContractVersion}; +use cw20::{Cw20ReceiveMsg, TokenInfoResponse}; pub use cw20_base::allowances::{ execute_burn_from, execute_decrease_allowance, execute_increase_allowance, execute_send_from, execute_transfer_from, query_allowance, @@ -32,28 +20,22 @@ pub use cw20_base::enumerable::{query_all_accounts, query_owner_allowances}; use cw_controllers::ClaimsResponse; use cw_utils::Duration; use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; +use dao_voting::duration::validate_duration; + +use crate::math; +use crate::msg::{ + ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, + ReceiveMsg, StakedBalanceAtHeightResponse, StakedValueResponse, StakerBalanceResponse, + TotalStakedAtHeightResponse, TotalValueResponse, +}; +use crate::state::{ + Config, BALANCE, CLAIMS, CONFIG, HOOKS, MAX_CLAIMS, STAKED_BALANCES, STAKED_TOTAL, +}; +use crate::ContractError; pub(crate) const CONTRACT_NAME: &str = "crates.io:cw20-stake"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -fn validate_duration(duration: Option) -> Result<(), ContractError> { - if let Some(unstaking_duration) = duration { - match unstaking_duration { - Duration::Height(height) => { - if height == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - Duration::Time(time) => { - if time == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - } - } - Ok(()) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, diff --git a/contracts/staking/cw20-stake/src/error.rs b/contracts/staking/cw20-stake/src/error.rs index 016f271bf..394cd0e04 100644 --- a/contracts/staking/cw20-stake/src/error.rs +++ b/contracts/staking/cw20-stake/src/error.rs @@ -5,29 +5,40 @@ use thiserror::Error; pub enum ContractError { #[error(transparent)] Std(#[from] StdError), + #[error(transparent)] Cw20Error(#[from] cw20_base::ContractError), + #[error(transparent)] Ownership(#[from] cw_ownable::OwnershipError), + #[error(transparent)] HookError(#[from] cw_hooks::HookError), - #[error("Provided cw20 errored in response to TokenInfo query")] - InvalidCw20 {}, - #[error("Nothing to claim")] - NothingToClaim {}, - #[error("Nothing to unstake")] - NothingStaked {}, + #[error(transparent)] + UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + + #[error("can not migrate. current version is up to date")] + AlreadyMigrated {}, + #[error("Unstaking this amount violates the invariant: (cw20 total_supply <= 2^128)")] Cw20InvaraintViolation {}, + #[error("Can not unstake more than has been staked")] ImpossibleUnstake {}, + + #[error("Provided cw20 errored in response to TokenInfo query")] + InvalidCw20 {}, + #[error("Invalid token")] InvalidToken { received: Addr, expected: Addr }, + + #[error("Nothing to claim")] + NothingToClaim {}, + + #[error("Nothing to unstake")] + NothingStaked {}, + #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] TooManyClaims {}, - #[error("Invalid unstaking duration, unstaking duration cannot be 0")] - InvalidUnstakingDuration {}, - #[error("can not migrate. current version is up to date")] - AlreadyMigrated {}, } diff --git a/contracts/staking/cw20-stake/src/tests.rs b/contracts/staking/cw20-stake/src/tests.rs index 81ae03b1a..1838265a5 100644 --- a/contracts/staking/cw20-stake/src/tests.rs +++ b/contracts/staking/cw20-stake/src/tests.rs @@ -1,3 +1,13 @@ +use anyhow::Result as AnyResult; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use cosmwasm_std::{to_binary, Addr, Empty, MessageInfo, Uint128, WasmMsg}; +use cw20::Cw20Coin; +use cw_controllers::{Claim, ClaimsResponse}; +use cw_multi_test::{next_block, App, AppResponse, Contract, ContractWrapper, Executor}; +use cw_ownable::{Action, Ownership, OwnershipError}; +use cw_utils::Duration; +use cw_utils::Expiration::AtHeight; +use dao_voting::duration::UnstakingDurationError; use std::borrow::BorrowMut; use crate::msg::{ @@ -7,20 +17,9 @@ use crate::msg::{ }; use crate::state::{Config, MAX_CLAIMS}; use crate::ContractError; -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{to_binary, Addr, Empty, MessageInfo, Uint128, WasmMsg}; -use cw20::Cw20Coin; -use cw_ownable::{Action, Ownership, OwnershipError}; -use cw_utils::Duration; - -use cw_multi_test::{next_block, App, AppResponse, Contract, ContractWrapper, Executor}; -use anyhow::Result as AnyResult; use cw20_stake_v1 as v1; -use cw_controllers::{Claim, ClaimsResponse}; -use cw_utils::Expiration::AtHeight; - const ADDR1: &str = "addr0001"; const ADDR2: &str = "addr0002"; const ADDR3: &str = "addr0003"; @@ -273,14 +272,20 @@ fn test_update_config() { .unwrap_err() .downcast() .unwrap(); - assert_eq!(err, ContractError::InvalidUnstakingDuration {}); + assert_eq!( + err, + ContractError::UnstakingDurationError(UnstakingDurationError::InvalidUnstakingDuration {}) + ); let info = mock_info(OWNER, &[]); let err: ContractError = update_config(&mut app, &staking_addr, info, Some(Duration::Time(0))) .unwrap_err() .downcast() .unwrap(); - assert_eq!(err, ContractError::InvalidUnstakingDuration {}); + assert_eq!( + err, + ContractError::UnstakingDurationError(UnstakingDurationError::InvalidUnstakingDuration {}) + ); } #[test] diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs index f3ba4546d..446720f06 100644 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ b/contracts/voting/dao-voting-native-staked/src/contract.rs @@ -11,7 +11,10 @@ use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; use dao_interface::voting::{ IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; +use dao_voting::{ + duration::validate_duration, + threshold::{ActiveThreshold, ActiveThresholdResponse}, +}; use crate::error::ContractError; use crate::msg::{ @@ -29,24 +32,6 @@ pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); // when using active threshold with percent const PRECISION_FACTOR: u128 = 10u128.pow(9); -fn validate_duration(duration: Option) -> Result<(), ContractError> { - if let Some(unstaking_duration) = duration { - match unstaking_duration { - Duration::Height(height) => { - if height == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - Duration::Time(time) => { - if time == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - } - } - Ok(()) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, diff --git a/contracts/voting/dao-voting-native-staked/src/error.rs b/contracts/voting/dao-voting-native-staked/src/error.rs index 9829c2a07..8601af4b2 100644 --- a/contracts/voting/dao-voting-native-staked/src/error.rs +++ b/contracts/voting/dao-voting-native-staked/src/error.rs @@ -13,15 +13,15 @@ pub enum ContractError { #[error(transparent)] HookError(#[from] cw_hooks::HookError), + #[error(transparent)] + UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + #[error("Unauthorized")] Unauthorized {}, #[error("Denom does not exist on chain")] InvalidDenom {}, - #[error("Invalid unstaking duration, unstaking duration cannot be 0")] - InvalidUnstakingDuration {}, - #[error("Nothing to claim")] NothingToClaim {}, diff --git a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs index be1828582..efb7ead0c 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -16,10 +16,12 @@ use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; use dao_interface::voting::{ IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; +use dao_voting::{ + duration::validate_duration, + threshold::{ActiveThreshold, ActiveThresholdResponse}, +}; use crate::error::ContractError; - use crate::msg::{ DenomResponse, ExecuteMsg, GetHooksResponse, InitialBalance, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, StakerBalanceResponse, TokenInfo, @@ -42,24 +44,6 @@ const INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID: u64 = 0; // when using active threshold with percent const PRECISION_FACTOR: u128 = 10u128.pow(9); -fn validate_duration(duration: Option) -> Result<(), ContractError> { - if let Some(unstaking_duration) = duration { - match unstaking_duration { - Duration::Height(height) => { - if height == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - Duration::Time(time) => { - if time == 0 { - return Err(ContractError::InvalidUnstakingDuration {}); - } - } - } - } - Ok(()) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, diff --git a/contracts/voting/dao-voting-token-factory-staked/src/error.rs b/contracts/voting/dao-voting-token-factory-staked/src/error.rs index 2a5425e0b..9881975c2 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/error.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/error.rs @@ -16,6 +16,9 @@ pub enum ContractError { #[error(transparent)] HookError(#[from] cw_hooks::HookError), + #[error(transparent)] + UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + #[error("Absolute count threshold cannot be greater than the total token supply")] InvalidAbsoluteCount {}, @@ -28,9 +31,6 @@ pub enum ContractError { #[error("Can only unstake less than or equal to the amount you have staked")] InvalidUnstakeAmount {}, - #[error("Invalid unstaking duration, unstaking duration cannot be 0")] - InvalidUnstakingDuration {}, - #[error("Nothing to claim")] NothingToClaim {}, diff --git a/packages/dao-voting/src/duration.rs b/packages/dao-voting/src/duration.rs new file mode 100644 index 000000000..bea32880d --- /dev/null +++ b/packages/dao-voting/src/duration.rs @@ -0,0 +1,26 @@ +use cw_utils::Duration; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum UnstakingDurationError { + #[error("Invalid unstaking duration, unstaking duration cannot be 0")] + InvalidUnstakingDuration {}, +} + +pub fn validate_duration(duration: Option) -> Result<(), UnstakingDurationError> { + if let Some(unstaking_duration) = duration { + match unstaking_duration { + Duration::Height(height) => { + if height == 0 { + return Err(UnstakingDurationError::InvalidUnstakingDuration {}); + } + } + Duration::Time(time) => { + if time == 0 { + return Err(UnstakingDurationError::InvalidUnstakingDuration {}); + } + } + } + } + Ok(()) +} diff --git a/packages/dao-voting/src/lib.rs b/packages/dao-voting/src/lib.rs index 208cfc468..7395691b2 100644 --- a/packages/dao-voting/src/lib.rs +++ b/packages/dao-voting/src/lib.rs @@ -1,6 +1,7 @@ #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] pub mod deposit; +pub mod duration; pub mod error; pub mod multiple_choice; pub mod pre_propose;