From 4b1398cbd9897b2e0f919ea321748cb8fdeac734 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 13:53:37 -0700 Subject: [PATCH 01/59] Add missing hooks to dao-voting-native-staked, start adding tests --- .../dao-voting-native-staked/src/contract.rs | 10 +++++ .../dao-voting-native-staked/src/tests.rs | 43 ++++++++++++++++++- .../src/contract.rs | 4 ++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs index 80a413a9f..0fb85acb6 100644 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ b/contracts/voting/dao-voting-native-staked/src/contract.rs @@ -13,6 +13,7 @@ use dao_interface::voting::{ use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; use crate::error::ContractError; +use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; use crate::msg::{ DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, StakerBalanceResponse, @@ -138,7 +139,11 @@ pub fn execute_stake( |total| -> StdResult { Ok(total.unwrap_or_default().checked_add(amount)?) }, )?; + // Add stake hook messages + let hook_msgs = stake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + Ok(Response::new() + .add_submessages(hook_msgs) .add_attribute("action", "stake") .add_attribute("amount", amount.to_string()) .add_attribute("from", info.sender)) @@ -176,6 +181,9 @@ pub fn execute_unstake( }, )?; + // Add unstake hook messages + let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + let config = CONFIG.load(deps.storage)?; match config.unstaking_duration { None => { @@ -185,6 +193,7 @@ pub fn execute_unstake( }); Ok(Response::new() .add_message(msg) + .add_submessages(hook_msgs) .add_attribute("action", "unstake") .add_attribute("from", info.sender) .add_attribute("amount", amount) @@ -203,6 +212,7 @@ pub fn execute_unstake( duration.after(&env.block), )?; Ok(Response::new() + .add_submessages(hook_msgs) .add_attribute("action", "unstake") .add_attribute("from", info.sender) .add_attribute("amount", amount) diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs index 75a3b5672..df33b16d6 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-native-staked/src/tests.rs @@ -1261,7 +1261,48 @@ fn test_add_remove_hooks() { } #[test] -pub fn test_migrate_update_version() { +fn test_staking_hooks() { + let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + denom: DENOM.to_string(), + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Add a staking hook. + app.execute_contract( + Addr::unchecked(DAO_ADDR), + addr.clone(), + &ExecuteMsg::AddHook { + addr: "hook".to_string(), + }, + &[], + ) + .unwrap(); + + // TODO need a contract to recieve the message + // Stake some tokens + let res = stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + + // Make sure hook is included in response + println!("stake hooks {:?}", res); + + app.update_block(next_block); + + // Unstake some + let res = unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); + + // Make sure hook is included in response + println!("unstake hooks {:?}", res); +} + +#[test] +fn test_migrate_update_version() { let mut deps = mock_dependencies(); cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); 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 60f8b8cb3..3a0f166a0 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -197,6 +197,8 @@ pub fn execute_stake( env.block.height, |total| -> StdResult { Ok(total.unwrap_or_default().checked_add(amount)?) }, )?; + + // Add stake hook messages let hook_msgs = stake_hook_msgs(deps.storage, info.sender.clone(), amount)?; Ok(Response::::new() @@ -237,6 +239,8 @@ pub fn execute_unstake( .map_err(|_e| ContractError::InvalidUnstakeAmount {}) }, )?; + + // Add unstake hook messages let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), amount)?; let config = CONFIG.load(deps.storage)?; From de50a62492bbe56b743d566ff920d32538286ed9 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 13:59:30 -0700 Subject: [PATCH 02/59] Fix group contract attribute key emits "address" --- contracts/voting/dao-voting-cw4/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index efa311007..39d36de34 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -99,7 +99,7 @@ pub fn instantiate( Ok(Response::new() .add_attribute("action", "instantiate") - .add_attribute("group_contract", "address")) + .add_attribute("group_contract", group_contract.to_string())) } } } From fbad6e77de3c0769063a3d117e080f66d5e73383 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 18:07:46 -0700 Subject: [PATCH 03/59] Fix absoluteCount can be configured to be greater than the total NFT supply --- .../dao-voting-cw721-staked/src/contract.rs | 22 +++++- .../dao-voting-cw721-staked/src/error.rs | 3 + .../src/testing/tests.rs | 74 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 613edc917..ef53f0084 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -13,7 +13,7 @@ use cosmwasm_std::{ MessageInfo, Reply, Response, StdError, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; use cw2::set_contract_version; -use cw721::{Cw721ReceiveMsg, NumTokensResponse}; +use cw721::{Cw721QueryMsg, Cw721ReceiveMsg, NumTokensResponse}; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; use dao_interface::state::Admin; @@ -93,9 +93,20 @@ pub fn instantiate( } } ActiveThreshold::AbsoluteCount { count } => { + // Check Absolute count is not zero if count.is_zero() { return Err(ContractError::ZeroActiveCount {}); } + + // Check Absolute count is less than the supply of NFTs for existing NFT contracts + if let NftContract::Existing { ref address } = msg.nft_contract { + let nft_supply: NumTokensResponse = deps + .querier + .query_wasm_smart(address, &Cw721QueryMsg::NumTokens {})?; + if count > &Uint128::new(nft_supply.count.into()) { + return Err(ContractError::InvalidActiveCount {}); + } + } } } ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; @@ -642,6 +653,15 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Uint128::new(initial_nfts.len() as u128) { + return Err(ContractError::InvalidActiveCount {}); + } + } + } + // Add mint submessages let mint_submessages: Vec = initial_nfts .iter() diff --git a/contracts/voting/dao-voting-cw721-staked/src/error.rs b/contracts/voting/dao-voting-cw721-staked/src/error.rs index 10fb9a2db..6135b1fa8 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/error.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/error.rs @@ -12,6 +12,9 @@ pub enum ContractError { #[error(transparent)] HookError(#[from] cw_controllers::HookError), + #[error("Active threshold count is greater than supply")] + InvalidActiveCount {}, + #[error("Active threshold percentage must be greater than 0 and less than 1")] InvalidActivePercentage {}, diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs index 85b984636..b8a52ebab 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs @@ -24,6 +24,7 @@ use crate::{ }, }; +use super::instantiate::instantiate_cw721_base; use super::{ execute::{add_hook, remove_hook}, is_error, @@ -502,6 +503,79 @@ fn test_instantiate_zero_active_threshold_count() { .unwrap(); } +#[test] +#[should_panic(expected = "Active threshold count is greater than supply")] +fn test_instantiate_invalid_active_threshold_count_new_nft() { + let mut app = App::default(); + let cw721_id = app.store_code(cw721_base_contract()); + let module_id = app.store_code(voting_cw721_staked_contract()); + + app.instantiate_contract( + module_id, + Addr::unchecked(CREATOR_ADDR), + &InstantiateMsg { + owner: Some(Admin::Address { + addr: CREATOR_ADDR.to_string(), + }), + nft_contract: NftContract::New { + code_id: cw721_id, + label: "Test NFT".to_string(), + msg: to_binary(&Cw721InstantiateMsg { + name: "Test NFT".to_string(), + symbol: "TEST".to_string(), + minter: CREATOR_ADDR.to_string(), + }) + .unwrap(), + initial_nfts: vec![to_binary(&Cw721ExecuteMsg::::Mint { + owner: CREATOR_ADDR.to_string(), + token_uri: Some("https://example.com".to_string()), + token_id: "1".to_string(), + extension: Empty {}, + }) + .unwrap()], + }, + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(100), + }), + }, + &[], + "cw721_voting", + None, + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Active threshold count is greater than supply")] +fn test_instantiate_invalid_active_threshold_count_existing_nft() { + let mut app = App::default(); + let cw721_id = app.store_code(cw721_base_contract()); + let module_id = app.store_code(voting_cw721_staked_contract()); + let cw721_addr = instantiate_cw721_base(&mut app, CREATOR_ADDR, CREATOR_ADDR); + + app.instantiate_contract( + module_id, + Addr::unchecked(CREATOR_ADDR), + &InstantiateMsg { + owner: Some(Admin::Address { + addr: CREATOR_ADDR.to_string(), + }), + nft_contract: NftContract::Existing { + address: cw721_addr.to_string(), + }, + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(100), + }), + }, + &[], + "cw721_voting", + None, + ) + .unwrap(); +} + #[test] fn test_active_threshold_absolute_count() { let mut app = App::default(); From 2a50a0baa04035a798654e2ca6e9d7eaef0f11a0 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 18:20:45 -0700 Subject: [PATCH 04/59] Fix absoluteCount threshold for a new token is not validated --- .../src/testing/tests.rs | 1 - .../src/contract.rs | 17 +++++++ .../src/tests/test_tube/integration_tests.rs | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs index b8a52ebab..3254f6ef3 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs @@ -550,7 +550,6 @@ fn test_instantiate_invalid_active_threshold_count_new_nft() { #[should_panic(expected = "Active threshold count is greater than supply")] fn test_instantiate_invalid_active_threshold_count_existing_nft() { let mut app = App::default(); - let cw721_id = app.store_code(cw721_base_contract()); let module_id = app.store_code(voting_cw721_staked_contract()); let cw721_addr = instantiate_cw721_base(&mut app, CREATOR_ADDR, CREATOR_ADDR); 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 3a0f166a0..f6213c2ec 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -78,7 +78,11 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; DAO.save(deps.storage, &info.sender)?; + // Validate Active Threshold if let Some(active_threshold) = msg.active_threshold.as_ref() { + // Only check active threshold percentage as new tokens don't exist yet + // We will check Absolute count (if configured) later for both existing + // and new tokens. if let ActiveThreshold::Percentage { percent } = active_threshold { if *percent > Decimal::percent(100) || *percent <= Decimal::percent(0) { return Err(ContractError::InvalidActivePercentage {}); @@ -92,6 +96,7 @@ pub fn instantiate( match msg.token_info { TokenInfo::Existing { denom } => { + // Validate active threshold absolute count if configured if let Some(ActiveThreshold::AbsoluteCount { count }) = msg.active_threshold { assert_valid_absolute_count_threshold(deps.as_ref(), &denom, count)?; } @@ -610,6 +615,18 @@ pub fn reply( let total_supply = initial_supply + token.initial_dao_balance.unwrap_or_default(); + // Validate active threshold absolute count if configured + if let Some(ActiveThreshold::AbsoluteCount { count }) = + ACTIVE_THRESHOLD.may_load(deps.storage)? + { + if count.is_zero() { + return Err(ContractError::ZeroActiveCount {}); + } + if count > initial_supply { + return Err(ContractError::InvalidAbsoluteCount {}); + } + } + // Cannot instantiate with no initial token owners because it would // immediately lock the DAO. if initial_supply.is_zero() { diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs index 08573a542..67fe8d18d 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{Coin, Uint128}; use cw_tokenfactory_issuer::msg::DenomUnit; +use dao_voting::threshold::ActiveThreshold; use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_test_tube::{Account, OsmosisTestApp}; @@ -202,6 +203,50 @@ fn test_instantiate_invalid_metadata_fails() { .unwrap_err(); } +#[test] +fn test_instantiate_invalid_active_threshold_count_fails() { + let app = OsmosisTestApp::new(); + let env = TestEnvBuilder::new().default_setup(&app); + let tf_issuer_id = env.get_tf_issuer_code_id(); + + let dao = app + .init_account(&[Coin::new(100000000000, "uosmo")]) + .unwrap(); + + // TODO verify error is correct + env.instantiate( + &InstantiateMsg { + token_issuer_code_id: tf_issuer_id, + token_info: TokenInfo::New(NewTokenInfo { + subdenom: "cat".to_string(), + metadata: Some(NewDenomMetadata { + description: "Awesome token, get it meow!".to_string(), + additional_denom_units: Some(vec![DenomUnit { + denom: "cat".to_string(), + // Exponent 0 is automatically set + exponent: 0, + aliases: vec![], + }]), + display: "cat".to_string(), + name: "Cat Token".to_string(), + symbol: "CAT".to_string(), + }), + initial_balances: vec![InitialBalance { + amount: Uint128::new(100), + address: env.accounts[0].address(), + }], + initial_dao_balance: None, + }), + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(1000), + }), + }, + dao, + ) + .unwrap_err(); +} + #[test] fn test_instantiate_no_initial_balances_fails() { let app = OsmosisTestApp::new(); From cdb8da2ea37085991132f716bf18ac022ef4350e Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 18:28:36 -0700 Subject: [PATCH 05/59] Fix issuer contract can be blacklisted --- .../cw-tokenfactory-issuer/src/contract.rs | 2 +- .../external/cw-tokenfactory-issuer/src/error.rs | 3 +++ .../cw-tokenfactory-issuer/src/execute.rs | 6 ++++++ .../tests/cases/blacklist.rs | 15 +++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 144486cb2..561f18ac6 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -88,7 +88,7 @@ pub fn execute( from_address: address, } => execute::burn(deps, env, info, amount, address), ExecuteMsg::Blacklist { address, status } => { - execute::blacklist(deps, info, address, status) + execute::blacklist(deps, env, info, address, status) } ExecuteMsg::Whitelist { address, status } => { execute::whitelist(deps, info, address, status) diff --git a/contracts/external/cw-tokenfactory-issuer/src/error.rs b/contracts/external/cw-tokenfactory-issuer/src/error.rs index 221b3fd0a..7d5c03c86 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/error.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/error.rs @@ -15,6 +15,9 @@ pub enum ContractError { #[error("The address '{address}' is blacklisted")] Blacklisted { address: String }, + #[error("Cannot blacklist the issuer contract itself")] + CannotBlacklistSelf {}, + #[error("The contract is frozen for denom {denom:?}")] ContractFrozen { denom: String }, diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 916a11d9b..85ea1e4e8 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -380,6 +380,7 @@ pub fn freeze( pub fn blacklist( deps: DepsMut, + env: Env, info: MessageInfo, address: String, status: bool, @@ -391,6 +392,11 @@ pub fn blacklist( let address = deps.api.addr_validate(&address)?; + // Check this issuer contract is not blacklisting itself + if address == env.contract.address { + return Err(ContractError::CannotBlacklistSelf {}); + } + // update blacklisted status // validate that blacklisteed is a valid address // NOTE: Does not check if new status is same as old status diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs index ceb738ba8..417acba83 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs @@ -146,6 +146,21 @@ fn blacklist_by_blacklister_should_pass() { ); } +#[test] +fn blacklist_issuer_itself_fails() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; + + env.cw_tokenfactory_issuer + .set_blacklister(&non_owner.address(), true, owner) + .unwrap(); + // TODO check the error message and make sure this is correct + env.cw_tokenfactory_issuer + .blacklist(&env.cw_tokenfactory_issuer.address, true, non_owner) + .unwrap_err(); +} + #[test] fn blacklist_by_non_blacklister_should_fail() { let env = TestEnv::default(); From 74420d16917a9c7493c7a1b791edc3b57cea6815 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 20:20:48 -0700 Subject: [PATCH 06/59] Fix Stargaze collection info bug, and absolute count validation sg721 has two addresses that control it. The `minter` which can mint NFTs, and the `creator` which can update collection metadata and royalties. We now set both of those to be the DAO. --- .../dao-voting-cw721-staked/src/contract.rs | 91 +++++++++----- .../src/testing/tests.rs | 119 ++++++++++++++---- 2 files changed, 158 insertions(+), 52 deletions(-) diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index ef53f0084..3bc7dcc6d 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -24,6 +24,7 @@ pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-cw721-staked"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const INSTANTIATE_NFT_CONTRACT_REPLY_ID: u64 = 0; +const VALIDATE_ABSOLUTE_COUNT_FOR_NEW_NFT_CONTRACTS: u64 = 1; // We multiply by this when calculating needed power for being active // when using active threshold with percent @@ -36,10 +37,19 @@ pub enum NftInstantiateMsg { } impl NftInstantiateMsg { - fn update_minter(&mut self, minter: &str) { + fn modify_instantiate_msg(&mut self, minter: &str, dao: &str) { match self { + // Update minter for cw721 NFTs NftInstantiateMsg::Cw721(msg) => msg.minter = minter.to_string(), - NftInstantiateMsg::Sg721(msg) => msg.minter = minter.to_string(), + NftInstantiateMsg::Sg721(msg) => { + // Update minter and collection creator for sg721 NFTs + // The collection creator is the only one able to call certain methods + // in sg721 contracts + msg.minter = minter.to_string(); + // This should be the DAO, which will be able to control metadata about + // the collection as well as royalties + msg.collection_info.creator = dao.to_string(); + } } } @@ -141,8 +151,15 @@ pub fn instantiate( } => { // Deserialize the binary msg into either cw721 or sg721 let mut instantiate_msg = try_deserialize_nft_instantiate_msg(instantiate_msg)?; - // Update the minter to be this contract - instantiate_msg.update_minter(env.contract.address.as_str()); + + // Modify the InstantiateMsg such that the minter is now this contract. + // We will update ownership of the NFT contract to be the DAO in the submessage reply. + // + // NOTE: sg721 also has a creator that is set in the `collection_info` field, + // we override this with the address of the DAO (the sender of this message). + // In sg721 the `creator` address controls metadata and royalties. + instantiate_msg + .modify_instantiate_msg(env.contract.address.as_str(), info.sender.as_str()); // Check there is at least one NFT to initialize if initial_nfts.is_empty() { @@ -653,17 +670,8 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Uint128::new(initial_nfts.len() as u128) { - return Err(ContractError::InvalidActiveCount {}); - } - } - } - // Add mint submessages - let mint_submessages: Vec = initial_nfts + let mut submessages: Vec = initial_nfts .iter() .flat_map(|nft| -> Result { Ok(SubMsg::new(WasmMsg::Execute { @@ -677,29 +685,54 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result::UpdateOwnership( - cw721_base::Action::TransferOwnership { - new_owner: dao.to_string(), - expiry: None, - }, - ), - )?, - funds: vec![], - }; + // Last submessage updates owner. + // The reply is used for validation after setup. + submessages.push(SubMsg::reply_on_success( + WasmMsg::Execute { + contract_addr: nft_contract.clone(), + msg: to_binary( + &cw721_base::msg::ExecuteMsg::::UpdateOwnership( + cw721_base::Action::TransferOwnership { + new_owner: dao.to_string(), + expiry: None, + }, + ), + )?, + funds: vec![], + }, + VALIDATE_ABSOLUTE_COUNT_FOR_NEW_NFT_CONTRACTS, + )); Ok(Response::default() .add_attribute("method", "instantiate") .add_attribute("nft_contract", nft_contract) - .add_message(update_minter_msg) - .add_submessages(mint_submessages)) + .add_submessages(submessages)) } Err(_) => Err(ContractError::NftInstantiateError {}), } } + VALIDATE_ABSOLUTE_COUNT_FOR_NEW_NFT_CONTRACTS => { + // Check that absolute count is not greater than supply + // NOTE: we have to check this in a reply as it is potentially possible + // to include non-mint messages in `initial_nfts`. + if let Some(active_threshold) = ACTIVE_THRESHOLD.may_load(deps.storage)? { + if let ActiveThreshold::AbsoluteCount { count } = active_threshold { + // Load config for nft contract address + let collection_addr = CONFIG.load(deps.storage)?.nft_address; + + // Query the total supply of the NFT contract + let supply: NumTokensResponse = deps + .querier + .query_wasm_smart(collection_addr, &Cw721QueryMsg::NumTokens {})?; + + // Chec the count is not greater than supply + if count > Uint128::new(supply.count.into()) { + return Err(ContractError::InvalidActiveCount {}); + } + } + } + Ok(Response::new()) + } _ => Err(ContractError::UnknownReplyId { id: msg.id }), } } diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs index 3254f6ef3..1783f2e6c 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs @@ -8,7 +8,8 @@ use cw_utils::Duration; use dao_interface::{state::Admin, voting::IsActiveResponse}; use dao_testing::contracts::{cw721_base_contract, voting_cw721_staked_contract}; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -use sg721::CollectionInfo; +use sg721::{CollectionInfo, RoyaltyInfoResponse, UpdateCollectionInfoMsg}; +use sg721_base::msg::CollectionInfoResponse; use sg_multi_test::StargazeApp; use sg_std::StargazeMsgWrapper; @@ -1074,30 +1075,30 @@ fn test_no_initial_nfts_fails() { ); } +// Setup Stargaze contracts for multi-test +fn sg721_base_contract() -> Box> { + let contract = ContractWrapper::new( + sg721_base::entry::execute, + sg721_base::entry::instantiate, + sg721_base::entry::query, + ); + Box::new(contract) +} + +// Stargze contracts need a custom message wrapper +fn voting_sg721_staked_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ) + .with_reply_empty(crate::contract::reply); + Box::new(contract) +} + // I can create new Stargaze NFT collection when creating a dao-voting-cw721-staked contract #[test] fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { - // Setup Stargaze contracts for multi-test - fn sg721_base_contract() -> Box> { - let contract = ContractWrapper::new( - sg721_base::entry::execute, - sg721_base::entry::instantiate, - sg721_base::entry::query, - ); - Box::new(contract) - } - - // Stargze contracts need a custom message wrapper - fn voting_sg721_staked_contract() -> Box> { - let contract = ContractWrapper::new_with_empty( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply_empty(crate::contract::reply); - Box::new(contract) - } - let mut app = StargazeApp::default(); let module_id = app.store_code(voting_sg721_staked_contract()); let sg721_id = app.store_code(sg721_base_contract()); @@ -1150,7 +1151,7 @@ fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { // Check that the NFT contract was created let owner: OwnerOfResponse = app.wrap().query_wasm_smart( - sg721_addr, + sg721_addr.clone(), &cw721::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, @@ -1158,9 +1159,81 @@ fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { )?; assert_eq!(owner.owner, CREATOR_ADDR); + // Check that collection info creator is set to the DAO (in this case CREATOR_ADDR) + // Normally the DAO would instantiate this contract + let creator: CollectionInfoResponse = app + .wrap() + .query_wasm_smart(sg721_addr, &sg721_base::msg::QueryMsg::CollectionInfo {})?; + assert_eq!(creator.creator, CREATOR_ADDR.to_string()); + Ok(()) } +#[test] +#[should_panic(expected = "Active threshold count is greater than supply")] +fn test_instantiate_with_new_sg721_collection_abs_count_validation() { + let mut app = StargazeApp::default(); + let module_id = app.store_code(voting_sg721_staked_contract()); + let sg721_id = app.store_code(sg721_base_contract()); + + // Test edge case + app.instantiate_contract( + module_id, + Addr::unchecked("contract0"), + &InstantiateMsg { + owner: Some(Admin::Address { + addr: "contract0".to_string(), + }), + nft_contract: NftContract::New { + code_id: sg721_id, + label: "Test NFT".to_string(), + msg: to_binary(&sg721::InstantiateMsg { + name: "Test NFT".to_string(), + symbol: "TEST".to_string(), + minter: "contract0".to_string(), + collection_info: CollectionInfo { + creator: "contract0".to_string(), + description: "Test NFT".to_string(), + image: "https://example.com/image.jpg".to_string(), + external_link: None, + explicit_content: None, + start_trading_time: None, + royalty_info: None, + }, + }) + .unwrap(), + initial_nfts: vec![ + to_binary(&sg721::ExecuteMsg::::Mint { + owner: "contract0".to_string(), + token_uri: Some("https://example.com".to_string()), + token_id: "1".to_string(), + extension: Empty {}, + }) + .unwrap(), + to_binary(&sg721::ExecuteMsg::::UpdateCollectionInfo { + collection_info: UpdateCollectionInfoMsg:: { + description: None, + image: None, + external_link: None, + explicit_content: None, + royalty_info: None, + }, + }) + .unwrap(), + ], + }, + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(2), + }), + }, + &[], + "cw721_voting", + None, + ) + .unwrap(); +} + #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); From 583db0cb848c3dc1bed22a4df2fbaf2f0c089723 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 20:30:05 -0700 Subject: [PATCH 07/59] Fix incorrect events are emitted for whitelist --- .../external/cw-tokenfactory-issuer/src/execute.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 85ea1e4e8..8f5ab9148 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -136,7 +136,6 @@ pub fn change_contract_owner( // update the contract owner in the contract config OWNER.save(deps.storage, &new_owner_addr)?; - // return OK Ok(Response::new() .add_attribute("action", "change_contract_owner") .add_attribute("new_owner", new_owner)) @@ -205,7 +204,6 @@ pub fn set_blacklister( BLACKLISTER_ALLOWANCES.remove(deps.storage, &address); } - // Return OK Ok(Response::new() .add_attribute("action", "set_blacklister") .add_attribute("blacklister", address) @@ -263,7 +261,6 @@ pub fn set_freezer( FREEZER_ALLOWANCES.remove(deps.storage, &address); } - // return OK Ok(Response::new() .add_attribute("action", "set_freezer") .add_attribute("freezer", address) @@ -324,7 +321,6 @@ pub fn set_burner( BURNER_ALLOWANCES.save(deps.storage, &address, &allowance)?; } - // return OK Ok(Response::new() .add_attribute("action", "set_burner") .add_attribute("burner", address) @@ -351,7 +347,6 @@ pub fn set_minter( MINTER_ALLOWANCES.save(deps.storage, &address, &allowance)?; } - // return OK Ok(Response::new() .add_attribute("action", "set_minter") .add_attribute("minter", address) @@ -372,7 +367,6 @@ pub fn freeze( // NOTE: Does not check if new status is same as old status IS_FROZEN.save(deps.storage, &status)?; - // return OK Ok(Response::new() .add_attribute("action", "freeze") .add_attribute("status", status.to_string())) @@ -407,7 +401,6 @@ pub fn blacklist( BLACKLISTED_ADDRESSES.remove(deps.storage, &address); } - // return OK Ok(Response::new() .add_attribute("action", "blacklist") .add_attribute("address", address) @@ -437,9 +430,8 @@ pub fn whitelist( WHITELISTED_ADDRESSES.remove(deps.storage, &address); } - // return OK Ok(Response::new() - .add_attribute("action", "blacklist") + .add_attribute("action", "whitelist") .add_attribute("address", address) .add_attribute("status", status.to_string())) } From c82fdd3f41f2d21c30b834010f54409c3082866c Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 20:44:28 -0700 Subject: [PATCH 08/59] Fix lack of denom validation --- .../dao-voting-native-staked/src/contract.rs | 19 +++++++++++++------ .../dao-voting-native-staked/src/error.rs | 3 +++ .../dao-voting-native-staked/src/tests.rs | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs index 0fb85acb6..a4e5b44a7 100644 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ b/contracts/voting/dao-voting-native-staked/src/contract.rs @@ -66,10 +66,18 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; DAO.save(deps.storage, &info.sender)?; + // Validate denom by checking supply + let supply: Coin = deps.querier.query_supply(msg.denom.to_string())?; + println!("supply {:?}", supply); + if Uint128::is_zero(&supply.amount) { + return Err(ContractError::InvalidDenom {}); + } + + // Validate active threshold if let Some(active_threshold) = msg.active_threshold.as_ref() { match active_threshold { ActiveThreshold::AbsoluteCount { count } => { - assert_valid_absolute_count_threshold(deps.as_ref(), &msg.denom, *count)?; + assert_valid_absolute_count_threshold(*count, supply.amount)?; } ActiveThreshold::Percentage { percent } => { if *percent > Decimal::percent(100) || *percent <= Decimal::percent(0) { @@ -85,15 +93,13 @@ pub fn instantiate( } pub fn assert_valid_absolute_count_threshold( - deps: Deps, - token_denom: &str, count: Uint128, + supply: Uint128, ) -> Result<(), ContractError> { if count.is_zero() { return Err(ContractError::ZeroActiveCount {}); } - let supply: Coin = deps.querier.query_supply(token_denom.to_string())?; - if count > supply.amount { + if count > supply { return Err(ContractError::InvalidAbsoluteCount {}); } Ok(()) @@ -285,7 +291,8 @@ pub fn execute_update_active_threshold( } ActiveThreshold::AbsoluteCount { count } => { let denom = CONFIG.load(deps.storage)?.denom; - assert_valid_absolute_count_threshold(deps.as_ref(), &denom, count)?; + let supply: Coin = deps.querier.query_supply(denom.to_string())?; + assert_valid_absolute_count_threshold(count, supply.amount)?; } } ACTIVE_THRESHOLD.save(deps.storage, &active_threshold)?; diff --git a/contracts/voting/dao-voting-native-staked/src/error.rs b/contracts/voting/dao-voting-native-staked/src/error.rs index b87342fb4..9829c2a07 100644 --- a/contracts/voting/dao-voting-native-staked/src/error.rs +++ b/contracts/voting/dao-voting-native-staked/src/error.rs @@ -16,6 +16,9 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized {}, + #[error("Denom does not exist on chain")] + InvalidDenom {}, + #[error("Invalid unstaking duration, unstaking duration cannot be 0")] InvalidUnstakingDuration {}, diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs index df33b16d6..cb45c8594 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-native-staked/src/tests.rs @@ -243,6 +243,25 @@ fn test_instantiate_invalid_unstaking_duration_height() { ); } +#[test] +#[should_panic(expected = "Denom does not exist on chain")] +fn test_instantiate_invalid_denom() { + let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); + + // Populated fields with height + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + // This denom has zero supply + denom: "uinvalid2".to_string(), + unstaking_duration: None, + active_threshold: None, + }, + ); +} + #[test] #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] fn test_instantiate_invalid_unstaking_duration_time() { From d3ad4871917af5204eb49e43bd80684a9f499bd9 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 20:56:57 -0700 Subject: [PATCH 09/59] Fix BEFORE_SEND_HOOK_FEATURES_ENABLED is not exposed through smart queries --- .../schema/cw-tokenfactory-issuer.json | 19 +++++++++++++++++++ .../cw-tokenfactory-issuer/src/contract.rs | 3 +++ .../cw-tokenfactory-issuer/src/msg.rs | 4 ++++ .../cw-tokenfactory-issuer/src/queries.rs | 9 +++++++-- .../tests/cases/set_before_update_hook.rs | 8 ++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json index 42ade5b4f..50c2489fe 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -908,6 +908,20 @@ } }, "additionalProperties": false + }, + { + "description": "Returns whether features that require MsgBeforeSendHook are enabled Most Cosmos chains do not support this feature yet.", + "type": "object", + "required": [ + "before_send_hook_features_enabled" + ], + "properties": { + "before_send_hook_features_enabled": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -970,6 +984,11 @@ } }, "responses": { + "before_send_hook_features_enabled": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" + }, "blacklistees": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "BlacklisteesResponse", diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 561f18ac6..d73f34743 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -188,6 +188,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResu QueryMsg::FreezerAllowances { start_after, limit } => to_binary( &queries::query_freezer_allowances(deps, start_after, limit)?, ), + QueryMsg::BeforeSendHookFeaturesEnabled {} => { + to_binary(&queries::query_before_send_hook_features(deps)?) + } } } diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 6d0bc0eea..249c0c7aa 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -166,6 +166,10 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + /// Returns whether features that require MsgBeforeSendHook are enabled + /// Most Cosmos chains do not support this feature yet. + #[returns(bool)] + BeforeSendHookFeaturesEnabled {}, } // We define a custom struct for each query response diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index be3d4f14f..827d64e5b 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -8,8 +8,9 @@ use crate::msg::{ OwnerResponse, StatusInfo, StatusResponse, WhitelisteesResponse, WhitelisterAllowancesResponse, }; use crate::state::{ - BLACKLISTED_ADDRESSES, BLACKLISTER_ALLOWANCES, BURNER_ALLOWANCES, DENOM, FREEZER_ALLOWANCES, - IS_FROZEN, MINTER_ALLOWANCES, OWNER, WHITELISTED_ADDRESSES, WHITELISTER_ALLOWANCES, + BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTER_ALLOWANCES, + BURNER_ALLOWANCES, DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, + WHITELISTED_ADDRESSES, WHITELISTER_ALLOWANCES, }; // Default settings for pagination @@ -232,4 +233,8 @@ pub fn query_is_freezer( Ok(StatusResponse { status }) } +pub fn query_before_send_hook_features(deps: Deps) -> StdResult { + BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage) +} + // query inspiration see https://github.com/mars-protocol/fields-of-mars/blob/v1.0.0/packages/fields-of-mars/src/martian_field.rs#L465-L473 diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs index 39299fd93..e5a2ef240 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs @@ -1,5 +1,6 @@ use cw_tokenfactory_issuer::ContractError; +use crate::msg::QueryMsg; use crate::test_env::{TestEnv, TokenfactoryIssuer}; #[test] @@ -29,4 +30,11 @@ fn test_set_before_update_hook() { err, TokenfactoryIssuer::execute_error(ContractError::BeforeSendHookAlreadyEnabled {}) ); + + // Query before update hook + let enabled: bool = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookFeaturesEnabled {}) + .unwrap(); + assert!(enabled); } From c6cf16d94d36d98bf3fd5f4afb5ec86f975c572e Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 20:59:31 -0700 Subject: [PATCH 10/59] Fix misleading from attribute when burning funds --- contracts/external/cw-tokenfactory-issuer/src/execute.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 8f5ab9148..6c0d7e86d 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -118,7 +118,8 @@ pub fn burn( Ok(Response::new() .add_message(burn_tokens_msg) .add_attribute("action", "burn") - .add_attribute("from", info.sender) + .add_attribute("burner", info.sender) + .add_attribute("burn_from_address", burn_from_address.to_string()) .add_attribute("amount", amount)) } From c02a52a2c611857ef556ea4869edd9cfabc324be Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 21:12:09 -0700 Subject: [PATCH 11/59] Fix broken tests from denom validation --- .../dao-proposal-single/src/testing/do_votes.rs | 15 +++++++++++++-- .../voting/dao-voting-native-staked/src/tests.rs | 12 ++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs index ecd72e325..a97e23c23 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs @@ -1,7 +1,7 @@ -use cosmwasm_std::{coins, Addr, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Uint128}; use cw20::Cw20Coin; -use cw_multi_test::{App, BankSudo, Executor}; +use cw_multi_test::{App, BankSudo, Executor, SudoMsg}; use dao_interface::state::ProposalModule; use dao_pre_propose_single as cppbps; @@ -96,6 +96,17 @@ where { let mut app = App::default(); + // Mint some ujuno so that it exists for native staking tests + // Otherwise denom validation will fail + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: "sodenomexists".to_string(), + amount: vec![Coin { + amount: Uint128::new(10), + denom: "ujuno".to_string(), + }], + })) + .unwrap(); + let mut initial_balances = votes .iter() .map(|TestSingleChoiceVote { voter, weight, .. }| Cw20Coin { diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs index cb45c8594..52e6d0023 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-native-staked/src/tests.rs @@ -1046,7 +1046,7 @@ fn test_active_threshold_percent_rounds_up() { #[test] fn test_active_threshold_none() { - let mut app = App::default(); + let mut app = mock_app(); let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, @@ -1140,7 +1140,7 @@ fn test_update_active_threshold() { #[test] #[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] fn test_active_threshold_percentage_gt_100() { - let mut app = App::default(); + let mut app = mock_app(); let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, @@ -1158,7 +1158,7 @@ fn test_active_threshold_percentage_gt_100() { #[test] #[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] fn test_active_threshold_percentage_lte_0() { - let mut app = App::default(); + let mut app = mock_app(); let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, @@ -1176,7 +1176,7 @@ fn test_active_threshold_percentage_lte_0() { #[test] #[should_panic(expected = "Absolute count threshold cannot be greater than the total token supply")] fn test_active_threshold_absolute_count_invalid() { - let mut app = App::default(); + let mut app = mock_app(); let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, @@ -1185,7 +1185,7 @@ fn test_active_threshold_absolute_count_invalid() { denom: DENOM.to_string(), unstaking_duration: Some(Duration::Height(5)), active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(301), + count: Uint128::new(3000000000000000000), }), }, ); @@ -1193,7 +1193,7 @@ fn test_active_threshold_absolute_count_invalid() { #[test] fn test_add_remove_hooks() { - let mut app = App::default(); + let mut app = mock_app(); let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, From 56159d2d6595478982d2b957295ea08fedcda569 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 21:18:57 -0700 Subject: [PATCH 12/59] Fix inconsistent attribute namings and orderings --- .../external/cw-tokenfactory-issuer/src/execute.rs | 2 +- contracts/voting/dao-voting-cw4/src/contract.rs | 2 +- .../voting/dao-voting-cw721-staked/src/contract.rs | 10 +++++----- .../dao-voting-token-factory-staked/src/contract.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 6c0d7e86d..3f0701ade 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -351,7 +351,7 @@ pub fn set_minter( Ok(Response::new() .add_attribute("action", "set_minter") .add_attribute("minter", address) - .add_attribute("amount", allowance)) + .add_attribute("allowance", allowance)) } pub fn freeze( diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index 39d36de34..a32a83d91 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -196,7 +196,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Err(ContractError::GroupContractInstantiateError {}), diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 3bc7dcc6d..e9d212052 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -135,13 +135,13 @@ pub fn instantiate( Ok(Response::default() .add_attribute("method", "instantiate") - .add_attribute("nft_contract", address) .add_attribute( "owner", owner .map(|a| a.into_string()) .unwrap_or_else(|| "None".to_string()), - )) + ) + .add_attribute("nft_contract", address)) } NftContract::New { code_id, @@ -190,13 +190,14 @@ pub fn instantiate( ); Ok(Response::default() - .add_submessage(instantiate_msg) + .add_attribute("method", "instantiate") .add_attribute( "owner", owner .map(|a| a.into_string()) .unwrap_or_else(|| "None".to_string()), - )) + ) + .add_submessage(instantiate_msg)) } } } @@ -704,7 +705,6 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result::new() .add_attribute("action", "instantiate") .add_attribute("token", "existing_token") - .add_attribute("token_denom", denom) + .add_attribute("denom", denom) .add_submessage(issuer_instantiate_msg)) } TokenInfo::New(token) => { From 27c1602a65ffd8e0cc539065b8b2174207171ae9 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 21:20:15 -0700 Subject: [PATCH 13/59] Fix INITITIAL_NFTS spelling XD --- contracts/voting/dao-voting-cw721-roles/src/contract.rs | 8 ++++---- contracts/voting/dao-voting-cw721-roles/src/state.rs | 2 +- contracts/voting/dao-voting-cw721-staked/src/contract.rs | 8 ++++---- contracts/voting/dao-voting-cw721-staked/src/state.rs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/voting/dao-voting-cw721-roles/src/contract.rs b/contracts/voting/dao-voting-cw721-roles/src/contract.rs index 62d6ba185..9d0f8852d 100644 --- a/contracts/voting/dao-voting-cw721-roles/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-roles/src/contract.rs @@ -14,7 +14,7 @@ use cw_utils::parse_reply_instantiate_data; use dao_cw721_extensions::roles::{ExecuteExt, MetadataExt, QueryExt}; use crate::msg::{ExecuteMsg, InstantiateMsg, NftContract, QueryMsg}; -use crate::state::{Config, CONFIG, DAO, INITITIAL_NFTS}; +use crate::state::{Config, CONFIG, DAO, INITIAL_NFTS}; use crate::ContractError; pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-cw721-roles"; @@ -57,7 +57,7 @@ pub fn instantiate( } // Save initial NFTs for use in reply - INITITIAL_NFTS.save(deps.storage, &initial_nfts)?; + INITIAL_NFTS.save(deps.storage, &initial_nfts)?; // Create instantiate submessage for NFT roles contract let msg = SubMsg::reply_on_success( @@ -177,7 +177,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result = initial_nfts @@ -202,7 +202,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result>(); // Clear space - INITITIAL_NFTS.remove(deps.storage); + INITIAL_NFTS.remove(deps.storage); // Update minter message let update_minter_msg = WasmMsg::Execute { diff --git a/contracts/voting/dao-voting-cw721-roles/src/state.rs b/contracts/voting/dao-voting-cw721-roles/src/state.rs index de55f8d3d..fb6e98779 100644 --- a/contracts/voting/dao-voting-cw721-roles/src/state.rs +++ b/contracts/voting/dao-voting-cw721-roles/src/state.rs @@ -13,4 +13,4 @@ pub const CONFIG: Item = Item::new("config"); pub const DAO: Item = Item::new("dao"); // Holds initial NFTs messages during instantiation. -pub const INITITIAL_NFTS: Item> = Item::new("initial_nfts"); +pub const INITIAL_NFTS: Item> = Item::new("initial_nfts"); diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index e9d212052..501d1b1f4 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -2,7 +2,7 @@ use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; use crate::state::{ register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, - INITITIAL_NFTS, MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, + INITIAL_NFTS, MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, }; use crate::ContractError; use cosmwasm_schema::cw_serde; @@ -175,7 +175,7 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; // Save initial NFTs for use in reply - INITITIAL_NFTS.save(deps.storage, &initial_nfts)?; + INITIAL_NFTS.save(deps.storage, &initial_nfts)?; // Create instantiate submessage for NFT contract let instantiate_msg = SubMsg::reply_on_success( @@ -669,7 +669,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result = initial_nfts @@ -684,7 +684,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result>(); // Clear space - INITITIAL_NFTS.remove(deps.storage); + INITIAL_NFTS.remove(deps.storage); // Last submessage updates owner. // The reply is used for validation after setup. diff --git a/contracts/voting/dao-voting-cw721-staked/src/state.rs b/contracts/voting/dao-voting-cw721-staked/src/state.rs index b7a3c00cd..bf8e2ef5f 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/state.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/state.rs @@ -20,7 +20,7 @@ pub const CONFIG: Item = Item::new("config"); pub const DAO: Item = Item::new("dao"); // Holds initial NFTs messages during instantiation. -pub const INITITIAL_NFTS: Item> = Item::new("initial_nfts"); +pub const INITIAL_NFTS: Item> = Item::new("initial_nfts"); /// The set of NFTs currently staked by each address. The existence of /// an `(address, token_id)` pair implies that `address` has staked From 89cf32bdb5cb355223eed41d7e228a92ed7142e0 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 21:40:32 -0700 Subject: [PATCH 14/59] Fix counterintuitive variable namings --- .../schema/cw-tokenfactory-issuer.json | 453 +++++------------- .../cw-tokenfactory-issuer/src/contract.rs | 12 +- .../cw-tokenfactory-issuer/src/execute.rs | 18 +- .../cw-tokenfactory-issuer/src/msg.rs | 16 +- .../cw-tokenfactory-issuer/src/queries.rs | 30 +- .../cw-tokenfactory-issuer/src/state.rs | 6 +- .../tests/cases/blacklist.rs | 8 +- .../tests/cases/whitelist.rs | 8 +- .../cw-tokenfactory-issuer/tests/test_env.rs | 16 +- 9 files changed, 166 insertions(+), 401 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json index 50c2489fe..e934c4d26 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -9,15 +9,11 @@ { "description": "`NewToken` will create a new token when instantiate the contract. Newly created token will have full denom as `factory//`. It will be attached to the contract setup the beforesend listener automatically.", "type": "object", - "required": [ - "new_token" - ], + "required": ["new_token"], "properties": { "new_token": { "type": "object", - "required": [ - "subdenom" - ], + "required": ["subdenom"], "properties": { "subdenom": { "description": "component of fulldenom (`factory//`).", @@ -32,15 +28,11 @@ { "description": "`ExistingToken` will use already created token. So to set this up, Token Factory admin for the existing token needs trasfer admin over to this contract, and optionally set the `BeforeSendHook` manually.", "type": "object", - "required": [ - "existing_token" - ], + "required": ["existing_token"], "properties": { "existing_token": { "type": "object", - "required": [ - "denom" - ], + "required": ["denom"], "properties": { "denom": { "type": "string" @@ -60,15 +52,11 @@ { "description": "Change the admin of the Token Factory denom itself.", "type": "object", - "required": [ - "change_token_factory_admin" - ], + "required": ["change_token_factory_admin"], "properties": { "change_token_factory_admin": { "type": "object", - "required": [ - "new_admin" - ], + "required": ["new_admin"], "properties": { "new_admin": { "type": "string" @@ -82,15 +70,11 @@ { "description": "Change the owner of this contract who is allowed to call privileged methods.", "type": "object", - "required": [ - "change_contract_owner" - ], + "required": ["change_contract_owner"], "properties": { "change_contract_owner": { "type": "object", - "required": [ - "new_owner" - ], + "required": ["new_owner"], "properties": { "new_owner": { "type": "string" @@ -104,15 +88,11 @@ { "description": "Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata.", "type": "object", - "required": [ - "set_denom_metadata" - ], + "required": ["set_denom_metadata"], "properties": { "set_denom_metadata": { "type": "object", - "required": [ - "metadata" - ], + "required": ["metadata"], "properties": { "metadata": { "$ref": "#/definitions/Metadata" @@ -126,16 +106,11 @@ { "description": "Grant/revoke mint allowance.", "type": "object", - "required": [ - "set_minter_allowance" - ], + "required": ["set_minter_allowance"], "properties": { "set_minter_allowance": { "type": "object", - "required": [ - "address", - "allowance" - ], + "required": ["address", "allowance"], "properties": { "address": { "type": "string" @@ -152,16 +127,11 @@ { "description": "Grant/revoke burn allowance.", "type": "object", - "required": [ - "set_burner_allowance" - ], + "required": ["set_burner_allowance"], "properties": { "set_burner_allowance": { "type": "object", - "required": [ - "address", - "allowance" - ], + "required": ["address", "allowance"], "properties": { "address": { "type": "string" @@ -178,16 +148,11 @@ { "description": "Grant/revoke permission to blacklist addresses", "type": "object", - "required": [ - "set_blacklister" - ], + "required": ["set_blacklister"], "properties": { "set_blacklister": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -204,16 +169,11 @@ { "description": "Grant/revoke permission to blacklist addresses", "type": "object", - "required": [ - "set_whitelister" - ], + "required": ["set_whitelister"], "properties": { "set_whitelister": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -230,9 +190,7 @@ { "description": "Attempt to SetBeforeSendHook on the token attached to this contract. This will fail if the token already has a SetBeforeSendHook or the chain still does not support it.", "type": "object", - "required": [ - "set_before_send_hook" - ], + "required": ["set_before_send_hook"], "properties": { "set_before_send_hook": { "type": "object", @@ -244,16 +202,11 @@ { "description": "Grant/revoke permission to freeze the token", "type": "object", - "required": [ - "set_freezer" - ], + "required": ["set_freezer"], "properties": { "set_freezer": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -270,16 +223,11 @@ { "description": "Mint token to address. Mint allowance is required and wiil be deducted after successful mint.", "type": "object", - "required": [ - "mint" - ], + "required": ["mint"], "properties": { "mint": { "type": "object", - "required": [ - "amount", - "to_address" - ], + "required": ["amount", "to_address"], "properties": { "amount": { "$ref": "#/definitions/Uint128" @@ -296,16 +244,11 @@ { "description": "Burn token to address. Burn allowance is required and wiil be deducted after successful burn.", "type": "object", - "required": [ - "burn" - ], + "required": ["burn"], "properties": { "burn": { "type": "object", - "required": [ - "amount", - "from_address" - ], + "required": ["amount", "from_address"], "properties": { "amount": { "$ref": "#/definitions/Uint128" @@ -322,16 +265,11 @@ { "description": "Block target address from sending/receiving token attached to this contract tokenfactory's beforesend listener must be set to this contract in order for it to work as intended.", "type": "object", - "required": [ - "blacklist" - ], + "required": ["blacklist"], "properties": { "blacklist": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -348,16 +286,11 @@ { "description": "Whitelist target address to be able to send tokens even if the token is frozen.", "type": "object", - "required": [ - "whitelist" - ], + "required": ["whitelist"], "properties": { "whitelist": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -374,15 +307,11 @@ { "description": "Block every token transfers of the token attached to this contract tokenfactory's beforesend listener must be set to this contract in order for it to work as intended.", "type": "object", - "required": [ - "freeze" - ], + "required": ["freeze"], "properties": { "freeze": { "type": "object", - "required": [ - "status" - ], + "required": ["status"], "properties": { "status": { "type": "boolean" @@ -396,17 +325,11 @@ { "description": "Force transfer token from one address to another.", "type": "object", - "required": [ - "force_transfer" - ], + "required": ["force_transfer"], "properties": { "force_transfer": { "type": "object", - "required": [ - "amount", - "from_address", - "to_address" - ], + "required": ["amount", "from_address", "to_address"], "properties": { "amount": { "$ref": "#/definitions/Uint128" @@ -428,11 +351,7 @@ "DenomUnit": { "description": "DenomUnit represents a struct that describes a given denomination unit of the basic token.", "type": "object", - "required": [ - "aliases", - "denom", - "exponent" - ], + "required": ["aliases", "denom", "exponent"], "properties": { "aliases": { "description": "aliases is a list of string aliases for the given denom", @@ -506,9 +425,7 @@ { "description": "IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse", "type": "object", - "required": [ - "is_frozen" - ], + "required": ["is_frozen"], "properties": { "is_frozen": { "type": "object", @@ -520,9 +437,7 @@ { "description": "Denom returns the token denom that this contract is the admin for. Response: DenomResponse", "type": "object", - "required": [ - "denom" - ], + "required": ["denom"], "properties": { "denom": { "type": "object", @@ -534,9 +449,7 @@ { "description": "Owner returns the owner of the contract. Response: OwnerResponse", "type": "object", - "required": [ - "owner" - ], + "required": ["owner"], "properties": { "owner": { "type": "object", @@ -548,15 +461,11 @@ { "description": "Allowance returns the allowance of the specified address. Response: AllowanceResponse", "type": "object", - "required": [ - "burn_allowance" - ], + "required": ["burn_allowance"], "properties": { "burn_allowance": { "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -570,26 +479,18 @@ { "description": "Allowances Enumerates over all allownances. Response: Vec", "type": "object", - "required": [ - "burn_allowances" - ], + "required": ["burn_allowances"], "properties": { "burn_allowances": { "type": "object", "properties": { "limit": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint32", "minimum": 0.0 }, "start_after": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -600,15 +501,11 @@ { "description": "Allowance returns the allowance of the specified user. Response: AllowanceResponse", "type": "object", - "required": [ - "mint_allowance" - ], + "required": ["mint_allowance"], "properties": { "mint_allowance": { "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -622,26 +519,18 @@ { "description": "Allowances Enumerates over all allownances. Response: AllowancesResponse", "type": "object", - "required": [ - "mint_allowances" - ], + "required": ["mint_allowances"], "properties": { "mint_allowances": { "type": "object", "properties": { "limit": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint32", "minimum": 0.0 }, "start_after": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -652,15 +541,11 @@ { "description": "IsBlacklisted returns wether the user is blacklisted or not. Response: StatusResponse", "type": "object", - "required": [ - "is_blacklisted" - ], + "required": ["is_blacklisted"], "properties": { "is_blacklisted": { "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -674,26 +559,18 @@ { "description": "Blacklistees enumerates over all addresses on the blacklist. Response: BlacklisteesResponse", "type": "object", - "required": [ - "blacklistees" - ], + "required": ["blacklistees"], "properties": { "blacklistees": { "type": "object", "properties": { "limit": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint32", "minimum": 0.0 }, "start_after": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -704,15 +581,11 @@ { "description": "IsBlacklister returns if the addres has blacklister privileges. Response: StatusResponse", "type": "object", - "required": [ - "is_blacklister" - ], + "required": ["is_blacklister"], "properties": { "is_blacklister": { "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -724,28 +597,20 @@ "additionalProperties": false }, { - "description": "Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklisterAllowancesResponse", + "description": "Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklistersResponse", "type": "object", - "required": [ - "blacklister_allowances" - ], + "required": ["blacklisters"], "properties": { - "blacklister_allowances": { + "blacklisters": { "type": "object", "properties": { "limit": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint32", "minimum": 0.0 }, "start_after": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -756,15 +621,11 @@ { "description": "IsWhitelisted returns wether the user is whitelisted or not. Response: StatusResponse", "type": "object", - "required": [ - "is_whitelisted" - ], + "required": ["is_whitelisted"], "properties": { "is_whitelisted": { "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -778,26 +639,18 @@ { "description": "Whitelistees enumerates over all addresses on the whitelist. Response: WhitelisteesResponse", "type": "object", - "required": [ - "whitelistees" - ], + "required": ["whitelistees"], "properties": { "whitelistees": { "type": "object", "properties": { "limit": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint32", "minimum": 0.0 }, "start_after": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -808,15 +661,11 @@ { "description": "IsWhitelister returns if the addres has whitelister privileges. Response: StatusResponse", "type": "object", - "required": [ - "is_whitelister" - ], + "required": ["is_whitelister"], "properties": { "is_whitelister": { "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -828,28 +677,20 @@ "additionalProperties": false }, { - "description": "Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelisterAllowancesResponse", + "description": "Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelistersResponse", "type": "object", - "required": [ - "whitelister_allowances" - ], + "required": ["whitelisters"], "properties": { - "whitelister_allowances": { + "whitelisters": { "type": "object", "properties": { "limit": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint32", "minimum": 0.0 }, "start_after": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -860,15 +701,11 @@ { "description": "IsFreezer returns whether the address has freezer status. Response: StatusResponse", "type": "object", - "required": [ - "is_freezer" - ], + "required": ["is_freezer"], "properties": { "is_freezer": { "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -882,26 +719,18 @@ { "description": "FreezerAllowances enumerates over all freezer addresses. Response: FreezerAllowancesResponse", "type": "object", - "required": [ - "freezer_allowances" - ], + "required": ["freezer_allowances"], "properties": { "freezer_allowances": { "type": "object", "properties": { "limit": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint32", "minimum": 0.0 }, "start_after": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -912,9 +741,7 @@ { "description": "Returns whether features that require MsgBeforeSendHook are enabled Most Cosmos chains do not support this feature yet.", "type": "object", - "required": [ - "before_send_hook_features_enabled" - ], + "required": ["before_send_hook_features_enabled"], "properties": { "before_send_hook_features_enabled": { "type": "object", @@ -933,17 +760,11 @@ "oneOf": [ { "type": "object", - "required": [ - "block_before_send" - ], + "required": ["block_before_send"], "properties": { "block_before_send": { "type": "object", - "required": [ - "amount", - "from", - "to" - ], + "required": ["amount", "from", "to"], "properties": { "amount": { "$ref": "#/definitions/Coin" @@ -964,10 +785,7 @@ "definitions": { "Coin": { "type": "object", - "required": [ - "amount", - "denom" - ], + "required": ["amount", "denom"], "properties": { "amount": { "$ref": "#/definitions/Uint128" @@ -993,9 +811,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "BlacklisteesResponse", "type": "object", - "required": [ - "blacklistees" - ], + "required": ["blacklistees"], "properties": { "blacklistees": { "type": "array", @@ -1008,10 +824,7 @@ "definitions": { "StatusInfo": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -1024,13 +837,11 @@ } } }, - "blacklister_allowances": { + "blacklisters": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BlacklisterAllowancesResponse", + "title": "BlacklistersResponse", "type": "object", - "required": [ - "blacklisters" - ], + "required": ["blacklisters"], "properties": { "blacklisters": { "type": "array", @@ -1043,10 +854,7 @@ "definitions": { "StatusInfo": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -1063,9 +871,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowanceResponse", "type": "object", - "required": [ - "allowance" - ], + "required": ["allowance"], "properties": { "allowance": { "$ref": "#/definitions/Uint128" @@ -1083,9 +889,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowancesResponse", "type": "object", - "required": [ - "allowances" - ], + "required": ["allowances"], "properties": { "allowances": { "type": "array", @@ -1098,10 +902,7 @@ "definitions": { "AllowanceInfo": { "type": "object", - "required": [ - "address", - "allowance" - ], + "required": ["address", "allowance"], "properties": { "address": { "type": "string" @@ -1122,9 +923,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "DenomResponse", "type": "object", - "required": [ - "denom" - ], + "required": ["denom"], "properties": { "denom": { "type": "string" @@ -1136,9 +935,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "FreezerAllowancesResponse", "type": "object", - "required": [ - "freezers" - ], + "required": ["freezers"], "properties": { "freezers": { "type": "array", @@ -1151,10 +948,7 @@ "definitions": { "StatusInfo": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -1171,9 +965,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", "type": "object", - "required": [ - "status" - ], + "required": ["status"], "properties": { "status": { "type": "boolean" @@ -1185,9 +977,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", "type": "object", - "required": [ - "status" - ], + "required": ["status"], "properties": { "status": { "type": "boolean" @@ -1199,9 +989,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", "type": "object", - "required": [ - "status" - ], + "required": ["status"], "properties": { "status": { "type": "boolean" @@ -1213,9 +1001,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "IsFrozenResponse", "type": "object", - "required": [ - "is_frozen" - ], + "required": ["is_frozen"], "properties": { "is_frozen": { "type": "boolean" @@ -1227,9 +1013,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", "type": "object", - "required": [ - "status" - ], + "required": ["status"], "properties": { "status": { "type": "boolean" @@ -1241,9 +1025,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", "type": "object", - "required": [ - "status" - ], + "required": ["status"], "properties": { "status": { "type": "boolean" @@ -1255,9 +1037,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowanceResponse", "type": "object", - "required": [ - "allowance" - ], + "required": ["allowance"], "properties": { "allowance": { "$ref": "#/definitions/Uint128" @@ -1275,9 +1055,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowancesResponse", "type": "object", - "required": [ - "allowances" - ], + "required": ["allowances"], "properties": { "allowances": { "type": "array", @@ -1290,10 +1068,7 @@ "definitions": { "AllowanceInfo": { "type": "object", - "required": [ - "address", - "allowance" - ], + "required": ["address", "allowance"], "properties": { "address": { "type": "string" @@ -1314,9 +1089,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "OwnerResponse", "type": "object", - "required": [ - "address" - ], + "required": ["address"], "properties": { "address": { "type": "string" @@ -1328,9 +1101,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "WhitelisteesResponse", "type": "object", - "required": [ - "whitelistees" - ], + "required": ["whitelistees"], "properties": { "whitelistees": { "type": "array", @@ -1343,10 +1114,7 @@ "definitions": { "StatusInfo": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" @@ -1359,13 +1127,11 @@ } } }, - "whitelister_allowances": { + "whitelisters": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WhitelisterAllowancesResponse", + "title": "WhitelistersResponse", "type": "object", - "required": [ - "whitelisters" - ], + "required": ["whitelisters"], "properties": { "whitelisters": { "type": "array", @@ -1378,10 +1144,7 @@ "definitions": { "StatusInfo": { "type": "object", - "required": [ - "address", - "status" - ], + "required": ["address", "status"], "properties": { "address": { "type": "string" diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index d73f34743..111ef53a7 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -169,9 +169,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResu QueryMsg::IsBlacklister { address } => { to_binary(&queries::query_is_blacklister(deps, address)?) } - QueryMsg::BlacklisterAllowances { start_after, limit } => to_binary( - &queries::query_blacklister_allowances(deps, start_after, limit)?, - ), + QueryMsg::Blacklisters { start_after, limit } => { + to_binary(&queries::query_blacklisters(deps, start_after, limit)?) + } QueryMsg::IsWhitelisted { address } => { to_binary(&queries::query_is_whitelisted(deps, address)?) } @@ -181,9 +181,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResu QueryMsg::IsWhitelister { address } => { to_binary(&queries::query_is_whitelister(deps, address)?) } - QueryMsg::WhitelisterAllowances { start_after, limit } => to_binary( - &queries::query_whitelister_allowances(deps, start_after, limit)?, - ), + QueryMsg::Whitelisters { start_after, limit } => { + to_binary(&queries::query_whitelisters(deps, start_after, limit)?) + } QueryMsg::IsFreezer { address } => to_binary(&queries::query_is_freezer(deps, address)?), QueryMsg::FreezerAllowances { start_after, limit } => to_binary( &queries::query_freezer_allowances(deps, start_after, limit)?, diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 3f0701ade..8432b3457 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -10,9 +10,9 @@ use crate::helpers::{ check_before_send_hook_features_enabled, check_bool_allowance, check_is_contract_owner, }; use crate::state::{ - BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTER_ALLOWANCES, - BURNER_ALLOWANCES, DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, - WHITELISTED_ADDRESSES, WHITELISTER_ALLOWANCES, + BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTERS, BURNER_ALLOWANCES, + DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, WHITELISTED_ADDRESSES, + WHITELISTERS, }; pub fn mint( @@ -200,9 +200,9 @@ pub fn set_blacklister( // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { - BLACKLISTER_ALLOWANCES.save(deps.storage, &address, &status)?; + BLACKLISTERS.save(deps.storage, &address, &status)?; } else { - BLACKLISTER_ALLOWANCES.remove(deps.storage, &address); + BLACKLISTERS.remove(deps.storage, &address); } Ok(Response::new() @@ -228,9 +228,9 @@ pub fn set_whitelister( // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { - WHITELISTER_ALLOWANCES.save(deps.storage, &address, &status)?; + WHITELISTERS.save(deps.storage, &address, &status)?; } else { - WHITELISTER_ALLOWANCES.remove(deps.storage, &address); + WHITELISTERS.remove(deps.storage, &address); } // Return OK @@ -383,7 +383,7 @@ pub fn blacklist( check_before_send_hook_features_enabled(deps.as_ref())?; // check to make sure that the sender has blacklister permissions - check_bool_allowance(deps.as_ref(), info, BLACKLISTER_ALLOWANCES)?; + check_bool_allowance(deps.as_ref(), info, BLACKLISTERS)?; let address = deps.api.addr_validate(&address)?; @@ -417,7 +417,7 @@ pub fn whitelist( check_before_send_hook_features_enabled(deps.as_ref())?; // check to make sure that the sender has blacklister permissions - check_bool_allowance(deps.as_ref(), info, WHITELISTER_ALLOWANCES)?; + check_bool_allowance(deps.as_ref(), info, WHITELISTERS)?; let address = deps.api.addr_validate(&address)?; diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 249c0c7aa..d2dd65239 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -133,9 +133,9 @@ pub enum QueryMsg { /// IsBlacklister returns if the addres has blacklister privileges. Response: StatusResponse #[returns(StatusResponse)] IsBlacklister { address: String }, - /// Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklisterAllowancesResponse - #[returns(BlacklisterAllowancesResponse)] - BlacklisterAllowances { + /// Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklistersResponse + #[returns(BlacklistersResponse)] + Blacklisters { start_after: Option, limit: Option, }, @@ -151,9 +151,9 @@ pub enum QueryMsg { /// IsWhitelister returns if the addres has whitelister privileges. Response: StatusResponse #[returns(StatusResponse)] IsWhitelister { address: String }, - /// Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelisterAllowancesResponse - #[returns(WhitelisterAllowancesResponse)] - WhitelisterAllowances { + /// Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelistersResponse + #[returns(WhitelistersResponse)] + Whitelisters { start_after: Option, limit: Option, }, @@ -222,7 +222,7 @@ pub struct BlacklisteesResponse { } #[cw_serde] -pub struct BlacklisterAllowancesResponse { +pub struct BlacklistersResponse { pub blacklisters: Vec, } @@ -232,7 +232,7 @@ pub struct WhitelisteesResponse { } #[cw_serde] -pub struct WhitelisterAllowancesResponse { +pub struct WhitelistersResponse { pub whitelisters: Vec, } diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index 827d64e5b..a129ee0f7 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -4,13 +4,13 @@ use token_bindings::TokenFactoryQuery; use crate::msg::{ AllowanceInfo, AllowanceResponse, AllowancesResponse, BlacklisteesResponse, - BlacklisterAllowancesResponse, DenomResponse, FreezerAllowancesResponse, IsFrozenResponse, - OwnerResponse, StatusInfo, StatusResponse, WhitelisteesResponse, WhitelisterAllowancesResponse, + BlacklistersResponse, DenomResponse, FreezerAllowancesResponse, IsFrozenResponse, + OwnerResponse, StatusInfo, StatusResponse, WhitelisteesResponse, WhitelistersResponse, }; use crate::state::{ - BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTER_ALLOWANCES, - BURNER_ALLOWANCES, DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, - WHITELISTED_ADDRESSES, WHITELISTER_ALLOWANCES, + BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTERS, BURNER_ALLOWANCES, + DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, WHITELISTED_ADDRESSES, + WHITELISTERS, }; // Default settings for pagination @@ -167,19 +167,19 @@ pub fn query_is_blacklister( deps: Deps, address: String, ) -> StdResult { - let status = BLACKLISTER_ALLOWANCES + let status = BLACKLISTERS .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } -pub fn query_blacklister_allowances( +pub fn query_blacklisters( deps: Deps, start_after: Option, limit: Option, -) -> StdResult { - Ok(BlacklisterAllowancesResponse { - blacklisters: query_status_map(deps, start_after, limit, BLACKLISTER_ALLOWANCES)?, +) -> StdResult { + Ok(BlacklistersResponse { + blacklisters: query_status_map(deps, start_after, limit, BLACKLISTERS)?, }) } @@ -197,19 +197,19 @@ pub fn query_is_whitelister( deps: Deps, address: String, ) -> StdResult { - let status = WHITELISTER_ALLOWANCES + let status = WHITELISTERS .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } -pub fn query_whitelister_allowances( +pub fn query_whitelisters( deps: Deps, start_after: Option, limit: Option, -) -> StdResult { - Ok(WhitelisterAllowancesResponse { - whitelisters: query_status_map(deps, start_after, limit, WHITELISTER_ALLOWANCES)?, +) -> StdResult { + Ok(WhitelistersResponse { + whitelisters: query_status_map(deps, start_after, limit, WHITELISTERS)?, }) } diff --git a/contracts/external/cw-tokenfactory-issuer/src/state.rs b/contracts/external/cw-tokenfactory-issuer/src/state.rs index 1f71f4437..9942ea759 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/state.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/state.rs @@ -17,9 +17,11 @@ pub const BEFORE_SEND_HOOK_FEATURES_ENABLED: Item = Item::new("hook_featur /// Whether or not token transfers are frozen pub const IS_FROZEN: Item = Item::new("is_frozen"); +// Address able to manange blacklists and whitelists +pub const BLACKLISTERS: Map<&Addr, bool> = Map::new("blacklisters"); +pub const WHITELISTERS: Map<&Addr, bool> = Map::new("whitelisters"); + /// Allowances -pub const BLACKLISTER_ALLOWANCES: Map<&Addr, bool> = Map::new("blacklister_allowances"); -pub const WHITELISTER_ALLOWANCES: Map<&Addr, bool> = Map::new("whitelister_allowances"); pub const BURNER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("burner_allowances"); pub const FREEZER_ALLOWANCES: Map<&Addr, bool> = Map::new("freezer_allowances"); pub const MINTER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("minter_allowances"); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs index 417acba83..a831367c7 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs @@ -73,7 +73,7 @@ fn set_blacklister_to_false_should_remove_it_from_storage() { assert_eq!( env.cw_tokenfactory_issuer - .query_blacklister_allowances(None, None) + .query_blacklisters(None, None) .unwrap() .blacklisters, vec![ @@ -94,7 +94,7 @@ fn set_blacklister_to_false_should_remove_it_from_storage() { assert_eq!( env.cw_tokenfactory_issuer - .query_blacklister_allowances(None, None) + .query_blacklisters(None, None) .unwrap() .blacklisters, vec![StatusInfo { @@ -259,7 +259,7 @@ fn query_blacklister_within_default_limit() { |env| { move |start_after, limit| { env.cw_tokenfactory_issuer - .query_blacklister_allowances(start_after, limit) + .query_blacklisters(start_after, limit) .unwrap() .blacklisters } @@ -285,7 +285,7 @@ fn query_blacklister_over_default_limit() { |env| { move |start_after, limit| { env.cw_tokenfactory_issuer - .query_blacklister_allowances(start_after, limit) + .query_blacklisters(start_after, limit) .unwrap() .blacklisters } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs index a4a39f385..923c062a9 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs @@ -73,7 +73,7 @@ fn set_whitelister_to_false_should_remove_it_from_storage() { assert_eq!( env.cw_tokenfactory_issuer - .query_whitelister_allowances(None, None) + .query_whitelisters(None, None) .unwrap() .whitelisters, vec![ @@ -94,7 +94,7 @@ fn set_whitelister_to_false_should_remove_it_from_storage() { assert_eq!( env.cw_tokenfactory_issuer - .query_whitelister_allowances(None, None) + .query_whitelisters(None, None) .unwrap() .whitelisters, vec![StatusInfo { @@ -244,7 +244,7 @@ fn query_whitelister_within_default_limit() { |env| { move |start_after, limit| { env.cw_tokenfactory_issuer - .query_whitelister_allowances(start_after, limit) + .query_whitelisters(start_after, limit) .unwrap() .whitelisters } @@ -270,7 +270,7 @@ fn query_whitelister_over_default_limit() { |env| { move |start_after, limit| { env.cw_tokenfactory_issuer - .query_whitelister_allowances(start_after, limit) + .query_whitelisters(start_after, limit) .unwrap() .whitelisters } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs index af9079487..5c45b724d 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs @@ -5,8 +5,8 @@ use cosmwasm_std::{Coin, Uint128}; use cw_tokenfactory_issuer::msg::{ - BlacklisteesResponse, BlacklisterAllowancesResponse, Metadata, MigrateMsg, - WhitelisteesResponse, WhitelisterAllowancesResponse, + BlacklisteesResponse, BlacklistersResponse, Metadata, MigrateMsg, WhitelisteesResponse, + WhitelistersResponse, }; use cw_tokenfactory_issuer::{ msg::{ @@ -422,12 +422,12 @@ impl TokenfactoryIssuer { self.query(&QueryMsg::FreezerAllowances { start_after, limit }) } - pub fn query_blacklister_allowances( + pub fn query_blacklisters( &self, start_after: Option, limit: Option, - ) -> Result { - self.query(&QueryMsg::BlacklisterAllowances { start_after, limit }) + ) -> Result { + self.query(&QueryMsg::Blacklisters { start_after, limit }) } pub fn query_blacklistees( @@ -438,12 +438,12 @@ impl TokenfactoryIssuer { self.query(&QueryMsg::Blacklistees { start_after, limit }) } - pub fn query_whitelister_allowances( + pub fn query_whitelisters( &self, start_after: Option, limit: Option, - ) -> Result { - self.query(&QueryMsg::WhitelisterAllowances { start_after, limit }) + ) -> Result { + self.query(&QueryMsg::Whitelisters { start_after, limit }) } pub fn query_whitelistees( From 8bcae64570d1598b0eef8cade95f0e3d090f16a2 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 23:28:28 -0700 Subject: [PATCH 15/59] Better API for cw-tokenfactory-issuer Many of these methods are not needed thanks to Authz. --- .../external/cw-tokenfactory-issuer/README.md | 16 +- .../schema/cw-tokenfactory-issuer.json | 1161 ----------------- .../cw-tokenfactory-issuer/src/contract.rs | 75 +- .../cw-tokenfactory-issuer/src/error.rs | 8 +- .../cw-tokenfactory-issuer/src/execute.rs | 152 +-- .../cw-tokenfactory-issuer/src/helpers.rs | 41 +- .../cw-tokenfactory-issuer/src/hooks.rs | 8 +- .../cw-tokenfactory-issuer/src/msg.rs | 157 +-- .../cw-tokenfactory-issuer/src/queries.rs | 104 +- .../cw-tokenfactory-issuer/src/state.rs | 11 +- .../tests/cases/allowlist.rs | 106 ++ .../tests/cases/beforesend.rs | 62 +- .../tests/cases/blacklist.rs | 354 ----- .../tests/cases/contract_owner.rs | 6 +- .../tests/cases/denylist.rs | 120 ++ .../tests/cases/freeze.rs | 175 +-- .../tests/cases/instantiate.rs | 5 - .../cw-tokenfactory-issuer/tests/cases/mod.rs | 4 +- .../tests/cases/set_before_update_hook.rs | 2 +- .../tests/cases/tokenfactory_admin.rs | 4 +- .../tests/cases/whitelist.rs | 339 ----- .../cw-tokenfactory-issuer/tests/test_env.rs | 157 +-- .../src/contract.rs | 2 +- 23 files changed, 466 insertions(+), 2603 deletions(-) delete mode 100644 contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json create mode 100644 contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs delete mode 100644 contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs create mode 100644 contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs delete mode 100644 contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs diff --git a/contracts/external/cw-tokenfactory-issuer/README.md b/contracts/external/cw-tokenfactory-issuer/README.md index fb615da62..9e6ec9f78 100644 --- a/contracts/external/cw-tokenfactory-issuer/README.md +++ b/contracts/external/cw-tokenfactory-issuer/README.md @@ -3,30 +3,34 @@ Forked from [osmosis-labs/cw-tokenfactory-issuer](https://github.com/osmosis-labs/cw-tokenfactory-issuer). This repo contains a set of contracts that when used in conjunction with the x/tokenfactory module in Osmosis, Juno, and many other chains will enable a centrally issued stablecoin with many features: + - Creating a new Token Factory token or using an existing one - Granting and revoking allowances for the minting and burning of tokens - Updating token metadata -- Freezing and unfreezing transfers, with a whitelist to allow some address to continue to transfer +- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to continue to transfer +- Denylist to prevent certain addresses from transferring - Force transfering tokens via the contract owner - Updating the contract owner or Token Factory admin It is intended to work on multiple chains supporting Token Factory, and has been tested on Juno Network and Osmosis. -The contract has an owner (which can be removed or updated via `ExecuteMsg::ChangeContractOwner {}`), but it can delegate capabilities to other acccounts. For example, the owner of a contract can delegate minting allowance of 1000 tokens to a new address. +The contract has an owner (which can be removed or updated via `ExecuteMsg::UpdateContractOwner {}`), but it can delegate capabilities to other acccounts. For example, the owner of a contract can delegate minting allowance of 1000 tokens to a new address. The contract is also the admin of the newly created Token Factory denom. For minting and burning, users then interact with the contract using its own ExecuteMsgs which trigger the contract's access control logic, and the contract then dispatches tokenfactory sdk.Msgs from its own contract account. -NOTE: this contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the blacklisting of specific accounts as well as the freezing of all transfers if necessary. This feature is not enabled on every chain using Token Factory, and so blacklisting and freezing features are disabled if `MsgBeforeSendHook` is not supported. DAOs wishing to leverage these features on chains after support is added can call `ExecuteMsg::SetBeforeSendHook {}`. +NOTE: this contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the denylisting of specific accounts as well as the freezing of all transfers if necessary. This feature is not enabled on every chain using Token Factory, and so denylisting and freezing features are disabled if `MsgBeforeSendHook` is not supported. DAOs wishing to leverage these features on chains after support is added can call `ExecuteMsg::SetBeforeSendHook {}`. ## Instantiation + When instantiating `cw-tokenfactory-issuer`, you can either create a `new_token` or an `existing_token`. ### Creating a new Token Factory token + To create a new Token Factory token, simply instantiate the contract with a `subdenom`, this will create a new contract as well as a token with a denom formatted as `factory/{contract_address}/{subdenom}`. Example instantiate message: -``` json +```json { "new_token": { "subdenom": "test" @@ -37,10 +41,12 @@ Example instantiate message: All other updates can be preformed afterwards via this contract's `ExecuteMsg` enum. See `src/msg.rs` for available methods. ### Using an Existing Token + You can also instantiate this contract with an existing token, however most features will not be available until the previous Token Factory admin transfers admin rights to the instantiated contract and optionally calls `ExecuteMsg::SetBeforeSendHook {}` to enable dependent features. Example instantiate message: -``` json + +```json { "existing_token": { "denom": "factory/{contract_address}/{subdenom}" diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json deleted file mode 100644 index e934c4d26..000000000 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ /dev/null @@ -1,1161 +0,0 @@ -{ - "contract_name": "cw-tokenfactory-issuer", - "contract_version": "2.2.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "oneOf": [ - { - "description": "`NewToken` will create a new token when instantiate the contract. Newly created token will have full denom as `factory//`. It will be attached to the contract setup the beforesend listener automatically.", - "type": "object", - "required": ["new_token"], - "properties": { - "new_token": { - "type": "object", - "required": ["subdenom"], - "properties": { - "subdenom": { - "description": "component of fulldenom (`factory//`).", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`ExistingToken` will use already created token. So to set this up, Token Factory admin for the existing token needs trasfer admin over to this contract, and optionally set the `BeforeSendHook` manually.", - "type": "object", - "required": ["existing_token"], - "properties": { - "existing_token": { - "type": "object", - "required": ["denom"], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Change the admin of the Token Factory denom itself.", - "type": "object", - "required": ["change_token_factory_admin"], - "properties": { - "change_token_factory_admin": { - "type": "object", - "required": ["new_admin"], - "properties": { - "new_admin": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Change the owner of this contract who is allowed to call privileged methods.", - "type": "object", - "required": ["change_contract_owner"], - "properties": { - "change_contract_owner": { - "type": "object", - "required": ["new_owner"], - "properties": { - "new_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata.", - "type": "object", - "required": ["set_denom_metadata"], - "properties": { - "set_denom_metadata": { - "type": "object", - "required": ["metadata"], - "properties": { - "metadata": { - "$ref": "#/definitions/Metadata" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Grant/revoke mint allowance.", - "type": "object", - "required": ["set_minter_allowance"], - "properties": { - "set_minter_allowance": { - "type": "object", - "required": ["address", "allowance"], - "properties": { - "address": { - "type": "string" - }, - "allowance": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Grant/revoke burn allowance.", - "type": "object", - "required": ["set_burner_allowance"], - "properties": { - "set_burner_allowance": { - "type": "object", - "required": ["address", "allowance"], - "properties": { - "address": { - "type": "string" - }, - "allowance": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Grant/revoke permission to blacklist addresses", - "type": "object", - "required": ["set_blacklister"], - "properties": { - "set_blacklister": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Grant/revoke permission to blacklist addresses", - "type": "object", - "required": ["set_whitelister"], - "properties": { - "set_whitelister": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Attempt to SetBeforeSendHook on the token attached to this contract. This will fail if the token already has a SetBeforeSendHook or the chain still does not support it.", - "type": "object", - "required": ["set_before_send_hook"], - "properties": { - "set_before_send_hook": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Grant/revoke permission to freeze the token", - "type": "object", - "required": ["set_freezer"], - "properties": { - "set_freezer": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Mint token to address. Mint allowance is required and wiil be deducted after successful mint.", - "type": "object", - "required": ["mint"], - "properties": { - "mint": { - "type": "object", - "required": ["amount", "to_address"], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "to_address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Burn token to address. Burn allowance is required and wiil be deducted after successful burn.", - "type": "object", - "required": ["burn"], - "properties": { - "burn": { - "type": "object", - "required": ["amount", "from_address"], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "from_address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Block target address from sending/receiving token attached to this contract tokenfactory's beforesend listener must be set to this contract in order for it to work as intended.", - "type": "object", - "required": ["blacklist"], - "properties": { - "blacklist": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Whitelist target address to be able to send tokens even if the token is frozen.", - "type": "object", - "required": ["whitelist"], - "properties": { - "whitelist": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Block every token transfers of the token attached to this contract tokenfactory's beforesend listener must be set to this contract in order for it to work as intended.", - "type": "object", - "required": ["freeze"], - "properties": { - "freeze": { - "type": "object", - "required": ["status"], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Force transfer token from one address to another.", - "type": "object", - "required": ["force_transfer"], - "properties": { - "force_transfer": { - "type": "object", - "required": ["amount", "from_address", "to_address"], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "from_address": { - "type": "string" - }, - "to_address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "DenomUnit": { - "description": "DenomUnit represents a struct that describes a given denomination unit of the basic token.", - "type": "object", - "required": ["aliases", "denom", "exponent"], - "properties": { - "aliases": { - "description": "aliases is a list of string aliases for the given denom", - "type": "array", - "items": { - "type": "string" - } - }, - "denom": { - "description": "denom represents the string name of the given denom unit (e.g uatom).", - "type": "string" - }, - "exponent": { - "description": "exponent represents power of 10 exponent that one must raise the base_denom to in order to equal the given DenomUnit's denom 1 denom = 1^exponent base_denom (e.g. with a base_denom of uatom, one can create a DenomUnit of 'atom' with exponent = 6, thus: 1 atom = 10^6 uatom).", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - } - }, - "Metadata": { - "description": "Metadata represents a struct that describes a basic token.", - "type": "object", - "required": [ - "base", - "denom_units", - "description", - "display", - "name", - "symbol" - ], - "properties": { - "base": { - "description": "base represents the base denom (should be the DenomUnit with exponent = 0).", - "type": "string" - }, - "denom_units": { - "description": "denom_units represents the list of DenomUnit's for a given coin", - "type": "array", - "items": { - "$ref": "#/definitions/DenomUnit" - } - }, - "description": { - "type": "string" - }, - "display": { - "description": "display indicates the suggested denom that should be displayed in clients.", - "type": "string" - }, - "name": { - "description": "name defines the name of the token (eg: Cosmos Atom)\n\nSince: cosmos-sdk 0.43", - "type": "string" - }, - "symbol": { - "description": "symbol is the token symbol usually shown on exchanges (eg: ATOM). This can be the same as the display.\n\nSince: cosmos-sdk 0.43", - "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" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse", - "type": "object", - "required": ["is_frozen"], - "properties": { - "is_frozen": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Denom returns the token denom that this contract is the admin for. Response: DenomResponse", - "type": "object", - "required": ["denom"], - "properties": { - "denom": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Owner returns the owner of the contract. Response: OwnerResponse", - "type": "object", - "required": ["owner"], - "properties": { - "owner": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allowance returns the allowance of the specified address. Response: AllowanceResponse", - "type": "object", - "required": ["burn_allowance"], - "properties": { - "burn_allowance": { - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allowances Enumerates over all allownances. Response: Vec", - "type": "object", - "required": ["burn_allowances"], - "properties": { - "burn_allowances": { - "type": "object", - "properties": { - "limit": { - "type": ["integer", "null"], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allowance returns the allowance of the specified user. Response: AllowanceResponse", - "type": "object", - "required": ["mint_allowance"], - "properties": { - "mint_allowance": { - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allowances Enumerates over all allownances. Response: AllowancesResponse", - "type": "object", - "required": ["mint_allowances"], - "properties": { - "mint_allowances": { - "type": "object", - "properties": { - "limit": { - "type": ["integer", "null"], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "IsBlacklisted returns wether the user is blacklisted or not. Response: StatusResponse", - "type": "object", - "required": ["is_blacklisted"], - "properties": { - "is_blacklisted": { - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Blacklistees enumerates over all addresses on the blacklist. Response: BlacklisteesResponse", - "type": "object", - "required": ["blacklistees"], - "properties": { - "blacklistees": { - "type": "object", - "properties": { - "limit": { - "type": ["integer", "null"], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "IsBlacklister returns if the addres has blacklister privileges. Response: StatusResponse", - "type": "object", - "required": ["is_blacklister"], - "properties": { - "is_blacklister": { - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklistersResponse", - "type": "object", - "required": ["blacklisters"], - "properties": { - "blacklisters": { - "type": "object", - "properties": { - "limit": { - "type": ["integer", "null"], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "IsWhitelisted returns wether the user is whitelisted or not. Response: StatusResponse", - "type": "object", - "required": ["is_whitelisted"], - "properties": { - "is_whitelisted": { - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Whitelistees enumerates over all addresses on the whitelist. Response: WhitelisteesResponse", - "type": "object", - "required": ["whitelistees"], - "properties": { - "whitelistees": { - "type": "object", - "properties": { - "limit": { - "type": ["integer", "null"], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "IsWhitelister returns if the addres has whitelister privileges. Response: StatusResponse", - "type": "object", - "required": ["is_whitelister"], - "properties": { - "is_whitelister": { - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelistersResponse", - "type": "object", - "required": ["whitelisters"], - "properties": { - "whitelisters": { - "type": "object", - "properties": { - "limit": { - "type": ["integer", "null"], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "IsFreezer returns whether the address has freezer status. Response: StatusResponse", - "type": "object", - "required": ["is_freezer"], - "properties": { - "is_freezer": { - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "FreezerAllowances enumerates over all freezer addresses. Response: FreezerAllowancesResponse", - "type": "object", - "required": ["freezer_allowances"], - "properties": { - "freezer_allowances": { - "type": "object", - "properties": { - "limit": { - "type": ["integer", "null"], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns whether features that require MsgBeforeSendHook are enabled Most Cosmos chains do not support this feature yet.", - "type": "object", - "required": ["before_send_hook_features_enabled"], - "properties": { - "before_send_hook_features_enabled": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": null, - "sudo": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "SudoMsg", - "description": "SudoMsg is only exposed for internal Cosmos SDK modules to call. This is showing how we can expose \"admin\" functionality than can not be called by external users or contracts, but only trusted (native/Go) code in the blockchain", - "oneOf": [ - { - "type": "object", - "required": ["block_before_send"], - "properties": { - "block_before_send": { - "type": "object", - "required": ["amount", "from", "to"], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "from": { - "type": "string" - }, - "to": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Coin": { - "type": "object", - "required": ["amount", "denom"], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "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" - } - } - }, - "responses": { - "before_send_hook_features_enabled": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Boolean", - "type": "boolean" - }, - "blacklistees": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BlacklisteesResponse", - "type": "object", - "required": ["blacklistees"], - "properties": { - "blacklistees": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "StatusInfo": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, - "blacklisters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BlacklistersResponse", - "type": "object", - "required": ["blacklisters"], - "properties": { - "blacklisters": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "StatusInfo": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, - "burn_allowance": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllowanceResponse", - "type": "object", - "required": ["allowance"], - "properties": { - "allowance": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "burn_allowances": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllowancesResponse", - "type": "object", - "required": ["allowances"], - "properties": { - "allowances": { - "type": "array", - "items": { - "$ref": "#/definitions/AllowanceInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "AllowanceInfo": { - "type": "object", - "required": ["address", "allowance"], - "properties": { - "address": { - "type": "string" - }, - "allowance": { - "$ref": "#/definitions/Uint128" - } - }, - "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" - } - } - }, - "denom": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "DenomResponse", - "type": "object", - "required": ["denom"], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - }, - "freezer_allowances": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "FreezerAllowancesResponse", - "type": "object", - "required": ["freezers"], - "properties": { - "freezers": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "StatusInfo": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, - "is_blacklisted": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": ["status"], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "is_blacklister": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": ["status"], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "is_freezer": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": ["status"], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "is_frozen": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "IsFrozenResponse", - "type": "object", - "required": ["is_frozen"], - "properties": { - "is_frozen": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "is_whitelisted": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": ["status"], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "is_whitelister": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StatusResponse", - "type": "object", - "required": ["status"], - "properties": { - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "mint_allowance": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllowanceResponse", - "type": "object", - "required": ["allowance"], - "properties": { - "allowance": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "mint_allowances": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllowancesResponse", - "type": "object", - "required": ["allowances"], - "properties": { - "allowances": { - "type": "array", - "items": { - "$ref": "#/definitions/AllowanceInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "AllowanceInfo": { - "type": "object", - "required": ["address", "allowance"], - "properties": { - "address": { - "type": "string" - }, - "allowance": { - "$ref": "#/definitions/Uint128" - } - }, - "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" - } - } - }, - "owner": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerResponse", - "type": "object", - "required": ["address"], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - }, - "whitelistees": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WhitelisteesResponse", - "type": "object", - "required": ["whitelistees"], - "properties": { - "whitelistees": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "StatusInfo": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, - "whitelisters": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WhitelistersResponse", - "type": "object", - "required": ["whitelisters"], - "properties": { - "whitelisters": { - "type": "array", - "items": { - "$ref": "#/definitions/StatusInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "StatusInfo": { - "type": "object", - "required": ["address", "status"], - "properties": { - "address": { - "type": "string" - }, - "status": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - } - } -} diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 111ef53a7..6a915df65 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -87,12 +87,8 @@ pub fn execute( amount, from_address: address, } => execute::burn(deps, env, info, amount, address), - ExecuteMsg::Blacklist { address, status } => { - execute::blacklist(deps, env, info, address, status) - } - ExecuteMsg::Whitelist { address, status } => { - execute::whitelist(deps, info, address, status) - } + ExecuteMsg::Deny { address, status } => execute::deny(deps, env, info, address, status), + ExecuteMsg::Allow { address, status } => execute::allow(deps, info, address, status), ExecuteMsg::Freeze { status } => execute::freeze(deps, info, status), ExecuteMsg::ForceTransfer { amount, @@ -101,11 +97,11 @@ pub fn execute( } => execute::force_transfer(deps, env, info, amount, from_address, to_address), // Admin functions - ExecuteMsg::ChangeTokenFactoryAdmin { new_admin } => { - execute::change_tokenfactory_admin(deps, info, new_admin) + ExecuteMsg::UpdateTokenFactoryAdmin { new_admin } => { + execute::update_tokenfactory_admin(deps, info, new_admin) } - ExecuteMsg::ChangeContractOwner { new_owner } => { - execute::change_contract_owner(deps, info, new_owner) + ExecuteMsg::UpdateContractOwner { new_owner } => { + execute::update_contract_owner(deps, info, new_owner) } ExecuteMsg::SetMinterAllowance { address, allowance } => { execute::set_minter(deps, info, address, allowance) @@ -114,15 +110,6 @@ pub fn execute( execute::set_burner(deps, info, address, allowance) } ExecuteMsg::SetBeforeSendHook {} => execute::set_before_send_hook(deps, env, info), - ExecuteMsg::SetBlacklister { address, status } => { - execute::set_blacklister(deps, info, address, status) - } - ExecuteMsg::SetWhitelister { address, status } => { - execute::set_whitelister(deps, info, address, status) - } - ExecuteMsg::SetFreezer { address, status } => { - execute::set_freezer(deps, info, address, status) - } ExecuteMsg::SetDenomMetadata { metadata } => { execute::set_denom_metadata(deps, env, info, metadata) } @@ -145,52 +132,32 @@ pub fn sudo( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::IsFrozen {} => to_binary(&queries::query_is_frozen(deps)?), - QueryMsg::Denom {} => to_binary(&queries::query_denom(deps)?), - QueryMsg::Owner {} => to_binary(&queries::query_owner(deps)?), + QueryMsg::Allowlist { start_after, limit } => { + to_binary(&queries::query_allowlist(deps, start_after, limit)?) + } + QueryMsg::BeforeSendHookFeaturesEnabled {} => { + to_binary(&queries::query_before_send_hook_features(deps)?) + } QueryMsg::BurnAllowance { address } => { to_binary(&queries::query_burn_allowance(deps, address)?) } QueryMsg::BurnAllowances { start_after, limit } => { to_binary(&queries::query_burn_allowances(deps, start_after, limit)?) } + QueryMsg::Denom {} => to_binary(&queries::query_denom(deps)?), + QueryMsg::Denylist { start_after, limit } => { + to_binary(&queries::query_denylist(deps, start_after, limit)?) + } + QueryMsg::IsAllowed { address } => to_binary(&queries::query_is_allowed(deps, address)?), + QueryMsg::IsDenied { address } => to_binary(&queries::query_is_denied(deps, address)?), + QueryMsg::IsFrozen {} => to_binary(&queries::query_is_frozen(deps)?), + QueryMsg::Owner {} => to_binary(&queries::query_owner(deps)?), QueryMsg::MintAllowance { address } => { to_binary(&queries::query_mint_allowance(deps, address)?) } QueryMsg::MintAllowances { start_after, limit } => { to_binary(&queries::query_mint_allowances(deps, start_after, limit)?) } - QueryMsg::IsBlacklisted { address } => { - to_binary(&queries::query_is_blacklisted(deps, address)?) - } - QueryMsg::Blacklistees { start_after, limit } => { - to_binary(&queries::query_blacklistees(deps, start_after, limit)?) - } - QueryMsg::IsBlacklister { address } => { - to_binary(&queries::query_is_blacklister(deps, address)?) - } - QueryMsg::Blacklisters { start_after, limit } => { - to_binary(&queries::query_blacklisters(deps, start_after, limit)?) - } - QueryMsg::IsWhitelisted { address } => { - to_binary(&queries::query_is_whitelisted(deps, address)?) - } - QueryMsg::Whitelistees { start_after, limit } => { - to_binary(&queries::query_whitelistees(deps, start_after, limit)?) - } - QueryMsg::IsWhitelister { address } => { - to_binary(&queries::query_is_whitelister(deps, address)?) - } - QueryMsg::Whitelisters { start_after, limit } => { - to_binary(&queries::query_whitelisters(deps, start_after, limit)?) - } - QueryMsg::IsFreezer { address } => to_binary(&queries::query_is_freezer(deps, address)?), - QueryMsg::FreezerAllowances { start_after, limit } => to_binary( - &queries::query_freezer_allowances(deps, start_after, limit)?, - ), - QueryMsg::BeforeSendHookFeaturesEnabled {} => { - to_binary(&queries::query_before_send_hook_features(deps)?) - } } } @@ -218,7 +185,7 @@ pub fn reply( // SetBeforeSendHook to this contract // this will trigger sudo endpoint before any bank send - // which makes blacklisting / freezing possible + // which makes denylisting / freezing possible let msg_set_beforesend_hook: CosmosMsg = MsgSetBeforeSendHook { sender: env.contract.address.to_string(), denom: new_token_denom.clone(), diff --git a/contracts/external/cw-tokenfactory-issuer/src/error.rs b/contracts/external/cw-tokenfactory-issuer/src/error.rs index 7d5c03c86..1f6ef792a 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/error.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/error.rs @@ -12,11 +12,11 @@ pub enum ContractError { #[error("MsgBeforeSendHook is already configured. Features requiring it are already enabled.")] BeforeSendHookAlreadyEnabled {}, - #[error("The address '{address}' is blacklisted")] - Blacklisted { address: String }, + #[error("The address '{address}' is denied transfer abilities")] + Denied { address: String }, - #[error("Cannot blacklist the issuer contract itself")] - CannotBlacklistSelf {}, + #[error("Cannot denylist the issuer contract itself")] + CannotDenylistSelf {}, #[error("The contract is frozen for denom {denom:?}")] ContractFrozen { denom: String }, diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 8432b3457..60d793768 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -6,13 +6,10 @@ use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; use crate::error::ContractError; -use crate::helpers::{ - check_before_send_hook_features_enabled, check_bool_allowance, check_is_contract_owner, -}; +use crate::helpers::{check_before_send_hook_features_enabled, check_is_contract_owner}; use crate::state::{ - BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTERS, BURNER_ALLOWANCES, - DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, WHITELISTED_ADDRESSES, - WHITELISTERS, + ALLOWLIST, BEFORE_SEND_HOOK_FEATURES_ENABLED, BURNER_ALLOWANCES, DENOM, DENYLIST, IS_FROZEN, + MINTER_ALLOWANCES, OWNER, }; pub fn mint( @@ -123,7 +120,7 @@ pub fn burn( .add_attribute("amount", amount)) } -pub fn change_contract_owner( +pub fn update_contract_owner( deps: DepsMut, info: MessageInfo, new_owner: String, @@ -131,6 +128,8 @@ pub fn change_contract_owner( // Only allow current contract owner to change owner check_is_contract_owner(deps.as_ref(), info.sender)?; + // TODO make sure it's possible to renounce ownership all together + // TODO add test for NO OWNER // validate that new owner is a valid address let new_owner_addr = deps.api.addr_validate(&new_owner)?; @@ -138,11 +137,11 @@ pub fn change_contract_owner( OWNER.save(deps.storage, &new_owner_addr)?; Ok(Response::new() - .add_attribute("action", "change_contract_owner") + .add_attribute("action", "update_contract_owner") .add_attribute("new_owner", new_owner)) } -pub fn change_tokenfactory_admin( +pub fn update_tokenfactory_admin( deps: DepsMut, info: MessageInfo, new_admin: String, @@ -150,6 +149,8 @@ pub fn change_tokenfactory_admin( // Only allow current contract owner to change tokenfactory admin check_is_contract_owner(deps.as_ref(), info.sender)?; + // TODO make sure it's possible to renounce ownership all together + // TODO add test for NO ADMIN // validate that the new admin is a valid address let new_admin_addr = deps.api.addr_validate(&new_admin)?; @@ -162,7 +163,7 @@ pub fn change_tokenfactory_admin( // dispatch change admin msg Ok(Response::new() .add_message(change_admin_msg) - .add_attribute("action", "change_tokenfactory_admin") + .add_attribute("action", "update_tokenfactory_admin") .add_attribute("new_admin", new_admin)) } @@ -183,91 +184,6 @@ pub fn set_denom_metadata( })) } -pub fn set_blacklister( - deps: DepsMut, - info: MessageInfo, - address: String, - status: bool, -) -> Result, ContractError> { - check_before_send_hook_features_enabled(deps.as_ref())?; - - // Only allow current contract owner to set blacklister permission - check_is_contract_owner(deps.as_ref(), info.sender)?; - - let address = deps.api.addr_validate(&address)?; - - // set blacklister status - // NOTE: Does not check if new status is same as old status - // but if status is false, remove if exist to reduce space usage - if status { - BLACKLISTERS.save(deps.storage, &address, &status)?; - } else { - BLACKLISTERS.remove(deps.storage, &address); - } - - Ok(Response::new() - .add_attribute("action", "set_blacklister") - .add_attribute("blacklister", address) - .add_attribute("status", status.to_string())) -} - -pub fn set_whitelister( - deps: DepsMut, - info: MessageInfo, - address: String, - status: bool, -) -> Result, ContractError> { - check_before_send_hook_features_enabled(deps.as_ref())?; - - // Only allow current contract owner to set blacklister permission - check_is_contract_owner(deps.as_ref(), info.sender)?; - - let address = deps.api.addr_validate(&address)?; - - // set blacklister status - // NOTE: Does not check if new status is same as old status - // but if status is false, remove if exist to reduce space usage - if status { - WHITELISTERS.save(deps.storage, &address, &status)?; - } else { - WHITELISTERS.remove(deps.storage, &address); - } - - // Return OK - Ok(Response::new() - .add_attribute("action", "set_blacklister") - .add_attribute("blacklister", address) - .add_attribute("status", status.to_string())) -} - -pub fn set_freezer( - deps: DepsMut, - info: MessageInfo, - address: String, - status: bool, -) -> Result, ContractError> { - check_before_send_hook_features_enabled(deps.as_ref())?; - - // Only allow current contract owner to set freezer permission - check_is_contract_owner(deps.as_ref(), info.sender)?; - - let address = deps.api.addr_validate(&address)?; - - // set freezer status - // NOTE: Does not check if new status is same as old status - // but if status is false, remove if exist to reduce space usage - if status { - FREEZER_ALLOWANCES.save(deps.storage, &address, &status)?; - } else { - FREEZER_ALLOWANCES.remove(deps.storage, &address); - } - - Ok(Response::new() - .add_attribute("action", "set_freezer") - .add_attribute("freezer", address) - .add_attribute("status", status.to_string())) -} - pub fn set_before_send_hook( deps: DepsMut, env: Env, @@ -286,10 +202,10 @@ pub fn set_before_send_hook( // SetBeforeSendHook to this contract // this will trigger sudo endpoint before any bank send - // which makes blacklisting / freezing possible + // which makes denylisting / freezing possible let msg_set_beforesend_hook: CosmosMsg = MsgSetBeforeSendHook { sender: env.contract.address.to_string(), - denom: denom.clone(), + denom, cosmwasm_address: env.contract.address.to_string(), } .into(); @@ -361,8 +277,8 @@ pub fn freeze( ) -> Result, ContractError> { check_before_send_hook_features_enabled(deps.as_ref())?; - // check to make sure that the sender has freezer permissions - check_bool_allowance(deps.as_ref(), info, FREEZER_ALLOWANCES)?; + // Only allow current contract owner to call this method + check_is_contract_owner(deps.as_ref(), info.sender)?; // Update config frozen status // NOTE: Does not check if new status is same as old status @@ -373,7 +289,7 @@ pub fn freeze( .add_attribute("status", status.to_string())) } -pub fn blacklist( +pub fn deny( deps: DepsMut, env: Env, info: MessageInfo, @@ -382,33 +298,33 @@ pub fn blacklist( ) -> Result, ContractError> { check_before_send_hook_features_enabled(deps.as_ref())?; - // check to make sure that the sender has blacklister permissions - check_bool_allowance(deps.as_ref(), info, BLACKLISTERS)?; + // Only allow current contract owner to call this method + check_is_contract_owner(deps.as_ref(), info.sender)?; let address = deps.api.addr_validate(&address)?; - // Check this issuer contract is not blacklisting itself + // Check this issuer contract is not denylisting itself if address == env.contract.address { - return Err(ContractError::CannotBlacklistSelf {}); + return Err(ContractError::CannotDenylistSelf {}); } - // update blacklisted status - // validate that blacklisteed is a valid address + // update denylist status + // validate that denylisteed is a valid address // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { - BLACKLISTED_ADDRESSES.save(deps.storage, &address, &status)?; + DENYLIST.save(deps.storage, &address, &status)?; } else { - BLACKLISTED_ADDRESSES.remove(deps.storage, &address); + DENYLIST.remove(deps.storage, &address); } Ok(Response::new() - .add_attribute("action", "blacklist") + .add_attribute("action", "denylist") .add_attribute("address", address) .add_attribute("status", status.to_string())) } -pub fn whitelist( +pub fn allow( deps: DepsMut, info: MessageInfo, address: String, @@ -416,23 +332,23 @@ pub fn whitelist( ) -> Result, ContractError> { check_before_send_hook_features_enabled(deps.as_ref())?; - // check to make sure that the sender has blacklister permissions - check_bool_allowance(deps.as_ref(), info, WHITELISTERS)?; + // Only allow current contract owner to call this method + check_is_contract_owner(deps.as_ref(), info.sender)?; let address = deps.api.addr_validate(&address)?; - // update blacklisted status - // validate that blacklisteed is a valid address + // update denylist status + // validate that denylisteed is a valid address // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { - WHITELISTED_ADDRESSES.save(deps.storage, &address, &status)?; + ALLOWLIST.save(deps.storage, &address, &status)?; } else { - WHITELISTED_ADDRESSES.remove(deps.storage, &address); + ALLOWLIST.remove(deps.storage, &address); } Ok(Response::new() - .add_attribute("action", "whitelist") + .add_attribute("action", "allowlist") .add_attribute("address", address) .add_attribute("status", status.to_string())) } @@ -455,7 +371,7 @@ pub fn force_transfer( let force_transfer_msg: CosmosMsg = MsgForceTransfer { transfer_from_address: from_address.clone(), transfer_to_address: to_address.clone(), - amount: Some(Coin::new(amount.u128(), denom.clone()).into()), + amount: Some(Coin::new(amount.u128(), denom).into()), sender: env.contract.address.to_string(), } .into(); diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index 5262f9a79..589c168af 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -1,10 +1,8 @@ use crate::state::{ - BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, DENOM, IS_FROZEN, OWNER, - WHITELISTED_ADDRESSES, + ALLOWLIST, BEFORE_SEND_HOOK_FEATURES_ENABLED, DENOM, DENYLIST, IS_FROZEN, OWNER, }; use crate::ContractError; -use cosmwasm_std::{Addr, Coin, Deps, MessageInfo, Uint128}; -use cw_storage_plus::Map; +use cosmwasm_std::{Addr, Coin, Deps, Uint128}; use token_bindings::TokenFactoryQuery; pub fn check_contract_has_funds( @@ -54,37 +52,14 @@ pub fn check_before_send_hook_features_enabled( } } -pub fn check_bool_allowance( - deps: Deps, - info: MessageInfo, - allowances: Map<&Addr, bool>, -) -> Result<(), ContractError> { - let res = allowances.load(deps.storage, &info.sender); - match res { - Ok(authorized) => { - if !authorized { - return Err(ContractError::Unauthorized {}); - } - } - Err(error) => { - if let cosmwasm_std::StdError::NotFound { .. } = error { - return Err(ContractError::Unauthorized {}); - } else { - return Err(ContractError::Std(error)); - } - } - } - Ok(()) -} - -pub fn check_is_not_blacklisted( +pub fn check_is_not_denied( deps: Deps, address: String, ) -> Result<(), ContractError> { let addr = deps.api.addr_validate(&address)?; - if let Some(is_blacklisted) = BLACKLISTED_ADDRESSES.may_load(deps.storage, &addr)? { - if is_blacklisted { - return Err(ContractError::Blacklisted { address }); + if let Some(is_denied) = DENYLIST.may_load(deps.storage, &addr)? { + if is_denied { + return Err(ContractError::Denied { address }); } }; Ok(()) @@ -105,8 +80,8 @@ pub fn check_is_not_frozen( let is_denom_frozen = is_frozen && denom == contract_denom; if is_denom_frozen { let addr = deps.api.addr_validate(address)?; - if let Some(is_whitelisted) = WHITELISTED_ADDRESSES.may_load(deps.storage, &addr)? { - if is_whitelisted { + if let Some(is_allowed) = ALLOWLIST.may_load(deps.storage, &addr)? { + if is_allowed { return Ok(()); } }; diff --git a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs index af4166d35..2d552692d 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Coin, DepsMut, Response}; use token_bindings::TokenFactoryQuery; use crate::error::ContractError; -use crate::helpers::{check_is_not_blacklisted, check_is_not_frozen}; +use crate::helpers::{check_is_not_denied, check_is_not_frozen}; pub fn beforesend_hook( deps: DepsMut, @@ -13,9 +13,9 @@ pub fn beforesend_hook( // assert that denom of this contract is not frozen check_is_not_frozen(deps.as_ref(), &from, &coin.denom)?; - // assert that neither 'from' or 'to' address is blacklisted - check_is_not_blacklisted(deps.as_ref(), from)?; - check_is_not_blacklisted(deps.as_ref(), to)?; + // assert that neither 'from' or 'to' address is denylist + check_is_not_denied(deps.as_ref(), from)?; + check_is_not_denied(deps.as_ref(), to)?; Ok(Response::new().add_attribute("action", "before_send")) } diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index d2dd65239..be37f4b42 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -22,37 +22,8 @@ pub struct MigrateMsg {} #[cw_serde] pub enum ExecuteMsg { - /// Change the admin of the Token Factory denom itself. - ChangeTokenFactoryAdmin { new_admin: String }, - - /// Change the owner of this contract who is allowed to call privileged methods. - ChangeContractOwner { new_owner: String }, - - /// Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata. - SetDenomMetadata { metadata: Metadata }, - - /// Grant/revoke mint allowance. - SetMinterAllowance { address: String, allowance: Uint128 }, - - /// Grant/revoke burn allowance. - SetBurnerAllowance { address: String, allowance: Uint128 }, - - /// Grant/revoke permission to blacklist addresses - SetBlacklister { address: String, status: bool }, - - /// Grant/revoke permission to blacklist addresses - SetWhitelister { address: String, status: bool }, - - /// Attempt to SetBeforeSendHook on the token attached to this contract. - /// This will fail if the token already has a SetBeforeSendHook or the chain - /// still does not support it. - SetBeforeSendHook {}, - - /// Grant/revoke permission to freeze the token - SetFreezer { address: String, status: bool }, - - /// Mint token to address. Mint allowance is required and wiil be deducted after successful mint. - Mint { to_address: String, amount: Uint128 }, + /// Allow adds the target address to the allowlist to be able to send tokens even if the token is frozen. + Allow { address: String, status: bool }, /// Burn token to address. Burn allowance is required and wiil be deducted after successful burn. Burn { @@ -60,15 +31,17 @@ pub enum ExecuteMsg { amount: Uint128, }, - /// Block target address from sending/receiving token attached to this contract - /// tokenfactory's beforesend listener must be set to this contract in order for it to work as intended. - Blacklist { address: String, status: bool }, + /// Mint token to address. Mint allowance is required and wiil be deducted after successful mint. + Mint { to_address: String, amount: Uint128 }, - /// Whitelist target address to be able to send tokens even if the token is frozen. - Whitelist { address: String, status: bool }, + /// Deny adds the target address to the denylist, whis prevents them from sending/receiving the token attached + /// to this contract tokenfactory's BeforeSendHook listener must be set to this contract in order for this + /// feature to work as intended. + Deny { address: String, status: bool }, - /// Block every token transfers of the token attached to this contract - /// tokenfactory's beforesend listener must be set to this contract in order for it to work as intended. + /// Block every token transfers of the token attached to this contract. + /// Token Factory's BeforeSendHook listener must be set to this contract in order for this + /// feature to work as intended. Freeze { status: bool }, /// Force transfer token from one address to another. @@ -77,6 +50,33 @@ pub enum ExecuteMsg { from_address: String, to_address: String, }, + + /// Attempt to SetBeforeSendHook on the token attached to this contract. + /// This will fail if the token already has a SetBeforeSendHook or the chain + /// still does not support it. + SetBeforeSendHook {}, + + /// Grant/revoke burn allowance. + SetBurnerAllowance { address: String, allowance: Uint128 }, + + /// Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata. + SetDenomMetadata { metadata: Metadata }, + + /// Grant/revoke mint allowance. + SetMinterAllowance { address: String, allowance: Uint128 }, + + /// Updates the admin of the Token Factory token. + /// Normally this is the cw-tokenfactory-issuer contract itself. + /// This is intended to be used only if you seek to transfer ownership + /// of the Token somewhere else (i.e. to another management contract). + UpdateTokenFactoryAdmin { new_admin: String }, + + /// Updates the owner of this contract who is allowed to call privileged methods. + /// NOTE: this is separate from the Token Factory token admin, for this contract to work + /// at all, it needs to the be the Token Factory token admin. + /// + /// Normally, the contract owner will be a DAO. + UpdateContractOwner { new_owner: String }, } /// SudoMsg is only exposed for internal Cosmos SDK modules to call. @@ -97,75 +97,59 @@ pub enum QueryMsg { /// IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse #[returns(IsFrozenResponse)] IsFrozen {}, + /// Denom returns the token denom that this contract is the admin for. Response: DenomResponse #[returns(DenomResponse)] Denom {}, + /// Owner returns the owner of the contract. Response: OwnerResponse #[returns(OwnerResponse)] Owner {}, + /// Allowance returns the allowance of the specified address. Response: AllowanceResponse #[returns(AllowanceResponse)] BurnAllowance { address: String }, + /// Allowances Enumerates over all allownances. Response: Vec #[returns(AllowancesResponse)] BurnAllowances { start_after: Option, limit: Option, }, + /// Allowance returns the allowance of the specified user. Response: AllowanceResponse #[returns(AllowanceResponse)] MintAllowance { address: String }, + /// Allowances Enumerates over all allownances. Response: AllowancesResponse #[returns(AllowancesResponse)] MintAllowances { start_after: Option, limit: Option, }, - /// IsBlacklisted returns wether the user is blacklisted or not. Response: StatusResponse - #[returns(StatusResponse)] - IsBlacklisted { address: String }, - /// Blacklistees enumerates over all addresses on the blacklist. Response: BlacklisteesResponse - #[returns(BlacklisteesResponse)] - Blacklistees { - start_after: Option, - limit: Option, - }, - /// IsBlacklister returns if the addres has blacklister privileges. Response: StatusResponse - #[returns(StatusResponse)] - IsBlacklister { address: String }, - /// Blacklisters Enumerates over all the addresses with blacklister privileges. Response: BlacklistersResponse - #[returns(BlacklistersResponse)] - Blacklisters { - start_after: Option, - limit: Option, - }, - /// IsWhitelisted returns wether the user is whitelisted or not. Response: StatusResponse - #[returns(StatusResponse)] - IsWhitelisted { address: String }, - /// Whitelistees enumerates over all addresses on the whitelist. Response: WhitelisteesResponse - #[returns(WhitelisteesResponse)] - Whitelistees { - start_after: Option, - limit: Option, - }, - /// IsWhitelister returns if the addres has whitelister privileges. Response: StatusResponse + + /// IsDenied returns wether the user is on denylist or not. Response: StatusResponse #[returns(StatusResponse)] - IsWhitelister { address: String }, - /// Whitelisters Enumerates over all the addresses with whitelister privileges. Response: WhitelistersResponse - #[returns(WhitelistersResponse)] - Whitelisters { + IsDenied { address: String }, + + /// Denylist enumerates over all addresses on the denylist. Response: DenylistResponse + #[returns(DenylistResponse)] + Denylist { start_after: Option, limit: Option, }, - /// IsFreezer returns whether the address has freezer status. Response: StatusResponse + + /// IsAllowed returns wether the user is on the allowlist or not. Response: StatusResponse #[returns(StatusResponse)] - IsFreezer { address: String }, - /// FreezerAllowances enumerates over all freezer addresses. Response: FreezerAllowancesResponse - #[returns(FreezerAllowancesResponse)] - FreezerAllowances { + IsAllowed { address: String }, + + /// Allowlist enumerates over all addresses on the allowlist. Response: AllowlistResponse + #[returns(AllowlistResponse)] + Allowlist { start_after: Option, limit: Option, }, + /// Returns whether features that require MsgBeforeSendHook are enabled /// Most Cosmos chains do not support this feature yet. #[returns(bool)] @@ -217,26 +201,11 @@ pub struct StatusInfo { } #[cw_serde] -pub struct BlacklisteesResponse { - pub blacklistees: Vec, -} - -#[cw_serde] -pub struct BlacklistersResponse { - pub blacklisters: Vec, -} - -#[cw_serde] -pub struct WhitelisteesResponse { - pub whitelistees: Vec, -} - -#[cw_serde] -pub struct WhitelistersResponse { - pub whitelisters: Vec, +pub struct DenylistResponse { + pub denylist: Vec, } #[cw_serde] -pub struct FreezerAllowancesResponse { - pub freezers: Vec, +pub struct AllowlistResponse { + pub allowlist: Vec, } diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index a129ee0f7..bb8913c60 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -3,14 +3,12 @@ use cw_storage_plus::{Bound, Map}; use token_bindings::TokenFactoryQuery; use crate::msg::{ - AllowanceInfo, AllowanceResponse, AllowancesResponse, BlacklisteesResponse, - BlacklistersResponse, DenomResponse, FreezerAllowancesResponse, IsFrozenResponse, - OwnerResponse, StatusInfo, StatusResponse, WhitelisteesResponse, WhitelistersResponse, + AllowanceInfo, AllowanceResponse, AllowancesResponse, AllowlistResponse, DenomResponse, + DenylistResponse, IsFrozenResponse, OwnerResponse, StatusInfo, StatusResponse, }; use crate::state::{ - BEFORE_SEND_HOOK_FEATURES_ENABLED, BLACKLISTED_ADDRESSES, BLACKLISTERS, BURNER_ALLOWANCES, - DENOM, FREEZER_ALLOWANCES, IS_FROZEN, MINTER_ALLOWANCES, OWNER, WHITELISTED_ADDRESSES, - WHITELISTERS, + ALLOWLIST, BEFORE_SEND_HOOK_FEATURES_ENABLED, BURNER_ALLOWANCES, DENOM, DENYLIST, IS_FROZEN, + MINTER_ALLOWANCES, OWNER, }; // Default settings for pagination @@ -105,26 +103,30 @@ pub fn query_burn_allowances( }) } -pub fn query_is_blacklisted( +pub fn query_is_denied( deps: Deps, address: String, ) -> StdResult { - let status = BLACKLISTED_ADDRESSES + let status = DENYLIST .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } -pub fn query_is_whitelisted( +pub fn query_is_allowed( deps: Deps, address: String, ) -> StdResult { - let status = WHITELISTED_ADDRESSES + let status = ALLOWLIST .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } +pub fn query_before_send_hook_features(deps: Deps) -> StdResult { + BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage) +} + pub fn query_status_map( deps: Deps, start_after: Option, @@ -153,88 +155,22 @@ pub fn query_status_map( .collect() } -pub fn query_blacklistees( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - Ok(BlacklisteesResponse { - blacklistees: query_status_map(deps, start_after, limit, BLACKLISTED_ADDRESSES)?, - }) -} - -pub fn query_is_blacklister( - deps: Deps, - address: String, -) -> StdResult { - let status = BLACKLISTERS - .load(deps.storage, &deps.api.addr_validate(&address)?) - .unwrap_or(false); - Ok(StatusResponse { status }) -} - -pub fn query_blacklisters( +pub fn query_allowlist( deps: Deps, start_after: Option, limit: Option, -) -> StdResult { - Ok(BlacklistersResponse { - blacklisters: query_status_map(deps, start_after, limit, BLACKLISTERS)?, +) -> StdResult { + Ok(AllowlistResponse { + allowlist: query_status_map(deps, start_after, limit, ALLOWLIST)?, }) } -pub fn query_whitelistees( +pub fn query_denylist( deps: Deps, start_after: Option, limit: Option, -) -> StdResult { - Ok(WhitelisteesResponse { - whitelistees: query_status_map(deps, start_after, limit, WHITELISTED_ADDRESSES)?, +) -> StdResult { + Ok(DenylistResponse { + denylist: query_status_map(deps, start_after, limit, DENYLIST)?, }) } - -pub fn query_is_whitelister( - deps: Deps, - address: String, -) -> StdResult { - let status = WHITELISTERS - .load(deps.storage, &deps.api.addr_validate(&address)?) - .unwrap_or(false); - Ok(StatusResponse { status }) -} - -pub fn query_whitelisters( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - Ok(WhitelistersResponse { - whitelisters: query_status_map(deps, start_after, limit, WHITELISTERS)?, - }) -} - -pub fn query_freezer_allowances( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - Ok(FreezerAllowancesResponse { - freezers: query_status_map(deps, start_after, limit, FREEZER_ALLOWANCES)?, - }) -} - -pub fn query_is_freezer( - deps: Deps, - address: String, -) -> StdResult { - let status = FREEZER_ALLOWANCES - .load(deps.storage, &deps.api.addr_validate(&address)?) - .unwrap_or(false); - Ok(StatusResponse { status }) -} - -pub fn query_before_send_hook_features(deps: Deps) -> StdResult { - BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage) -} - -// query inspiration see https://github.com/mars-protocol/fields-of-mars/blob/v1.0.0/packages/fields-of-mars/src/martian_field.rs#L465-L473 diff --git a/contracts/external/cw-tokenfactory-issuer/src/state.rs b/contracts/external/cw-tokenfactory-issuer/src/state.rs index 9942ea759..c688949ac 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/state.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/state.rs @@ -4,11 +4,11 @@ use cw_storage_plus::{Item, Map}; pub const OWNER: Item = Item::new("owner"); pub const DENOM: Item = Item::new("denom"); -/// Blacklisted addresses prevented from transferring tokens -pub const BLACKLISTED_ADDRESSES: Map<&Addr, bool> = Map::new("blacklisted_addresses"); +/// Denylist addresses prevented from transferring tokens +pub const DENYLIST: Map<&Addr, bool> = Map::new("denylist"); /// Addresses allowed to transfer tokens even if the token is frozen -pub const WHITELISTED_ADDRESSES: Map<&Addr, bool> = Map::new("whitelisted_addresses"); +pub const ALLOWLIST: Map<&Addr, bool> = Map::new("allowlist"); /// Whether or not features that require MsgBeforeSendHook are enabled /// Many Token Factory chains do not yet support MsgBeforeSendHook @@ -17,11 +17,6 @@ pub const BEFORE_SEND_HOOK_FEATURES_ENABLED: Item = Item::new("hook_featur /// Whether or not token transfers are frozen pub const IS_FROZEN: Item = Item::new("is_frozen"); -// Address able to manange blacklists and whitelists -pub const BLACKLISTERS: Map<&Addr, bool> = Map::new("blacklisters"); -pub const WHITELISTERS: Map<&Addr, bool> = Map::new("whitelisters"); - /// Allowances pub const BURNER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("burner_allowances"); -pub const FREEZER_ALLOWANCES: Map<&Addr, bool> = Map::new("freezer_allowances"); pub const MINTER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("minter_allowances"); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs new file mode 100644 index 000000000..fe1c59ae4 --- /dev/null +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs @@ -0,0 +1,106 @@ +use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; +use osmosis_test_tube::Account; + +use crate::test_env::{ + test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, +}; + +#[test] +fn allowlist_by_owner_should_pass() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let allowlistee = &env.test_accs[2]; + + env.cw_tokenfactory_issuer + .allow(&allowlistee.address(), true, owner) + .unwrap(); + + // should be allowlist after set true + assert!( + env.cw_tokenfactory_issuer + .query_is_allowed(&allowlistee.address()) + .unwrap() + .status + ); + + env.cw_tokenfactory_issuer + .allow(&allowlistee.address(), false, owner) + .unwrap(); + + // should be unallowlist after set false + assert!( + !env.cw_tokenfactory_issuer + .query_is_allowed(&allowlistee.address()) + .unwrap() + .status + ); +} + +#[test] +fn allowlist_by_non_owern_should_fail() { + let env = TestEnv::default(); + let non_owner = &env.test_accs[1]; + let allowlistee = &env.test_accs[2]; + let err = env + .cw_tokenfactory_issuer + .allow(&allowlistee.address(), true, non_owner) + .unwrap_err(); + + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + ); +} + +// query allowlist +#[test] +fn query_allowlist_within_default_limit() { + test_query_within_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + env.cw_tokenfactory_issuer + .allow(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_allowlist(start_after, limit) + .unwrap() + .allowlist + } + }, + ); +} + +#[test] +fn query_allowlist_over_default_limit() { + test_query_over_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + env.cw_tokenfactory_issuer + .allow(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_allowlist(start_after, limit) + .unwrap() + .allowlist + } + }, + ); +} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs index 9464d8035..c7ecf2c33 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs @@ -29,10 +29,6 @@ fn before_send_should_block_on_frozen() { let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; // freeze - env.cw_tokenfactory_issuer - .set_freezer(&owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); // bank send should fail @@ -48,37 +44,31 @@ fn before_send_should_block_on_frozen() { } #[test] -fn white_listed_addresses_can_transfer_when_token_frozen() { +fn allowlisted_addresses_can_transfer_when_token_frozen() { let env = TestEnv::default(); let owner = &env.test_accs[0]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - let whitelistee = &env.test_accs[1]; + let allowlistee = &env.test_accs[1]; let other = &env.test_accs[2]; // freeze - env.cw_tokenfactory_issuer - .set_freezer(&owner.address(), true, owner) - .unwrap(); env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); // bank send should fail let err = env - .send_tokens(whitelistee.address(), coins(10000, denom.clone()), owner) + .send_tokens(allowlistee.address(), coins(10000, denom.clone()), owner) .unwrap_err(); assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); - // Whitelist address + // Allowlist address env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), true, owner) + .allow(&allowlistee.address(), true, owner) .unwrap(); // bank send should pass - env.send_tokens(other.address(), coins(10000, denom.clone()), whitelistee) + env.send_tokens(other.address(), coins(10000, denom.clone()), allowlistee) .unwrap_err(); - // Non whitelisted address can't transfer, bank send should fail + // Non allowlist address can't transfer, bank send should fail let err = env .send_tokens(other.address(), coins(10000, denom.clone()), owner) .unwrap_err(); @@ -86,26 +76,23 @@ fn white_listed_addresses_can_transfer_when_token_frozen() { } #[test] -fn before_send_should_block_sending_from_blacklisted_address() { +fn before_send_should_block_sending_from_denylist_address() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let blacklistee = &env.test_accs[1]; + let denylistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // mint to blacklistee + // mint to denylistee env.cw_tokenfactory_issuer .set_minter(&owner.address(), 20000, owner) .unwrap(); env.cw_tokenfactory_issuer - .mint(&blacklistee.address(), 20000, owner) + .mint(&denylistee.address(), 20000, owner) .unwrap(); - // blacklist - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); + // denylist env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, owner) + .deny(&denylistee.address(), true, owner) .unwrap(); // bank send should fail @@ -113,19 +100,19 @@ fn before_send_should_block_sending_from_blacklisted_address() { .send_tokens( env.test_accs[2].address(), coins(10000, denom.clone()), - blacklistee, + denylistee, ) .unwrap_err(); - let blacklistee_addr = blacklistee.address(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{blacklistee_addr}' is blacklisted: execute wasm contract failed") }); + let denylistee_addr = denylistee.address(); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denylist: execute wasm contract failed") }); } #[test] -fn before_send_should_block_sending_to_blacklisted_address() { +fn before_send_should_block_sending_to_denylist_address() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let blacklistee = &env.test_accs[1]; + let denylistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; // mint to self @@ -136,19 +123,16 @@ fn before_send_should_block_sending_to_blacklisted_address() { .mint(&owner.address(), 10000, owner) .unwrap(); - // blacklist - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); + // denylist env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, owner) + .deny(&denylistee.address(), true, owner) .unwrap(); // bank send should fail let err = env - .send_tokens(blacklistee.address(), coins(10000, denom.clone()), owner) + .send_tokens(denylistee.address(), coins(10000, denom.clone()), owner) .unwrap_err(); - let blacklistee_addr = blacklistee.address(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{blacklistee_addr}' is blacklisted: execute wasm contract failed") }); + let denylistee_addr = denylistee.address(); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denylist: execute wasm contract failed") }); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs deleted file mode 100644 index a831367c7..000000000 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/blacklist.rs +++ /dev/null @@ -1,354 +0,0 @@ -use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; -use osmosis_test_tube::Account; - -use crate::test_env::{ - test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, -}; - -#[test] -fn set_blacklister_performed_by_contract_owner_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), true, owner) - .unwrap(); - - let is_blacklister = env - .cw_tokenfactory_issuer - .query_is_blacklister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(is_blacklister); - - env.cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), false, owner) - .unwrap(); - - let is_blacklister = env - .cw_tokenfactory_issuer - .query_is_blacklister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(!is_blacklister); -} - -#[test] -fn set_blacklister_performed_by_non_contract_owner_should_fail() { - let env = TestEnv::default(); - let non_owner = &env.test_accs[1]; - - let err = env - .cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), true, non_owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_blacklister_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_blacklister(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .set_blacklister(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklisters(None, None) - .unwrap() - .blacklisters, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .set_blacklister(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklisters(None, None) - .unwrap() - .blacklisters, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_blacklister(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -#[test] -fn blacklist_by_blacklister_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - let blacklistee = &env.test_accs[2]; - - env.cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, non_owner) - .unwrap(); - - // should be blacklisted after set true - assert!( - env.cw_tokenfactory_issuer - .query_is_blacklisted(&blacklistee.address()) - .unwrap() - .status - ); - - env.cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), false, non_owner) - .unwrap(); - - // should be unblacklisted after set false - assert!( - !env.cw_tokenfactory_issuer - .query_is_blacklisted(&blacklistee.address()) - .unwrap() - .status - ); -} - -#[test] -fn blacklist_issuer_itself_fails() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_blacklister(&non_owner.address(), true, owner) - .unwrap(); - // TODO check the error message and make sure this is correct - env.cw_tokenfactory_issuer - .blacklist(&env.cw_tokenfactory_issuer.address, true, non_owner) - .unwrap_err(); -} - -#[test] -fn blacklist_by_non_blacklister_should_fail() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let blacklistee = &env.test_accs[2]; - let err = env - .cw_tokenfactory_issuer - .blacklist(&blacklistee.address(), true, owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_blacklist_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .blacklist(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .blacklist(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklistees(None, None) - .unwrap() - .blacklistees, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .blacklist(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_blacklistees(None, None) - .unwrap() - .blacklistees, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_blacklisted(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -// query blacklisters -#[test] -fn query_blacklister_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklisters(start_after, limit) - .unwrap() - .blacklisters - } - }, - ); -} - -#[test] -fn query_blacklister_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklisters(start_after, limit) - .unwrap() - .blacklisters - } - }, - ); -} -// query blacklistees -#[test] -fn query_blacklistee_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .blacklist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklistees(start_after, limit) - .unwrap() - .blacklistees - } - }, - ); -} - -#[test] -fn query_blacklistee_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_blacklister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .blacklist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_blacklistees(start_after, limit) - .unwrap() - .blacklistees - } - }, - ); -} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs index 3140fac06..436a4fe10 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs @@ -15,7 +15,7 @@ fn change_owner_by_owner_should_work() { ); env.cw_tokenfactory_issuer - .change_contract_owner(&new_owner.address(), prev_owner) + .update_contract_owner(&new_owner.address(), prev_owner) .unwrap(); assert_eq!( @@ -26,7 +26,7 @@ fn change_owner_by_owner_should_work() { // previous owner should not be able to execute owner action assert_eq!( env.cw_tokenfactory_issuer - .change_contract_owner(&prev_owner.address(), prev_owner) + .update_contract_owner(&prev_owner.address(), prev_owner) .unwrap_err(), TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) ); @@ -39,7 +39,7 @@ fn change_owner_by_non_owner_should_fail() { let err = env .cw_tokenfactory_issuer - .change_contract_owner(&new_owner.address(), new_owner) + .update_contract_owner(&new_owner.address(), new_owner) .unwrap_err(); assert_eq!( diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs new file mode 100644 index 000000000..12b1943f2 --- /dev/null +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs @@ -0,0 +1,120 @@ +use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; +use osmosis_test_tube::Account; + +use crate::test_env::{ + test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, +}; + +#[test] +fn denylist_by_owner_should_pass() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let denylistee = &env.test_accs[2]; + + env.cw_tokenfactory_issuer + .deny(&denylistee.address(), true, owner) + .unwrap(); + + // should be denylist after set true + assert!( + env.cw_tokenfactory_issuer + .query_is_denied(&denylistee.address()) + .unwrap() + .status + ); + + env.cw_tokenfactory_issuer + .deny(&denylistee.address(), false, owner) + .unwrap(); + + // should be undenylist after set false + assert!( + !env.cw_tokenfactory_issuer + .query_is_denied(&denylistee.address()) + .unwrap() + .status + ); +} + +#[test] +fn denylist_by_non_denylister_should_fail() { + let env = TestEnv::default(); + let non_owner = &env.test_accs[0]; + let denylistee = &env.test_accs[2]; + let err = env + .cw_tokenfactory_issuer + .deny(&denylistee.address(), true, non_owner) + .unwrap_err(); + + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + ); +} + +#[test] +fn set_denylist_to_issuer_itself_fails() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; + + // TODO check the error message and make sure this is correct + env.cw_tokenfactory_issuer + .deny(&env.cw_tokenfactory_issuer.contract_addr, true, owner) + .unwrap_err(); +} + +// query denylist +#[test] +fn query_denylist_within_default_limit() { + test_query_within_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + + env.cw_tokenfactory_issuer + .deny(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_denylist(start_after, limit) + .unwrap() + .denylist + } + }, + ); +} + +#[test] +fn query_denylist_over_default_limit() { + test_query_over_default_limit::( + |(_, addr)| StatusInfo { + address: addr.to_string(), + status: true, + }, + |env| { + move |expected_result| { + let owner = &env.test_accs[0]; + + env.cw_tokenfactory_issuer + .deny(&expected_result.address, true, owner) + .unwrap(); + } + }, + |env| { + move |start_after, limit| { + env.cw_tokenfactory_issuer + .query_denylist(start_after, limit) + .unwrap() + .denylist + } + }, + ); +} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs index c72b9cd46..8ad94d0c0 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs @@ -6,121 +6,11 @@ use crate::test_env::{ }; #[test] -fn set_freezer_performed_by_contract_owner_should_pass() { +fn freeze_by_owener_should_pass() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), true, owner) - .unwrap(); - - let is_freezer = env - .cw_tokenfactory_issuer - .query_is_freezer(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(is_freezer); - env.cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), false, owner) - .unwrap(); - - let is_freezer = env - .cw_tokenfactory_issuer - .query_is_freezer(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(!is_freezer); -} - -#[test] -fn set_freezer_performed_by_non_contract_owner_should_fail() { - let env = TestEnv::default(); - let non_owner = &env.test_accs[1]; - - let err = env - .cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), true, non_owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_freezer_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_freezer(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .set_freezer(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_freezer_allowances(None, None) - .unwrap() - .freezers, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .set_freezer(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_freezer_allowances(None, None) - .unwrap() - .freezers, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_freezer(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -#[test] -fn freeze_by_freezer_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_freezer(&non_owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer.freeze(true, non_owner).unwrap(); + env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); // should be frozen after set true assert!( @@ -130,7 +20,7 @@ fn freeze_by_freezer_should_pass() { .is_frozen ); - env.cw_tokenfactory_issuer.freeze(false, non_owner).unwrap(); + env.cw_tokenfactory_issuer.freeze(false, owner).unwrap(); // should be unfrozen after set false assert!( @@ -144,63 +34,14 @@ fn freeze_by_freezer_should_pass() { #[test] fn freeze_by_non_freezer_should_fail() { let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let err = env.cw_tokenfactory_issuer.freeze(true, owner).unwrap_err(); + let non_owner = &env.test_accs[1]; + let err = env + .cw_tokenfactory_issuer + .freeze(true, non_owner) + .unwrap_err(); assert_eq!( err, TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) ); } - -#[test] -fn query_freezer_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_freezer(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_freezer_allowances(start_after, limit) - .unwrap() - .freezers - } - }, - ); -} - -#[test] -fn query_freezer_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_freezer(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_freezer_allowances(start_after, limit) - .unwrap() - .freezers - } - }, - ); -} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs index 786653642..8ec4493c2 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs @@ -69,11 +69,6 @@ fn instantiate_with_new_token_shoud_set_hook_correctly() { env.cw_tokenfactory_issuer.contract_addr, subdenom ); - // freeze - env.cw_tokenfactory_issuer - .set_freezer(&owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); // bank send should fail diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs index 3fe1f6085..d8fc83720 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs @@ -1,12 +1,12 @@ +mod allowlist; mod beforesend; -mod blacklist; mod burn; mod contract_owner; mod denom_metadata; +mod denylist; mod force_transfer; mod freeze; mod instantiate; mod mint; mod set_before_update_hook; mod tokenfactory_admin; -mod whitelist; diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs index e5a2ef240..eeb7a9795 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs @@ -1,6 +1,6 @@ +use cw_tokenfactory_issuer::msg::QueryMsg; use cw_tokenfactory_issuer::ContractError; -use crate::msg::QueryMsg; use crate::test_env::{TestEnv, TokenfactoryIssuer}; #[test] diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs index 65a3151e8..005cdf7a1 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs @@ -11,7 +11,7 @@ fn transfer_token_factory_admin_by_contract_owner_should_pass() { let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; env.cw_tokenfactory_issuer - .change_tokenfactory_admin(&new_admin.address(), owner) + .update_tokenfactory_admin(&new_admin.address(), owner) .unwrap(); assert_eq!(new_admin.address(), env.token_admin(&denom)); @@ -25,7 +25,7 @@ fn transfer_token_factory_admin_by_non_contract_owner_should_fail() { let err = env .cw_tokenfactory_issuer - .change_tokenfactory_admin(&someone_else.address(), non_owner) + .update_tokenfactory_admin(&someone_else.address(), non_owner) .unwrap_err(); assert_eq!( diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs deleted file mode 100644 index 923c062a9..000000000 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/whitelist.rs +++ /dev/null @@ -1,339 +0,0 @@ -use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; -use osmosis_test_tube::Account; - -use crate::test_env::{ - test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, -}; - -#[test] -fn set_whitelister_performed_by_contract_owner_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - - env.cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), true, owner) - .unwrap(); - - let is_whitelister = env - .cw_tokenfactory_issuer - .query_is_whitelister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(is_whitelister); - - env.cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), false, owner) - .unwrap(); - - let is_whitelister = env - .cw_tokenfactory_issuer - .query_is_whitelister(&env.test_accs[1].address()) - .unwrap() - .status; - - assert!(!is_whitelister); -} - -#[test] -fn set_whitelister_performed_by_non_contract_owner_should_fail() { - let env = TestEnv::default(); - let non_owner = &env.test_accs[1]; - - let err = env - .cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), true, non_owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_whitelister_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_whitelister(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .set_whitelister(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelisters(None, None) - .unwrap() - .whitelisters, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .set_whitelister(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelisters(None, None) - .unwrap() - .whitelisters, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_whitelister(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -#[test] -fn whitelist_by_whitelister_should_pass() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - let whitelistee = &env.test_accs[2]; - - env.cw_tokenfactory_issuer - .set_whitelister(&non_owner.address(), true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), true, non_owner) - .unwrap(); - - // should be whitelisted after set true - assert!( - env.cw_tokenfactory_issuer - .query_is_whitelisted(&whitelistee.address()) - .unwrap() - .status - ); - - env.cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), false, non_owner) - .unwrap(); - - // should be unwhitelisted after set false - assert!( - !env.cw_tokenfactory_issuer - .query_is_whitelisted(&whitelistee.address()) - .unwrap() - .status - ); -} - -#[test] -fn whitelist_by_non_whitelister_should_fail() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - let whitelistee = &env.test_accs[2]; - let err = env - .cw_tokenfactory_issuer - .whitelist(&whitelistee.address(), true, owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) - ); -} - -#[test] -fn set_whitelist_to_false_should_remove_it_from_storage() { - let env = TestEnv::default(); - let owner = &env.test_accs[0]; - - let mut sorted_addrs = env - .test_accs - .iter() - .map(|acc| acc.address()) - .collect::>(); - sorted_addrs.sort(); - - env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .whitelist(&sorted_addrs[0], true, owner) - .unwrap(); - env.cw_tokenfactory_issuer - .whitelist(&sorted_addrs[1], true, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelistees(None, None) - .unwrap() - .whitelistees, - vec![ - StatusInfo { - address: sorted_addrs[0].clone(), - status: true - }, - StatusInfo { - address: sorted_addrs[1].clone(), - status: true - } - ] - ); - - env.cw_tokenfactory_issuer - .whitelist(&sorted_addrs[1], false, owner) - .unwrap(); - - assert_eq!( - env.cw_tokenfactory_issuer - .query_whitelistees(None, None) - .unwrap() - .whitelistees, - vec![StatusInfo { - address: sorted_addrs[0].clone(), - status: true - },] - ); - - assert!( - !env.cw_tokenfactory_issuer - .query_is_whitelisted(&sorted_addrs[1]) - .unwrap() - .status - ); -} - -// query whitelisters -#[test] -fn query_whitelister_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelisters(start_after, limit) - .unwrap() - .whitelisters - } - }, - ); -} - -#[test] -fn query_whitelister_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |allowance| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&allowance.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelisters(start_after, limit) - .unwrap() - .whitelisters - } - }, - ); -} -// query whitelistees -#[test] -fn query_whitelistee_within_default_limit() { - test_query_within_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .whitelist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelistees(start_after, limit) - .unwrap() - .whitelistees - } - }, - ); -} - -#[test] -fn query_whitelistee_over_default_limit() { - test_query_over_default_limit::( - |(_, addr)| StatusInfo { - address: addr.to_string(), - status: true, - }, - |env| { - move |expected_result| { - let owner = &env.test_accs[0]; - env.cw_tokenfactory_issuer - .set_whitelister(&owner.address(), true, owner) - .unwrap(); - - env.cw_tokenfactory_issuer - .whitelist(&expected_result.address, true, owner) - .unwrap(); - } - }, - |env| { - move |start_after, limit| { - env.cw_tokenfactory_issuer - .query_whitelistees(start_after, limit) - .unwrap() - .whitelistees - } - }, - ); -} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs index 5c45b724d..f7476b7f5 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs @@ -4,15 +4,11 @@ use cosmwasm_std::{Coin, Uint128}; -use cw_tokenfactory_issuer::msg::{ - BlacklisteesResponse, BlacklistersResponse, Metadata, MigrateMsg, WhitelisteesResponse, - WhitelistersResponse, -}; +use cw_tokenfactory_issuer::msg::{AllowlistResponse, DenylistResponse, Metadata, MigrateMsg}; use cw_tokenfactory_issuer::{ msg::{ - AllowanceResponse, AllowancesResponse, DenomResponse, ExecuteMsg, - FreezerAllowancesResponse, InstantiateMsg, IsFrozenResponse, OwnerResponse, QueryMsg, - StatusResponse, + AllowanceResponse, AllowancesResponse, DenomResponse, ExecuteMsg, InstantiateMsg, + IsFrozenResponse, OwnerResponse, QueryMsg, StatusResponse, }, ContractError, }; @@ -173,32 +169,33 @@ impl TokenfactoryIssuer { wasm.execute(&self.contract_addr, execute_msg, funds, signer) } - pub fn change_contract_owner( + pub fn update_contract_owner( &self, new_owner: &str, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::ChangeContractOwner { + &ExecuteMsg::UpdateContractOwner { new_owner: new_owner.to_string(), }, &[], signer, ) } - pub fn change_tokenfactory_admin( + pub fn update_tokenfactory_admin( &self, new_admin: &str, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::ChangeTokenFactoryAdmin { + &ExecuteMsg::UpdateTokenFactoryAdmin { new_admin: new_admin.to_string(), }, &[], signer, ) } + pub fn set_denom_metadata( &self, metadata: Metadata, @@ -270,22 +267,6 @@ impl TokenfactoryIssuer { ) } - pub fn set_freezer( - &self, - address: &str, - status: bool, - signer: &SigningAccount, - ) -> RunnerExecuteResult { - self.execute( - &ExecuteMsg::SetFreezer { - address: address.to_string(), - status, - }, - &[], - signer, - ) - } - pub fn set_before_send_hook( &self, signer: &SigningAccount, @@ -293,38 +274,6 @@ impl TokenfactoryIssuer { self.execute(&ExecuteMsg::SetBeforeSendHook {}, &[], signer) } - pub fn set_blacklister( - &self, - address: &str, - status: bool, - signer: &SigningAccount, - ) -> RunnerExecuteResult { - self.execute( - &ExecuteMsg::SetBlacklister { - address: address.to_string(), - status, - }, - &[], - signer, - ) - } - - pub fn set_whitelister( - &self, - address: &str, - status: bool, - signer: &SigningAccount, - ) -> RunnerExecuteResult { - self.execute( - &ExecuteMsg::SetWhitelister { - address: address.to_string(), - status, - }, - &[], - signer, - ) - } - pub fn force_transfer( &self, signer: &SigningAccount, @@ -351,14 +300,14 @@ impl TokenfactoryIssuer { self.execute(&ExecuteMsg::Freeze { status }, &[], signer) } - pub fn blacklist( + pub fn deny( &self, address: &str, status: bool, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::Blacklist { + &ExecuteMsg::Deny { address: address.to_string(), status, }, @@ -367,14 +316,14 @@ impl TokenfactoryIssuer { ) } - pub fn whitelist( + pub fn allow( &self, address: &str, status: bool, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::Whitelist { + &ExecuteMsg::Allow { address: address.to_string(), status, }, @@ -396,76 +345,18 @@ impl TokenfactoryIssuer { self.query(&QueryMsg::Denom {}) } - pub fn query_is_freezer(&self, address: &str) -> Result { - self.query(&QueryMsg::IsFreezer { - address: address.to_string(), - }) - } - - pub fn query_is_blacklister(&self, address: &str) -> Result { - self.query(&QueryMsg::IsBlacklister { - address: address.to_string(), - }) - } - - pub fn query_is_whitelister(&self, address: &str) -> Result { - self.query(&QueryMsg::IsWhitelister { - address: address.to_string(), - }) - } - - pub fn query_freezer_allowances( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::FreezerAllowances { start_after, limit }) - } - - pub fn query_blacklisters( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::Blacklisters { start_after, limit }) - } - - pub fn query_blacklistees( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::Blacklistees { start_after, limit }) - } - - pub fn query_whitelisters( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::Whitelisters { start_after, limit }) - } - - pub fn query_whitelistees( - &self, - start_after: Option, - limit: Option, - ) -> Result { - self.query(&QueryMsg::Whitelistees { start_after, limit }) - } - pub fn query_is_frozen(&self) -> Result { self.query(&QueryMsg::IsFrozen {}) } - pub fn query_is_blacklisted(&self, address: &str) -> Result { - self.query(&QueryMsg::IsBlacklisted { + pub fn query_is_denied(&self, address: &str) -> Result { + self.query(&QueryMsg::IsDenied { address: address.to_string(), }) } - pub fn query_is_whitelisted(&self, address: &str) -> Result { - self.query(&QueryMsg::IsWhitelisted { + pub fn query_is_allowed(&self, address: &str) -> Result { + self.query(&QueryMsg::IsAllowed { address: address.to_string(), }) } @@ -502,6 +393,22 @@ impl TokenfactoryIssuer { self.query(&QueryMsg::BurnAllowances { start_after, limit }) } + pub fn query_allowlist( + &self, + start_after: Option, + limit: Option, + ) -> Result { + self.query(&QueryMsg::Allowlist { start_after, limit }) + } + + pub fn query_denylist( + &self, + start_after: Option, + limit: Option, + ) -> Result { + self.query(&QueryMsg::Denylist { start_after, limit }) + } + pub fn migrate( &self, testdata: &str, 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 f8fcc1a41..452e83091 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -714,7 +714,7 @@ pub fn reply( // Update issuer contract owner to be the DAO msgs.push(WasmMsg::Execute { contract_addr: issuer_addr.clone(), - msg: to_binary(&IssuerExecuteMsg::ChangeContractOwner { + msg: to_binary(&IssuerExecuteMsg::UpdateContractOwner { new_owner: dao.to_string(), })?, funds: vec![], From 3b75780b1f33be6ee72ee909431a2ab9e00e7557 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 1 Sep 2023 23:33:21 -0700 Subject: [PATCH 16/59] Appease clippy gods --- .../dao-voting-cw721-staked/src/contract.rs | 28 +++++++++---------- .../dao-voting-native-staked/src/contract.rs | 2 +- .../dao-voting-native-staked/src/tests.rs | 2 +- .../src/contract.rs | 4 +-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 501d1b1f4..b12682cd8 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -715,20 +715,20 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Uint128::new(supply.count.into()) { - return Err(ContractError::InvalidActiveCount {}); - } + if let Some(ActiveThreshold::AbsoluteCount { count }) = + ACTIVE_THRESHOLD.may_load(deps.storage)? + { + // Load config for nft contract address + let collection_addr = CONFIG.load(deps.storage)?.nft_address; + + // Query the total supply of the NFT contract + let supply: NumTokensResponse = deps + .querier + .query_wasm_smart(collection_addr, &Cw721QueryMsg::NumTokens {})?; + + // Chec the count is not greater than supply + if count > Uint128::new(supply.count.into()) { + return Err(ContractError::InvalidActiveCount {}); } } Ok(Response::new()) diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs index a4e5b44a7..af2966134 100644 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ b/contracts/voting/dao-voting-native-staked/src/contract.rs @@ -291,7 +291,7 @@ pub fn execute_update_active_threshold( } ActiveThreshold::AbsoluteCount { count } => { let denom = CONFIG.load(deps.storage)?.denom; - let supply: Coin = deps.querier.query_supply(denom.to_string())?; + let supply: Coin = deps.querier.query_supply(denom)?; assert_valid_absolute_count_threshold(count, supply.amount)?; } } diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs index 52e6d0023..5a83bc4fa 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-native-staked/src/tests.rs @@ -1314,7 +1314,7 @@ fn test_staking_hooks() { app.update_block(next_block); // Unstake some - let res = unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); + let res = unstake_tokens(&mut app, addr, ADDR1, 75).unwrap(); // Make sure hook is included in response println!("unstake hooks {:?}", res); 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 452e83091..60fbd6642 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -132,7 +132,7 @@ pub fn instantiate( admin: Some(info.sender.to_string()), code_id: msg.token_issuer_code_id, msg: to_binary(&IssuerInstantiateMsg::NewToken { - subdenom: token.subdenom.clone(), + subdenom: token.subdenom, })?, funds: info.funds, label: "cw-tokenfactory-issuer".to_string(), @@ -703,7 +703,7 @@ pub fn reply( msgs.push(WasmMsg::Execute { contract_addr: issuer_addr.clone(), msg: to_binary(&IssuerExecuteMsg::Mint { - to_address: dao.to_string().clone(), + to_address: dao.to_string(), amount: initial_dao_balance, })?, funds: vec![], From ce4e4fcacbf1f316bba3a273cc184b494be38e16 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 12:58:51 -0700 Subject: [PATCH 17/59] Reduce unneeded gas consumption by setting admin directly --- .../voting/dao-voting-cw4/src/contract.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index a32a83d91..11a33ffd4 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -61,12 +61,14 @@ pub fn instantiate( return Err(ContractError::ZeroTotalWeight {}); } - // We need to set ourself as the CW4 admin it is then transferred to the DAO in the reply + // Instantiate group contract, set DAO as admin. + // Voting module contracts are instantiated by the main dao-dao-core + // contract, so the Admin is set to info.sender. let msg = WasmMsg::Instantiate { admin: Some(info.sender.to_string()), code_id: cw4_group_code_id, msg: to_binary(&cw4_group::msg::InstantiateMsg { - admin: Some(env.contract.address.to_string()), + admin: Some(info.sender.to_string()), members: initial_members, })?, funds: vec![], @@ -185,19 +187,8 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Err(ContractError::GroupContractInstantiateError {}), } From df1591bda92e97cbabee059cb884213d95b6e002 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 12:59:55 -0700 Subject: [PATCH 18/59] Fix unneeded reply_always when instantiating new token --- .../voting/dao-voting-token-factory-staked/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 60fbd6642..84c299cf0 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -127,7 +127,7 @@ pub fn instantiate( TokenInfo::New(token) => { // Tnstantiate cw-token-factory-issuer contract // DAO (sender) is set as contract admin - let issuer_instantiate_msg = SubMsg::reply_always( + let issuer_instantiate_msg = SubMsg::reply_on_success( WasmMsg::Instantiate { admin: Some(info.sender.to_string()), code_id: msg.token_issuer_code_id, From bfff54306e70a7a7fefa17234712eedc70f41c40 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 13:00:43 -0700 Subject: [PATCH 19/59] Remove unused function --- .../cw-tokenfactory-issuer/src/helpers.rs | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index 589c168af..c1bf2fc9f 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -2,33 +2,9 @@ use crate::state::{ ALLOWLIST, BEFORE_SEND_HOOK_FEATURES_ENABLED, DENOM, DENYLIST, IS_FROZEN, OWNER, }; use crate::ContractError; -use cosmwasm_std::{Addr, Coin, Deps, Uint128}; +use cosmwasm_std::{Addr, Deps}; use token_bindings::TokenFactoryQuery; -pub fn check_contract_has_funds( - denom: String, - funds: &[Coin], - amount: Uint128, -) -> Result<(), ContractError> { - if let Some(c) = funds.iter().find(|c| c.denom == denom) { - if c.amount < amount { - Err(ContractError::NotEnoughFunds { - denom, - funds: c.amount.u128(), - needed: amount.u128(), - }) - } else { - Ok(()) - } - } else { - Err(ContractError::NotEnoughFunds { - denom, - funds: 0u128, - needed: amount.u128(), - }) - } -} - pub fn check_is_contract_owner( deps: Deps, sender: Addr, From 360297abfa342478984c89234b939428cc93d3d0 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 13:15:21 -0700 Subject: [PATCH 20/59] Improve comments --- .../cw-tokenfactory-issuer/src/execute.rs | 67 +++++++++---------- .../dao-voting-cw721-staked/src/contract.rs | 4 +- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 60d793768..50866b9bd 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -19,48 +19,47 @@ pub fn mint( to_address: String, amount: Uint128, ) -> Result, ContractError> { - // validate that to_address is a valid address + // Validate that to_address is a valid address deps.api.addr_validate(&to_address)?; - // don't allow minting of 0 coins + // Don't allow minting of 0 coins if amount.is_zero() { return Err(ContractError::ZeroAmount {}); } - // decrease minter allowance + // Decrease minter allowance let allowance = MINTER_ALLOWANCES .may_load(deps.storage, &info.sender)? .unwrap_or_else(Uint128::zero); - // if minter allowance goes negative, throw error + // If minter allowance goes negative, throw error let updated_allowance = allowance .checked_sub(amount) .map_err(|_| ContractError::not_enough_mint_allowance(amount, allowance))?; - // if minter allowance goes 0, remove from storage + // If minter allowance goes 0, remove from storage if updated_allowance.is_zero() { MINTER_ALLOWANCES.remove(deps.storage, &info.sender); } else { MINTER_ALLOWANCES.save(deps.storage, &info.sender, &updated_allowance)?; } - // get token denom from contract + // Get token denom from contract let denom = DENOM.load(deps.storage)?; - // create tokenfactory MsgMint which mints coins to the contract address + // Create tokenfactory MsgMint which mints coins to the contract address let mint_tokens_msg = TokenFactoryMsg::mint_contract_tokens( denom.clone(), amount, env.contract.address.into_string(), ); - // send newly minted coins from contract to designated recipient + // Send newly minted coins from contract to designated recipient let send_tokens_msg = BankMsg::Send { to_address: to_address.clone(), amount: coins(amount.u128(), denom), }; - // dispatch msgs Ok(Response::new() .add_message(mint_tokens_msg) .add_message(send_tokens_msg) @@ -76,32 +75,32 @@ pub fn burn( amount: Uint128, address: String, ) -> Result, ContractError> { - // don't allow burning of 0 coins + // Don't allow burning of 0 coins if amount.is_zero() { return Err(ContractError::ZeroAmount {}); } - // decrease burner allowance + // Decrease burner allowance let allowance = BURNER_ALLOWANCES .may_load(deps.storage, &info.sender)? .unwrap_or_else(Uint128::zero); - // if burner allowance goes negative, throw error + // If burner allowance goes negative, throw error let updated_allowance = allowance .checked_sub(amount) .map_err(|_| ContractError::not_enough_burn_allowance(amount, allowance))?; - // if burner allowance goes 0, remove from storage + // If burner allowance goes 0, remove from storage if updated_allowance.is_zero() { BURNER_ALLOWANCES.remove(deps.storage, &info.sender); } else { BURNER_ALLOWANCES.save(deps.storage, &info.sender, &updated_allowance)?; } - // get token denom from contract config + // Get token denom from contract config let denom = DENOM.load(deps.storage)?; - // create tokenfactory MsgBurn which burns coins from the contract address + // Create tokenfactory MsgBurn which burns coins from the contract address // NOTE: this requires the contract to own the tokens already let burn_from_address = deps.api.addr_validate(&address)?; let burn_tokens_msg: cosmwasm_std::CosmosMsg = MsgBurn { @@ -111,7 +110,6 @@ pub fn burn( } .into(); - // dispatch msg Ok(Response::new() .add_message(burn_tokens_msg) .add_attribute("action", "burn") @@ -130,10 +128,10 @@ pub fn update_contract_owner( // TODO make sure it's possible to renounce ownership all together // TODO add test for NO OWNER - // validate that new owner is a valid address + // Validate that new owner is a valid address let new_owner_addr = deps.api.addr_validate(&new_owner)?; - // update the contract owner in the contract config + // Update the contract owner in the contract config OWNER.save(deps.storage, &new_owner_addr)?; Ok(Response::new() @@ -151,18 +149,17 @@ pub fn update_tokenfactory_admin( // TODO make sure it's possible to renounce ownership all together // TODO add test for NO ADMIN - // validate that the new admin is a valid address + // Validate that the new admin is a valid address let new_admin_addr = deps.api.addr_validate(&new_admin)?; - // construct tokenfactory change admin msg - let change_admin_msg = TokenFactoryMsg::ChangeAdmin { + // Construct tokenfactory change admin msg + let update_admin_msg = TokenFactoryMsg::ChangeAdmin { denom: DENOM.load(deps.storage)?, new_admin_address: new_admin_addr.into(), }; - // dispatch change admin msg Ok(Response::new() - .add_message(change_admin_msg) + .add_message(update_admin_msg) .add_attribute("action", "update_tokenfactory_admin") .add_attribute("new_admin", new_admin)) } @@ -173,7 +170,7 @@ pub fn set_denom_metadata( info: MessageInfo, metadata: Metadata, ) -> Result, ContractError> { - // only allow current contract owner to set denom metadata + // Only allow current contract owner to set denom metadata check_is_contract_owner(deps.as_ref(), info.sender)?; Ok(Response::new() @@ -200,9 +197,9 @@ pub fn set_before_send_hook( // Load the Token Factory denom let denom = DENOM.load(deps.storage)?; - // SetBeforeSendHook to this contract - // this will trigger sudo endpoint before any bank send - // which makes denylisting / freezing possible + // SetBeforeSendHook to this contract. + // This will trigger sudo endpoint before any bank send, + // which makes denylisting / freezing possible. let msg_set_beforesend_hook: CosmosMsg = MsgSetBeforeSendHook { sender: env.contract.address.to_string(), denom, @@ -227,11 +224,10 @@ pub fn set_burner( // Only allow current contract owner to set burner allowance check_is_contract_owner(deps.as_ref(), info.sender)?; - // validate that burner is a valid address + // Validate that burner is a valid address let address = deps.api.addr_validate(&address)?; - // update allowance of burner - // remove key from state if set to 0 + // Update allowance of burner, remove key from state if set to 0 if allowance.is_zero() { BURNER_ALLOWANCES.remove(deps.storage, &address); } else { @@ -253,11 +249,10 @@ pub fn set_minter( // Only allow current contract owner to set minter allowance check_is_contract_owner(deps.as_ref(), info.sender)?; - // validate that minter is a valid address + // Validate that minter is a valid address let address = deps.api.addr_validate(&address)?; - // update allowance of minter - // remove key from state if set to 0 + // Update allowance of minter, remove key from state if set to 0 if allowance.is_zero() { MINTER_ALLOWANCES.remove(deps.storage, &address); } else { @@ -308,8 +303,7 @@ pub fn deny( return Err(ContractError::CannotDenylistSelf {}); } - // update denylist status - // validate that denylisteed is a valid address + // Update denylist status and validate that denylistee is a valid address // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { @@ -337,8 +331,7 @@ pub fn allow( let address = deps.api.addr_validate(&address)?; - // update denylist status - // validate that denylisteed is a valid address + // Update allowlist status and validate that allowlistee is a valid address // NOTE: Does not check if new status is same as old status // but if status is false, remove if exist to reduce space usage if status { diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index b12682cd8..0744569a3 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -686,8 +686,8 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Date: Sat, 2 Sep 2023 13:26:14 -0700 Subject: [PATCH 21/59] =?UTF-8?q?Use=20=E2=80=9CMigrate=20only=20if=20newe?= =?UTF-8?q?r=E2=80=9D=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cw-tokenfactory-issuer/src/contract.rs | 14 ++++++++++---- contracts/voting/dao-voting-cw4/src/contract.rs | 14 ++++++++++---- contracts/voting/dao-voting-cw4/src/tests.rs | 2 +- .../voting/dao-voting-cw721-staked/src/contract.rs | 14 ++++++++++---- .../voting/dao-voting-cw721-staked/src/msg.rs | 1 + .../dao-voting-cw721-staked/src/testing/tests.rs | 2 +- .../dao-voting-native-staked/src/contract.rs | 14 ++++++++++---- .../voting/dao-voting-native-staked/src/tests.rs | 2 +- .../src/contract.rs | 14 ++++++++++---- 9 files changed, 54 insertions(+), 23 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 6a915df65..b5e024a37 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, SubMsgResult, }; use cosmwasm_std::{CosmosMsg, Reply}; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ MsgCreateDenom, MsgCreateDenomResponse, MsgSetBeforeSendHook, }; @@ -167,9 +167,15 @@ pub fn migrate( _env: Env, _msg: MigrateMsg, ) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::new().add_attribute("action", "migrate")) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version < CONTRACT_VERSION.to_string() { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index 11a33ffd4..246889713 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg, Uint128, WasmMsg, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw4::{MemberListResponse, MemberResponse, TotalWeightResponse}; use cw_utils::parse_reply_instantiate_data; @@ -170,9 +170,15 @@ pub fn query_info(deps: Deps) -> StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version < CONTRACT_VERSION.to_string() { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/voting/dao-voting-cw4/src/tests.rs b/contracts/voting/dao-voting-cw4/src/tests.rs index 386d8f0e6..69ca443ef 100644 --- a/contracts/voting/dao-voting-cw4/src/tests.rs +++ b/contracts/voting/dao-voting-cw4/src/tests.rs @@ -735,7 +735,7 @@ fn test_zero_voting_power() { #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 0744569a3..8f351d62b 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -12,7 +12,7 @@ use cosmwasm_std::{ from_binary, to_binary, Addr, Binary, CosmosMsg, Decimal, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, StdError, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw721::{Cw721QueryMsg, Cw721ReceiveMsg, NumTokensResponse}; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; @@ -649,9 +649,15 @@ pub fn query_staked_nfts( #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version < CONTRACT_VERSION.to_string() { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 3ce904c8a..643247817 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -28,6 +28,7 @@ pub enum NftContract { #[cw_serde] pub struct InstantiateMsg { + /// TODO use cw_ownable /// May change unstaking duration and add hooks. pub owner: Option, /// Address of the cw721 NFT contract that may be staked. diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs index 1783f2e6c..d9b98ce04 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs @@ -1237,7 +1237,7 @@ fn test_instantiate_with_new_sg721_collection_abs_count_validation() { #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs index af2966134..b264efeca 100644 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ b/contracts/voting/dao-voting-native-staked/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, Uint256, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_controllers::ClaimsResponse; use cw_utils::{must_pay, Duration}; use dao_interface::voting::{ @@ -513,7 +513,13 @@ pub fn query_hooks(deps: Deps) -> StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version < CONTRACT_VERSION.to_string() { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs index 5a83bc4fa..6d943a758 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-native-staked/src/tests.rs @@ -1323,7 +1323,7 @@ fn test_staking_hooks() { #[test] fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); 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 84c299cf0..a8d8e8269 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{ coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_controllers::ClaimsResponse; use cw_storage_plus::Bound; use cw_tokenfactory_issuer::msg::{ @@ -566,9 +566,15 @@ pub fn migrate( _env: Env, _msg: MigrateMsg, ) -> Result, ContractError> { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version < CONTRACT_VERSION.to_string() { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] From b4256b1fa071449506f988087458a10bb4a97767 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 14:05:13 -0700 Subject: [PATCH 22/59] Clean up and fix issuer test-tube tests --- .../tests/cases/allowlist.rs | 5 ++- .../tests/cases/beforesend.rs | 30 ++++++++--------- .../tests/cases/burn.rs | 32 +++++++++---------- .../tests/cases/contract_owner.rs | 2 +- .../tests/cases/denom_metadata.rs | 14 ++++---- .../tests/cases/denylist.rs | 17 ++++++---- .../tests/cases/freeze.rs | 11 +++---- .../tests/cases/instantiate.rs | 6 ++-- .../tests/cases/mint.rs | 18 +++++------ 9 files changed, 67 insertions(+), 68 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs index fe1c59ae4..72e34d604 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs @@ -15,7 +15,7 @@ fn allowlist_by_owner_should_pass() { .allow(&allowlistee.address(), true, owner) .unwrap(); - // should be allowlist after set true + // Should be allowlist after set true assert!( env.cw_tokenfactory_issuer .query_is_allowed(&allowlistee.address()) @@ -27,7 +27,7 @@ fn allowlist_by_owner_should_pass() { .allow(&allowlistee.address(), false, owner) .unwrap(); - // should be unallowlist after set false + // Should be unallowlist after set false assert!( !env.cw_tokenfactory_issuer .query_is_allowed(&allowlistee.address()) @@ -52,7 +52,6 @@ fn allowlist_by_non_owern_should_fail() { ); } -// query allowlist #[test] fn query_allowlist_within_default_limit() { test_query_within_default_limit::( diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs index c7ecf2c33..76cdff00e 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs @@ -9,7 +9,7 @@ fn before_send_should_not_block_anything_by_default() { let owner = &env.test_accs[0]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // mint to self + // Mint to self env.cw_tokenfactory_issuer .set_minter(&owner.address(), 10000, owner) .unwrap(); @@ -17,7 +17,7 @@ fn before_send_should_not_block_anything_by_default() { .mint(&owner.address(), 10000, owner) .unwrap(); - // bank send should pass + // Bank send should pass env.send_tokens(env.test_accs[1].address(), coins(10000, denom), owner) .unwrap(); } @@ -28,10 +28,10 @@ fn before_send_should_block_on_frozen() { let owner = &env.test_accs[0]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // freeze + // Freeze env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - // bank send should fail + // Bank send should fail let err = env .send_tokens( env.test_accs[1].address(), @@ -51,10 +51,10 @@ fn allowlisted_addresses_can_transfer_when_token_frozen() { let allowlistee = &env.test_accs[1]; let other = &env.test_accs[2]; - // freeze + // Freeze env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - // bank send should fail + // Bank send should fail let err = env .send_tokens(allowlistee.address(), coins(10000, denom.clone()), owner) .unwrap_err(); @@ -65,7 +65,7 @@ fn allowlisted_addresses_can_transfer_when_token_frozen() { .allow(&allowlistee.address(), true, owner) .unwrap(); - // bank send should pass + // Bank send should pass env.send_tokens(other.address(), coins(10000, denom.clone()), allowlistee) .unwrap_err(); // Non allowlist address can't transfer, bank send should fail @@ -82,7 +82,7 @@ fn before_send_should_block_sending_from_denylist_address() { let denylistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // mint to denylistee + // Mint to denylistee env.cw_tokenfactory_issuer .set_minter(&owner.address(), 20000, owner) .unwrap(); @@ -90,12 +90,12 @@ fn before_send_should_block_sending_from_denylist_address() { .mint(&denylistee.address(), 20000, owner) .unwrap(); - // denylist + // Denylist env.cw_tokenfactory_issuer .deny(&denylistee.address(), true, owner) .unwrap(); - // bank send should fail + // Bank send should fail let err = env .send_tokens( env.test_accs[2].address(), @@ -105,7 +105,7 @@ fn before_send_should_block_sending_from_denylist_address() { .unwrap_err(); let denylistee_addr = denylistee.address(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denylist: execute wasm contract failed") }); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denied transfer abilities: execute wasm contract failed") }); } #[test] @@ -115,7 +115,7 @@ fn before_send_should_block_sending_to_denylist_address() { let denylistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; - // mint to self + // Mint to self env.cw_tokenfactory_issuer .set_minter(&owner.address(), 10000, owner) .unwrap(); @@ -123,16 +123,16 @@ fn before_send_should_block_sending_to_denylist_address() { .mint(&owner.address(), 10000, owner) .unwrap(); - // denylist + // Denylist env.cw_tokenfactory_issuer .deny(&denylistee.address(), true, owner) .unwrap(); - // bank send should fail + // Bank send should fail let err = env .send_tokens(denylistee.address(), coins(10000, denom.clone()), owner) .unwrap_err(); let denylistee_addr = denylistee.address(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denylist: execute wasm contract failed") }); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The address '{denylistee_addr}' is denied transfer abilities: execute wasm contract failed") }); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs index 66977a1e9..bd6dfc6df 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs @@ -52,18 +52,18 @@ fn set_allowance_to_0_should_remove_it_from_storage() { let owner = &env.test_accs[0]; let burner = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_burner(&burner.address(), allowance, owner) .unwrap(); - // set allowance to 0 + // Set allowance to 0 env.cw_tokenfactory_issuer .set_burner(&burner.address(), 0, owner) .unwrap(); - // check if key for the minter address is removed + // Check if key for the minter address is removed assert_eq!( env.cw_tokenfactory_issuer .query_burn_allowances(None, None) @@ -79,13 +79,13 @@ fn used_up_allowance_should_be_removed_from_storage() { let owner = &env.test_accs[0]; let burner = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_minter(&burner.address(), allowance, owner) .unwrap(); - // mint the whole allowance to be burned the same amount later + // Mint the whole allowance to be burned the same amount later env.cw_tokenfactory_issuer .mint(&burner.address(), allowance, burner) .unwrap(); @@ -94,12 +94,12 @@ fn used_up_allowance_should_be_removed_from_storage() { .set_burner(&burner.address(), allowance, owner) .unwrap(); - // use all allowance + // Use all allowance env.cw_tokenfactory_issuer .burn(&burner.address(), allowance, burner) .unwrap(); - // check if key for the burner address is removed + // Check if key for the burner address is removed assert_eq!( env.cw_tokenfactory_issuer .query_burn_allowances(None, None) @@ -127,7 +127,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow let burner = &env.test_accs[1]; let burn_to = &env.test_accs[2]; - // mint + // Mint env.cw_tokenfactory_issuer .set_minter(&burner.address(), allowance, owner) .unwrap(); @@ -136,7 +136,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow .mint(&burn_to.address(), burn_amount, burner) .unwrap(); - // burn + // Burn env.cw_tokenfactory_issuer .set_burner(&burner.address(), allowance, owner) .unwrap(); @@ -145,7 +145,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow .burn(&burn_to.address(), burn_amount, burner) .unwrap(); - // check if allowance is deducted properly + // Check if allowance is deducted properly let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) @@ -155,7 +155,7 @@ fn burn_whole_balance_but_less_than_or_eq_allowance_should_work_and_deduct_allow assert_eq!(resulted_allowance, allowance - burn_amount); - // check if resulted balance is 0 + // Check if resulted balance is 0 let amount = env .bank() .query_balance(&QueryBalanceRequest { @@ -185,7 +185,7 @@ fn burn_more_than_balance_should_fail_and_not_deduct_allowance() { let allowance = burn_amount; - // mint + // Mint env.cw_tokenfactory_issuer .set_minter(&burner.address(), balance, owner) .unwrap(); @@ -194,7 +194,7 @@ fn burn_more_than_balance_should_fail_and_not_deduct_allowance() { .mint(&burn_from.address(), balance, burner) .unwrap(); - // burn + // Burn env.cw_tokenfactory_issuer .set_burner(&burner.address(), allowance, owner) .unwrap(); @@ -211,7 +211,7 @@ fn burn_more_than_balance_should_fail_and_not_deduct_allowance() { } ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) @@ -251,7 +251,7 @@ fn burn_over_allowance_should_fail_and_not_deduct_allowance() { )) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) @@ -288,7 +288,7 @@ fn burn_0_should_fail_and_not_deduct_allowance() { TokenfactoryIssuer::execute_error(ContractError::ZeroAmount {}) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_burn_allowance(&burner.address()) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs index 436a4fe10..5c276be98 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs @@ -23,7 +23,7 @@ fn change_owner_by_owner_should_work() { env.cw_tokenfactory_issuer.query_owner().unwrap().address ); - // previous owner should not be able to execute owner action + // Previous owner should not be able to execute owner action assert_eq!( env.cw_tokenfactory_issuer .update_contract_owner(&prev_owner.address(), prev_owner) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs index 6076d200b..706c61a72 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs @@ -9,7 +9,7 @@ use crate::test_env::{TestEnv, TokenfactoryIssuer}; fn set_denom_metadata_by_contract_owner_should_work() { let subdenom = "usthb".to_string(); - // set no metadata + // Set no metadata let env = TestEnv::new(InstantiateMsg::NewToken { subdenom }, 0).unwrap(); let owner = &env.test_accs[0]; @@ -42,7 +42,7 @@ fn set_denom_metadata_by_contract_owner_should_work() { fn set_denom_metadata_by_contract_non_owner_should_fail() { let subdenom = "usthb".to_string(); - // set no metadata + // Set no metadata let env = TestEnv::new(InstantiateMsg::NewToken { subdenom }, 0).unwrap(); let non_owner = &env.test_accs[1]; @@ -67,7 +67,7 @@ fn set_denom_metadata_by_contract_non_owner_should_fail() { symbol: "STHB".to_string(), }; - // set denom metadata + // Set denom metadata let err = env .cw_tokenfactory_issuer .set_denom_metadata(metadata, non_owner) @@ -83,7 +83,7 @@ fn set_denom_metadata_by_contract_non_owner_should_fail() { fn set_denom_metadata_with_base_denom_unit_should_overides_default_base_denom_unit() { let subdenom = "usthb".to_string(); - // set no metadata + // Set no metadata let env = TestEnv::new(InstantiateMsg::NewToken { subdenom }, 0).unwrap(); let owner = &env.test_accs[0]; @@ -108,13 +108,13 @@ fn set_denom_metadata_with_base_denom_unit_should_overides_default_base_denom_un symbol: "STHB".to_string(), }; - // set denom metadata + // Set denom metadata env.cw_tokenfactory_issuer .set_denom_metadata(metadata.clone(), owner) .unwrap(); - // should update metadata - + // // TODO fix up this metadata test + // Should update metadata // assert_eq!( // env.bank() // .query_denom_metadata(&QueryDenomMetadataRequest { diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs index 12b1943f2..7f28f54e1 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs @@ -15,7 +15,7 @@ fn denylist_by_owner_should_pass() { .deny(&denylistee.address(), true, owner) .unwrap(); - // should be denylist after set true + // Should be denylist after set true assert!( env.cw_tokenfactory_issuer .query_is_denied(&denylistee.address()) @@ -27,7 +27,7 @@ fn denylist_by_owner_should_pass() { .deny(&denylistee.address(), false, owner) .unwrap(); - // should be undenylist after set false + // Should be undenylist after set false assert!( !env.cw_tokenfactory_issuer .query_is_denied(&denylistee.address()) @@ -39,7 +39,7 @@ fn denylist_by_owner_should_pass() { #[test] fn denylist_by_non_denylister_should_fail() { let env = TestEnv::default(); - let non_owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; let denylistee = &env.test_accs[2]; let err = env .cw_tokenfactory_issuer @@ -56,15 +56,18 @@ fn denylist_by_non_denylister_should_fail() { fn set_denylist_to_issuer_itself_fails() { let env = TestEnv::default(); let owner = &env.test_accs[0]; - let non_owner = &env.test_accs[1]; - // TODO check the error message and make sure this is correct - env.cw_tokenfactory_issuer + let err = env + .cw_tokenfactory_issuer .deny(&env.cw_tokenfactory_issuer.contract_addr, true, owner) .unwrap_err(); + + assert_eq!( + err, + TokenfactoryIssuer::execute_error(ContractError::CannotDenylistSelf {}) + ); } -// query denylist #[test] fn query_denylist_within_default_limit() { test_query_within_default_limit::( diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs index 8ad94d0c0..a25a94a25 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs @@ -1,9 +1,6 @@ -use cw_tokenfactory_issuer::{msg::StatusInfo, ContractError}; -use osmosis_test_tube::Account; +use cw_tokenfactory_issuer::ContractError; -use crate::test_env::{ - test_query_over_default_limit, test_query_within_default_limit, TestEnv, TokenfactoryIssuer, -}; +use crate::test_env::{TestEnv, TokenfactoryIssuer}; #[test] fn freeze_by_owener_should_pass() { @@ -12,7 +9,7 @@ fn freeze_by_owener_should_pass() { env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - // should be frozen after set true + // Should be frozen after set true assert!( env.cw_tokenfactory_issuer .query_is_frozen() @@ -22,7 +19,7 @@ fn freeze_by_owener_should_pass() { env.cw_tokenfactory_issuer.freeze(false, owner).unwrap(); - // should be unfrozen after set false + // Should be unfrozen after set false assert!( !env.cw_tokenfactory_issuer .query_is_frozen() diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs index 8ec4493c2..0742b2431 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs @@ -17,7 +17,7 @@ fn instantiate_with_new_token_shoud_set_initial_state_correctly() { let owner = &env.test_accs[0]; - // check tokenfactory's token admin + // Check tokenfactory's token admin let denom = format!( "factory/{}/{}", env.cw_tokenfactory_issuer.contract_addr, subdenom @@ -29,7 +29,7 @@ fn instantiate_with_new_token_shoud_set_initial_state_correctly() { "token admin must be tokenfactory-issuer contract" ); - // check initial contract state + // Check initial contract state let contract_denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; assert_eq!( denom, contract_denom, @@ -71,7 +71,7 @@ fn instantiate_with_new_token_shoud_set_hook_correctly() { env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - // bank send should fail + // Bank send should fail let err = env .send_tokens( env.test_accs[1].address(), diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs index cc632bc38..33823eeca 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs @@ -50,18 +50,18 @@ fn set_allowance_to_0_should_remove_it_from_storage() { let owner = &env.test_accs[0]; let minter = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_minter(&minter.address(), allowance, owner) .unwrap(); - // set allowance to 0 + // Set allowance to 0 env.cw_tokenfactory_issuer .set_minter(&minter.address(), 0, owner) .unwrap(); - // check if key for the minter address is removed + // Check if key for the minter address is removed assert_eq!( env.cw_tokenfactory_issuer .query_mint_allowances(None, None) @@ -77,18 +77,18 @@ fn used_up_allowance_should_be_removed_from_storage() { let owner = &env.test_accs[0]; let minter = &env.test_accs[1]; - // set allowance to some value + // Set allowance to some value let allowance = 1000000; env.cw_tokenfactory_issuer .set_minter(&minter.address(), allowance, owner) .unwrap(); - // use all allowance + // Use all allowance env.cw_tokenfactory_issuer .mint(&minter.address(), allowance, minter) .unwrap(); - // check if key for the minter address is removed + // Check if key for the minter address is removed assert_eq!( env.cw_tokenfactory_issuer .query_mint_allowances(None, None) @@ -124,7 +124,7 @@ fn mint_less_than_or_eq_allowance_should_pass_and_deduct_allowance() { .mint(&mint_to.address(), mint_amount, minter) .unwrap(); - // check if allowance is deducted properly + // Check if allowance is deducted properly let resulted_allowance = env .cw_tokenfactory_issuer .query_mint_allowance(&minter.address()) @@ -177,7 +177,7 @@ fn mint_over_allowance_should_fail_and_not_deduct_allowance() { )) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_mint_allowance(&minter.address()) @@ -214,7 +214,7 @@ fn mint_0_should_fail_and_not_deduct_allowance() { TokenfactoryIssuer::execute_error(ContractError::ZeroAmount {}) ); - // check if allowance stays the same + // Check if allowance stays the same let resulted_allowance = env .cw_tokenfactory_issuer .query_mint_allowance(&minter.address()) From 2781cec587a53e6a0784942ca438cf64b7ff9f77 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 14:56:39 -0700 Subject: [PATCH 23/59] Combine all dao-related hooks into a single package --- Cargo.lock | 218 ++++++++---------- Cargo.toml | 5 +- .../Cargo.toml | 2 +- .../dao-pre-propose-approver/Cargo.toml | 2 +- .../dao-pre-propose-multiple/Cargo.toml | 2 +- .../dao-pre-propose-single/Cargo.toml | 2 +- .../proposal/dao-proposal-multiple/Cargo.toml | 3 +- .../dao-proposal-multiple/src/contract.rs | 4 +- .../proposal/dao-proposal-single/Cargo.toml | 3 +- .../dao-proposal-single/src/contract.rs | 4 +- packages/cw-hooks/README.md | 2 +- .../Cargo.toml | 6 +- packages/dao-hooks/README.md | 20 ++ packages/dao-hooks/src/lib.rs | 7 + .../src/lib.rs => dao-hooks/src/proposal.rs} | 2 - packages/dao-hooks/src/stake.rs | 1 + .../src/lib.rs => dao-hooks/src/vote.rs} | 2 - packages/dao-pre-propose-base/Cargo.toml | 2 +- packages/dao-proposal-hooks/README.md | 10 - packages/dao-vote-hooks/Cargo.toml | 14 -- packages/dao-vote-hooks/README.md | 7 - scripts/publish.sh | 6 +- .../dao-proposal-hook-counter/Cargo.toml | 3 +- .../dao-proposal-hook-counter/src/contract.rs | 3 +- .../dao-proposal-hook-counter/src/msg.rs | 3 +- 25 files changed, 148 insertions(+), 185 deletions(-) rename packages/{dao-proposal-hooks => dao-hooks}/Cargo.toml (62%) create mode 100644 packages/dao-hooks/README.md create mode 100644 packages/dao-hooks/src/lib.rs rename packages/{dao-proposal-hooks/src/lib.rs => dao-hooks/src/proposal.rs} (97%) create mode 100644 packages/dao-hooks/src/stake.rs rename packages/{dao-vote-hooks/src/lib.rs => dao-hooks/src/vote.rs} (94%) delete mode 100644 packages/dao-proposal-hooks/README.md delete mode 100644 packages/dao-vote-hooks/Cargo.toml delete mode 100644 packages/dao-vote-hooks/README.md diff --git a/Cargo.lock b/Cargo.lock index 29caf23d6..02898f673 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -30,19 +30,13 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "anyhow" version = "1.0.75" @@ -152,9 +146,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -179,9 +173,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "base64ct" @@ -345,11 +339,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" dependencies = [ - "android-tzdata", "num-traits", ] @@ -540,9 +533,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dd316b3061747d6f57c1c4a131a5ba2f9446601a9276d05a4d25ab2ce0a7e0" +checksum = "871ce1d5a4b00ed1741f84b377eec19fadd81a904a227bc1e268d76539d26f5e" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -553,18 +546,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b14230c6942a301afb96f601af97ae09966601bd1007067a2c7fe8ffcfe303" +checksum = "7ce8b44b45a7c8c6d6f770cd0a51458c2445c7c15b6115e1d215fa35c77b305c" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027bdd5941b7d4b45bd773b6d88818dcc043e8db68916bfbd5caf971024dbea" +checksum = "99222fa0401ee36389550d8a065700380877a2299c3043d24c38d705708c9d9d" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -575,9 +568,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e069f6e65a9a1f55f8d7423703bed35e9311d029d91b357b17a07010d95cd7" +checksum = "4b74eaf9e585ef8e5e3486b240b13ee593cb0f658b5879696937d8c22243d4fb" dependencies = [ "proc-macro2", "quote", @@ -586,9 +579,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27a06f0f6c35b178563c6b1044245b3f750c4a66d9f6d2b942a6b29ad77d3ae" +checksum = "da78abcf059181e8cb01e95e5003cf64fe95dde6c72b3fe37e5cabc75cdba32a" dependencies = [ "base64 0.13.1", "bnum", @@ -606,9 +599,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81854e8f4cb8d6d0ff956de34af56ed70c5a09cb61431dbc854982d10f8886b7" +checksum = "b52be0d56b78f502f3acb75e40908a0d04d9f265b6beb0f86b36ec2ece048748" dependencies = [ "cosmwasm-std", "serde", @@ -1041,7 +1034,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "token-bindings 0.11.0", + "token-bindings 0.11.0 (git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954)", ] [[package]] @@ -1632,6 +1625,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dao-hooks" +version = "2.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-hooks", + "dao-voting 2.2.0", +] + [[package]] name = "dao-interface" version = "2.2.0" @@ -1695,9 +1698,9 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", @@ -1721,10 +1724,10 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-approval-single", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", @@ -1744,8 +1747,8 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw2 1.1.0", + "dao-hooks", "dao-interface", - "dao-proposal-hooks", "dao-voting 2.2.0", "serde", "thiserror", @@ -1765,9 +1768,9 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-multiple", "dao-testing", "dao-voting 2.2.0", @@ -1790,9 +1793,9 @@ dependencies = [ "cw20-base 1.1.0", "cw4-group 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", "dao-pre-propose-base", - "dao-proposal-hooks", "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", @@ -1836,25 +1839,14 @@ dependencies = [ "cw20 1.1.0", "cw20-base 1.1.0", "dao-dao-core", + "dao-hooks", "dao-interface", - "dao-proposal-hooks", "dao-proposal-single", - "dao-vote-hooks", "dao-voting 2.2.0", "dao-voting-cw20-balance", "thiserror", ] -[[package]] -name = "dao-proposal-hooks" -version = "2.2.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-hooks", - "dao-voting 2.2.0", -] - [[package]] name = "dao-proposal-multiple" version = "2.2.0" @@ -1876,12 +1868,11 @@ dependencies = [ "cw4-group 1.1.0", "cw721-base 0.18.0", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-pre-propose-base", "dao-pre-propose-multiple", - "dao-proposal-hooks", "dao-testing", - "dao-vote-hooks", "dao-voting 0.1.0", "dao-voting 2.2.0", "dao-voting-cw20-balance", @@ -1918,12 +1909,11 @@ dependencies = [ "cw721-base 0.18.0", "dao-dao-core", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-pre-propose-base", "dao-pre-propose-single", - "dao-proposal-hooks", "dao-testing", - "dao-vote-hooks", "dao-voting 0.1.0", "dao-voting 2.2.0", "dao-voting-cw20-balance", @@ -1991,17 +1981,7 @@ dependencies = [ "serde", "serde_json", "stake-cw20", - "token-bindings 0.10.3", -] - -[[package]] -name = "dao-vote-hooks" -version = "2.2.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-hooks", - "dao-voting 2.2.0", + "token-bindings 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2183,7 +2163,7 @@ dependencies = [ "osmosis-test-tube", "serde", "thiserror", - "token-bindings 0.11.0", + "token-bindings 0.11.0 (git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954)", "token-bindings-test", ] @@ -2348,18 +2328,18 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" dependencies = [ "serde", ] [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -2541,9 +2521,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "glob" @@ -2564,9 +2544,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -2598,12 +2578,11 @@ checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "headers" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", + "base64 0.21.3", "bytes", "headers-core", "http", @@ -2977,9 +2956,9 @@ checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "mime" @@ -3064,9 +3043,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "memchr", ] @@ -3241,19 +3220,20 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" dependencies = [ "pest", "pest_generator", @@ -3261,9 +3241,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" dependencies = [ "pest", "pest_meta", @@ -3274,9 +3254,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" dependencies = [ "once_cell", "pest", @@ -3305,9 +3285,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -3454,9 +3434,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", @@ -3466,9 +3446,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", @@ -3477,9 +3457,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rfc6979" @@ -3562,9 +3542,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" dependencies = [ "bitflags 2.4.0", "errno", @@ -3630,9 +3610,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" dependencies = [ "dyn-clone", "schemars_derive", @@ -3642,9 +3622,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" dependencies = [ "proc-macro2", "quote", @@ -3707,9 +3687,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.185" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -3743,9 +3723,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.185" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -3814,9 +3794,9 @@ dependencies = [ [[package]] name = "sg-std" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171f97d3032b7d713dd16decaed06479e7ce5585147f387860ad2fb3f7b9ed94" +checksum = "4db53aebc2b4f981dc20a51213544adde8beaace6880345627f4babe2e1bc3ab" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -3926,9 +3906,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -4341,18 +4321,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", @@ -4393,9 +4373,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "token-bindings" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752a7997ebaa191cf3d8436261e449732e65268e552e8bea6133c3b21b48fe36" +checksum = "9be1c893c90d2993320d9722516ece705460f464616313a62edadb9e71df4502" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4427,7 +4407,7 @@ dependencies = [ "schemars", "serde", "thiserror", - "token-bindings 0.11.0", + "token-bindings 0.11.0 (git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954)", ] [[package]] @@ -4553,7 +4533,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.2", + "base64 0.21.3", "bytes", "futures-core", "futures-util", @@ -4700,9 +4680,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", diff --git a/Cargo.toml b/Cargo.toml index 2d5bff497..dab3d3b44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,20 +94,19 @@ cw721-controllers = { path = "./packages/cw721-controllers", version = "2.2.0" } cw721-roles = { path = "./contracts/external/cw721-roles", version = "*" } dao-cw721-extensions = { path = "./packages/dao-cw721-extensions", version = "*" } dao-dao-core = { path = "./contracts/dao-dao-core", version = "2.2.0" } -dao-interface = { path = "./packages/dao-interface", version = "2.2.0" } dao-dao-macros = { path = "./packages/dao-dao-macros", version = "2.2.0" } +dao-hooks = { path = "./packages/dao-hooks", version = "*" } +dao-interface = { path = "./packages/dao-interface", version = "2.2.0" } dao-pre-propose-approval-single = { path = "./contracts/pre-propose/dao-pre-propose-approval-single", version = "2.2.0" } dao-pre-propose-approver = { path = "./contracts/pre-propose/dao-pre-propose-approver", version = "2.2.0" } dao-pre-propose-base = { path = "./packages/dao-pre-propose-base", version = "2.2.0" } dao-pre-propose-multiple = { path = "./contracts/pre-propose/dao-pre-propose-multiple", version = "2.2.0" } dao-pre-propose-single = { path = "./contracts/pre-propose/dao-pre-propose-single", version = "2.2.0" } dao-proposal-condorcet = { path = "./contracts/proposal/dao-proposal-condorcet", version = "2.2.0" } -dao-proposal-hooks = { path = "./packages/dao-proposal-hooks", version = "2.2.0" } dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", version = "2.2.0" } dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.2.0" } dao-proposal-sudo = { path = "./test-contracts/dao-proposal-sudo", version = "2.2.0" } dao-testing = { path = "./packages/dao-testing", version = "2.2.0" } -dao-vote-hooks = { path = "./packages/dao-vote-hooks", version = "2.2.0" } dao-voting = { path = "./packages/dao-voting", version = "2.2.0" } dao-voting-cw20-balance = { path = "./test-contracts/dao-voting-cw20-balance", version = "2.2.0" } dao-voting-cw20-staked = { path = "./contracts/voting/dao-voting-cw20-staked", version = "2.2.0" } diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml b/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml index 659b2d7d7..c38ce7f64 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-approval-single/Cargo.toml @@ -35,7 +35,7 @@ cw4-group = { workspace = true } cw20 = { workspace = true } cw20-base = { workspace = true } dao-dao-core = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-testing = { workspace = true } dao-voting = { workspace = true } dao-voting-cw4 = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml b/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml index 05bc50642..1f6e594fc 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-approver/Cargo.toml @@ -35,7 +35,7 @@ cw4-group = { workspace = true } cw20 = { workspace = true } cw20-base = { workspace = true } dao-dao-core = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-proposal-single = { workspace = true, features = ["library"] } dao-testing = { workspace = true } dao-voting = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml b/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml index 7352139b4..670c0f281 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-multiple/Cargo.toml @@ -37,4 +37,4 @@ dao-voting = { workspace = true } cw-denom = { workspace = true } dao-interface = { workspace = true } dao-testing = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } diff --git a/contracts/pre-propose/dao-pre-propose-single/Cargo.toml b/contracts/pre-propose/dao-pre-propose-single/Cargo.toml index c7d1dd39a..ac1fea9d4 100644 --- a/contracts/pre-propose/dao-pre-propose-single/Cargo.toml +++ b/contracts/pre-propose/dao-pre-propose-single/Cargo.toml @@ -36,6 +36,6 @@ dao-voting = { workspace = true } cw-denom = { workspace = true } dao-interface = { workspace = true } dao-testing = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-proposal-single = { workspace = true } cw-hooks = { workspace = true } diff --git a/contracts/proposal/dao-proposal-multiple/Cargo.toml b/contracts/proposal/dao-proposal-multiple/Cargo.toml index 35f200f8e..1d090e620 100644 --- a/contracts/proposal/dao-proposal-multiple/Cargo.toml +++ b/contracts/proposal/dao-proposal-multiple/Cargo.toml @@ -38,8 +38,7 @@ dao-pre-propose-base = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } cw-hooks = { workspace = true } -dao-proposal-hooks = { workspace = true } -dao-vote-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-pre-propose-multiple = { workspace = true } voting-v1 = { workspace = true } diff --git a/contracts/proposal/dao-proposal-multiple/src/contract.rs b/contracts/proposal/dao-proposal-multiple/src/contract.rs index 35465ab50..073eb5511 100644 --- a/contracts/proposal/dao-proposal-multiple/src/contract.rs +++ b/contracts/proposal/dao-proposal-multiple/src/contract.rs @@ -9,10 +9,10 @@ use cw2::set_contract_version; use cw_hooks::Hooks; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; +use dao_hooks::proposal::{new_proposal_hooks, proposal_status_changed_hooks}; +use dao_hooks::vote::new_vote_hooks; use dao_interface::voting::IsActiveResponse; use dao_pre_propose_multiple::contract::ExecuteMsg as PreProposeMsg; -use dao_proposal_hooks::{new_proposal_hooks, proposal_status_changed_hooks}; -use dao_vote_hooks::new_vote_hooks; use dao_voting::{ multiple_choice::{ MultipleChoiceOptions, MultipleChoiceVote, MultipleChoiceVotes, VotingStrategy, diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index fdf6e842d..4ef03f069 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -32,8 +32,7 @@ dao-pre-propose-base = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } cw-hooks = { workspace = true } -dao-proposal-hooks = { workspace = true } -dao-vote-hooks = { workspace = true } +dao-hooks = { workspace = true } cw-utils-v1 = { workspace = true} voting-v1 = { workspace = true } diff --git a/contracts/proposal/dao-proposal-single/src/contract.rs b/contracts/proposal/dao-proposal-single/src/contract.rs index 5f30030f5..b5401786a 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -9,9 +9,9 @@ use cw_hooks::Hooks; use cw_proposal_single_v1 as v1; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; +use dao_hooks::proposal::{new_proposal_hooks, proposal_status_changed_hooks}; +use dao_hooks::vote::new_vote_hooks; use dao_interface::voting::IsActiveResponse; -use dao_proposal_hooks::{new_proposal_hooks, proposal_status_changed_hooks}; -use dao_vote_hooks::new_vote_hooks; use dao_voting::pre_propose::{PreProposeInfo, ProposalCreationPolicy}; use dao_voting::proposal::{ SingleChoiceProposeMsg as ProposeMsg, DEFAULT_LIMIT, MAX_PROPOSAL_SIZE, diff --git a/packages/cw-hooks/README.md b/packages/cw-hooks/README.md index d597f945c..f078a6069 100644 --- a/packages/cw-hooks/README.md +++ b/packages/cw-hooks/README.md @@ -1,7 +1,7 @@ # CosmWasm DAO Hooks This package provides shared hook functionality used for -[proposal](../dao-proposal-hooks) and [vote](../dao-vote-hooks) hooks. +[dao-hooks](../dao-hooks). It deviates from other CosmWasm hook packages in that hooks can be modified based on their index in the hook list AND based on the diff --git a/packages/dao-proposal-hooks/Cargo.toml b/packages/dao-hooks/Cargo.toml similarity index 62% rename from packages/dao-proposal-hooks/Cargo.toml rename to packages/dao-hooks/Cargo.toml index 7ecb29919..d675dc65a 100644 --- a/packages/dao-proposal-hooks/Cargo.toml +++ b/packages/dao-hooks/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "dao-proposal-hooks" -authors = ["Callum Anderson "] -description = "A package for managing proposal hooks." +name = "dao-hooks" +authors = ["ekez ekez@withoutdoing.com", "Jake Hartnell "] +description = "A package for managing DAO vote, proposal, and stake hooks." edition = { workspace = true } license = { workspace = true } repository = { workspace = true } diff --git a/packages/dao-hooks/README.md b/packages/dao-hooks/README.md new file mode 100644 index 000000000..b83076cf5 --- /dev/null +++ b/packages/dao-hooks/README.md @@ -0,0 +1,20 @@ +# DAO Hooks +This package provides an interface for managing and dispatching proposal, +staking, and voting related hooks. + +### Proposal Hooks +There are two types of proposal hooks: +- **New Proposal Hook:** fired when a new proposal is created. +- **Proposal Staus Changed Hook:** fired when a proposal's status changes. + +Our wiki contains more info on [Proposal Hooks](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). + +### Stake Hooks +Staking hooks are fired when tokens are staked or unstaked in a DAO. + +TODO document types of staking hooks + +### Vote Hooks +Vote hooks are fired when new votes are cast. + +You can read more about vote hooks in our [wiki](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). diff --git a/packages/dao-hooks/src/lib.rs b/packages/dao-hooks/src/lib.rs new file mode 100644 index 000000000..89dda2b2c --- /dev/null +++ b/packages/dao-hooks/src/lib.rs @@ -0,0 +1,7 @@ +#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] + +pub mod proposal; +// pub mod stake; +pub mod vote; + +// TODO DAO hooks enum with test that contains all hooks diff --git a/packages/dao-proposal-hooks/src/lib.rs b/packages/dao-hooks/src/proposal.rs similarity index 97% rename from packages/dao-proposal-hooks/src/lib.rs rename to packages/dao-hooks/src/proposal.rs index 9e0142a88..9db23fcdb 100644 --- a/packages/dao-proposal-hooks/src/lib.rs +++ b/packages/dao-hooks/src/proposal.rs @@ -1,5 +1,3 @@ -#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] - use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; diff --git a/packages/dao-hooks/src/stake.rs b/packages/dao-hooks/src/stake.rs new file mode 100644 index 000000000..91a2a8d96 --- /dev/null +++ b/packages/dao-hooks/src/stake.rs @@ -0,0 +1 @@ +// TODO Stake hooks for NFTs and Tokens with unit tests diff --git a/packages/dao-vote-hooks/src/lib.rs b/packages/dao-hooks/src/vote.rs similarity index 94% rename from packages/dao-vote-hooks/src/lib.rs rename to packages/dao-hooks/src/vote.rs index cfc13aedd..b60eb5aee 100644 --- a/packages/dao-vote-hooks/src/lib.rs +++ b/packages/dao-hooks/src/vote.rs @@ -1,5 +1,3 @@ -#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] - use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; diff --git a/packages/dao-pre-propose-base/Cargo.toml b/packages/dao-pre-propose-base/Cargo.toml index ebc864973..a0be0d404 100644 --- a/packages/dao-pre-propose-base/Cargo.toml +++ b/packages/dao-pre-propose-base/Cargo.toml @@ -24,7 +24,7 @@ cw-denom = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-hooks = { workspace = true } -dao-proposal-hooks = { workspace = true } +dao-hooks = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } serde = { workspace = true } diff --git a/packages/dao-proposal-hooks/README.md b/packages/dao-proposal-hooks/README.md deleted file mode 100644 index 2ab9ba5bb..000000000 --- a/packages/dao-proposal-hooks/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# CosmWasm DAO Proposal Hooks - -This package provides an interface for managing and dispatching -proposal hooks from a proposal module. - -There are two types of proposal hooks: -- **New Proposal Hook:** fired when a new proposal is created. -- **Proposal Staus Changed Hook:** fired when a proposal's status changes. - -Our wiki contains more info on [Proposal Hooks](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). diff --git a/packages/dao-vote-hooks/Cargo.toml b/packages/dao-vote-hooks/Cargo.toml deleted file mode 100644 index 067e11158..000000000 --- a/packages/dao-vote-hooks/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "dao-vote-hooks" -authors = ["ekez ekez@withoutdoing.com"] -description = "A package for managing vote hooks." -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } - -[dependencies] -cosmwasm-std = { workspace = true } -cosmwasm-schema = { workspace = true } -cw-hooks = { workspace = true } -dao-voting = { workspace = true } diff --git a/packages/dao-vote-hooks/README.md b/packages/dao-vote-hooks/README.md deleted file mode 100644 index ae2be9cca..000000000 --- a/packages/dao-vote-hooks/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# CosmWasm DAO Vote Hooks - -This package provides an interface for managing and dispatching -vote hooks from a proposal module. Vote hooks are fired when new -votes are cast. - -You can read more about vote hooks in our [wiki](https://github.com/DA0-DA0/dao-contracts/wiki/Proposal-Hooks-Interactions). diff --git a/scripts/publish.sh b/scripts/publish.sh index ba42f086e..e8f688008 100644 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -67,16 +67,12 @@ cd packages/dao-voting cargo publish cd "$START_DIR" -cd packages/dao-vote-hooks +cd packages/dao-hooks cargo publish cd "$START_DIR" sleep 120 -cd packages/dao-proposal-hooks -cargo publish -cd "$START_DIR" - cd packages/dao-pre-propose-base cargo publish cd "$START_DIR" diff --git a/test-contracts/dao-proposal-hook-counter/Cargo.toml b/test-contracts/dao-proposal-hook-counter/Cargo.toml index 5effd61ef..395fb338a 100644 --- a/test-contracts/dao-proposal-hook-counter/Cargo.toml +++ b/test-contracts/dao-proposal-hook-counter/Cargo.toml @@ -22,8 +22,7 @@ cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } thiserror = { workspace = true } -dao-proposal-hooks = { workspace = true } -dao-vote-hooks = { workspace = true } +dao-hooks = { workspace = true } [dev-dependencies] cw-hooks = { workspace = true } diff --git a/test-contracts/dao-proposal-hook-counter/src/contract.rs b/test-contracts/dao-proposal-hook-counter/src/contract.rs index 2a9163d02..60353f935 100644 --- a/test-contracts/dao-proposal-hook-counter/src/contract.rs +++ b/test-contracts/dao-proposal-hook-counter/src/contract.rs @@ -2,8 +2,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; -use dao_proposal_hooks::ProposalHookMsg; -use dao_vote_hooks::VoteHookMsg; +use dao_hooks::{proposal::ProposalHookMsg, vote::VoteHookMsg}; use crate::error::ContractError; use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; diff --git a/test-contracts/dao-proposal-hook-counter/src/msg.rs b/test-contracts/dao-proposal-hook-counter/src/msg.rs index 825c57bf4..547f402ea 100644 --- a/test-contracts/dao-proposal-hook-counter/src/msg.rs +++ b/test-contracts/dao-proposal-hook-counter/src/msg.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use dao_proposal_hooks::ProposalHookMsg; -use dao_vote_hooks::VoteHookMsg; +use dao_hooks::{proposal::ProposalHookMsg, vote::VoteHookMsg}; #[cw_serde] pub struct InstantiateMsg { From f96e6e4097a6cacc9d1247585da8267b95e48365 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 15:25:40 -0700 Subject: [PATCH 24/59] Improve code resuse by consilidating stake hooks --- Cargo.lock | 5 ++ .../cw20-stake-external-rewards/Cargo.toml | 1 + .../src/contract.rs | 2 +- .../cw20-stake-external-rewards/src/msg.rs | 2 +- contracts/staking/cw20-stake/Cargo.toml | 2 + contracts/staking/cw20-stake/src/contract.rs | 6 +-- contracts/staking/cw20-stake/src/error.rs | 2 +- contracts/staking/cw20-stake/src/hooks.rs | 52 ------------------ contracts/staking/cw20-stake/src/lib.rs | 1 - contracts/staking/cw20-stake/src/state.rs | 2 +- .../voting/dao-voting-cw721-staked/Cargo.toml | 5 +- .../dao-voting-native-staked/Cargo.toml | 1 + .../dao-voting-native-staked/src/contract.rs | 6 +-- .../dao-voting-native-staked/src/hooks.rs | 51 ------------------ .../dao-voting-native-staked/src/lib.rs | 1 - .../dao-voting-native-staked/src/tests.rs | 15 +++--- packages/dao-hooks/src/lib.rs | 2 +- packages/dao-hooks/src/stake.rs | 54 ++++++++++++++++++- .../dao-proposal-hook-counter/src/contract.rs | 37 +++++++++++-- .../dao-proposal-hook-counter/src/msg.rs | 5 +- .../dao-proposal-hook-counter/src/state.rs | 3 +- 21 files changed, 123 insertions(+), 132 deletions(-) delete mode 100644 contracts/staking/cw20-stake/src/hooks.rs delete mode 100644 contracts/voting/dao-voting-native-staked/src/hooks.rs diff --git a/Cargo.lock b/Cargo.lock index 02898f673..1c5b2aa77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,6 +1291,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-controllers 1.1.0", + "cw-hooks", "cw-multi-test", "cw-ownable", "cw-paginate-storage 2.2.0", @@ -1301,6 +1302,7 @@ dependencies = [ "cw20 1.1.0", "cw20-base 1.1.0", "cw20-stake 0.2.6", + "dao-hooks", "thiserror", ] @@ -1322,6 +1324,7 @@ dependencies = [ "cw20 1.1.0", "cw20-base 1.1.0", "cw20-stake 2.2.0", + "dao-hooks", "stake-cw20-external-rewards", "thiserror", ] @@ -2108,6 +2111,7 @@ dependencies = [ "cw721-base 0.18.0", "cw721-controllers", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-testing", "dao-voting 2.2.0", @@ -2134,6 +2138,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.1.0", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-voting 2.2.0", "thiserror", diff --git a/contracts/staking/cw20-stake-external-rewards/Cargo.toml b/contracts/staking/cw20-stake-external-rewards/Cargo.toml index 1a7dd7cf1..b648436c1 100644 --- a/contracts/staking/cw20-stake-external-rewards/Cargo.toml +++ b/contracts/staking/cw20-stake-external-rewards/Cargo.toml @@ -28,6 +28,7 @@ cw2 = { workspace = true } thiserror = { workspace = true } cw20-stake = { workspace = true, features = ["library"]} cw-ownable = { workspace = true } +dao-hooks = { workspace = true } cw20-stake-external-rewards-v1 = { workspace = true } cw20-013 = { package = "cw20", version = "0.13" } diff --git a/contracts/staking/cw20-stake-external-rewards/src/contract.rs b/contracts/staking/cw20-stake-external-rewards/src/contract.rs index a2a50412d..b1774414f 100644 --- a/contracts/staking/cw20-stake-external-rewards/src/contract.rs +++ b/contracts/staking/cw20-stake-external-rewards/src/contract.rs @@ -19,7 +19,7 @@ use cosmwasm_std::{ }; use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw20::{Cw20ReceiveMsg, Denom}; -use cw20_stake::hooks::StakeChangedHookMsg; +use dao_hooks::stake::StakeChangedHookMsg; use cw20::Denom::Cw20; use std::cmp::min; diff --git a/contracts/staking/cw20-stake-external-rewards/src/msg.rs b/contracts/staking/cw20-stake-external-rewards/src/msg.rs index 565508e39..eb873c798 100644 --- a/contracts/staking/cw20-stake-external-rewards/src/msg.rs +++ b/contracts/staking/cw20-stake-external-rewards/src/msg.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Uint128; use cw20::{Cw20ReceiveMsg, Denom}; -use cw20_stake::hooks::StakeChangedHookMsg; +use dao_hooks::stake::StakeChangedHookMsg; use crate::state::{Config, RewardConfig}; diff --git a/contracts/staking/cw20-stake/Cargo.toml b/contracts/staking/cw20-stake/Cargo.toml index a3706eed7..10ebaadc3 100644 --- a/contracts/staking/cw20-stake/Cargo.toml +++ b/contracts/staking/cw20-stake/Cargo.toml @@ -21,6 +21,7 @@ cosmwasm-storage = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } +cw-hooks = { workspace = true } cw20 = { workspace = true } cw-utils = { workspace = true } cw20-base = { workspace = true, features = ["library"] } @@ -28,6 +29,7 @@ cw2 = { workspace = true } thiserror = { workspace = true } cw-paginate-storage = { workspace = true } cw-ownable = { workspace = true } +dao-hooks = { 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 d25417f87..295cc13b7 100644 --- a/contracts/staking/cw20-stake/src/contract.rs +++ b/contracts/staking/cw20-stake/src/contract.rs @@ -8,7 +8,6 @@ use cosmwasm_std::{ use cw20::{Cw20ReceiveMsg, TokenInfoResponse}; -use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; use crate::math; use crate::msg::{ ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, @@ -32,6 +31,7 @@ pub use cw20_base::contract::{ 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}; pub(crate) const CONTRACT_NAME: &str = "crates.io:cw20-stake"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -182,7 +182,7 @@ pub fn execute_stake( deps.storage, &balance.checked_add(amount).map_err(StdError::overflow)?, )?; - let hook_msgs = stake_hook_msgs(deps.storage, sender.clone(), amount_to_stake)?; + let hook_msgs = stake_hook_msgs(HOOKS, deps.storage, sender.clone(), amount_to_stake)?; Ok(Response::new() .add_submessages(hook_msgs) .add_attribute("action", "stake") @@ -230,7 +230,7 @@ pub fn execute_unstake( .checked_sub(amount_to_claim) .map_err(StdError::overflow)?, )?; - let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + let hook_msgs = unstake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; match config.unstaking_duration { None => { let cw_send_msg = cw20::Cw20ExecuteMsg::Transfer { diff --git a/contracts/staking/cw20-stake/src/error.rs b/contracts/staking/cw20-stake/src/error.rs index cd7140a21..016f271bf 100644 --- a/contracts/staking/cw20-stake/src/error.rs +++ b/contracts/staking/cw20-stake/src/error.rs @@ -10,7 +10,7 @@ pub enum ContractError { #[error(transparent)] Ownership(#[from] cw_ownable::OwnershipError), #[error(transparent)] - HookError(#[from] cw_controllers::HookError), + HookError(#[from] cw_hooks::HookError), #[error("Provided cw20 errored in response to TokenInfo query")] InvalidCw20 {}, diff --git a/contracts/staking/cw20-stake/src/hooks.rs b/contracts/staking/cw20-stake/src/hooks.rs deleted file mode 100644 index 5867d8ff4..000000000 --- a/contracts/staking/cw20-stake/src/hooks.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::state::HOOKS; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; - -// This is just a helper to properly serialize the above message -#[cw_serde] -pub enum StakeChangedHookMsg { - Stake { addr: Addr, amount: Uint128 }, - Unstake { addr: Addr, amount: Uint128 }, -} - -pub fn stake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Stake { addr, amount }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -pub fn unstake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Unstake { addr, amount }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum StakeChangedExecuteMsg { - StakeChangeHook(StakeChangedHookMsg), -} diff --git a/contracts/staking/cw20-stake/src/lib.rs b/contracts/staking/cw20-stake/src/lib.rs index 6688af2cd..e8bfc90e0 100644 --- a/contracts/staking/cw20-stake/src/lib.rs +++ b/contracts/staking/cw20-stake/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; mod error; -pub mod hooks; mod math; pub mod msg; pub mod state; diff --git a/contracts/staking/cw20-stake/src/state.rs b/contracts/staking/cw20-stake/src/state.rs index 2ebb1c887..5323d9008 100644 --- a/contracts/staking/cw20-stake/src/state.rs +++ b/contracts/staking/cw20-stake/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_controllers::Claims; -use cw_controllers::Hooks; +use cw_hooks::Hooks; use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; use cw_utils::Duration; diff --git a/contracts/voting/dao-voting-cw721-staked/Cargo.toml b/contracts/voting/dao-voting-cw721-staked/Cargo.toml index e5535c673..ea5e67529 100644 --- a/contracts/voting/dao-voting-cw721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-staked/Cargo.toml @@ -19,14 +19,15 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } -dao-dao-macros = { workspace = true } -dao-interface = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } cw721-controllers = { workspace = true } cw-paginate-storage = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } +dao-dao-macros = { workspace = true } +dao-hooks = { workspace = true } +dao-interface = { workspace = true } dao-voting = { workspace = true } sg-std = { workspace = true } sg721 = { workspace = true } diff --git a/contracts/voting/dao-voting-native-staked/Cargo.toml b/contracts/voting/dao-voting-native-staked/Cargo.toml index 01131e006..43afb4bd1 100644 --- a/contracts/voting/dao-voting-native-staked/Cargo.toml +++ b/contracts/voting/dao-voting-native-staked/Cargo.toml @@ -27,6 +27,7 @@ cw-paginate-storage = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } dao-dao-macros = { workspace = true } +dao-hooks = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs index b264efeca..39642295c 100644 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ b/contracts/voting/dao-voting-native-staked/src/contract.rs @@ -7,13 +7,13 @@ use cosmwasm_std::{ use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_controllers::ClaimsResponse; use cw_utils::{must_pay, Duration}; +use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; use dao_interface::voting::{ IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; use crate::error::ContractError; -use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; use crate::msg::{ DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, StakerBalanceResponse, @@ -146,7 +146,7 @@ pub fn execute_stake( )?; // Add stake hook messages - let hook_msgs = stake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + let hook_msgs = stake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; Ok(Response::new() .add_submessages(hook_msgs) @@ -188,7 +188,7 @@ pub fn execute_unstake( )?; // Add unstake hook messages - let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + let hook_msgs = unstake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; let config = CONFIG.load(deps.storage)?; match config.unstaking_duration { diff --git a/contracts/voting/dao-voting-native-staked/src/hooks.rs b/contracts/voting/dao-voting-native-staked/src/hooks.rs deleted file mode 100644 index a04d3b043..000000000 --- a/contracts/voting/dao-voting-native-staked/src/hooks.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::state::HOOKS; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; - -#[cw_serde] -pub enum StakeChangedHookMsg { - Stake { addr: Addr, amount: Uint128 }, - Unstake { addr: Addr, amount: Uint128 }, -} - -pub fn stake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Stake { addr, amount }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -pub fn unstake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Unstake { addr, amount }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum StakeChangedExecuteMsg { - StakeChangeHook(StakeChangedHookMsg), -} diff --git a/contracts/voting/dao-voting-native-staked/src/lib.rs b/contracts/voting/dao-voting-native-staked/src/lib.rs index 6c512e72b..d1800adbc 100644 --- a/contracts/voting/dao-voting-native-staked/src/lib.rs +++ b/contracts/voting/dao-voting-native-staked/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; mod error; -pub mod hooks; pub mod msg; pub mod state; diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs index 6d943a758..c350af173 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-native-staked/src/tests.rs @@ -1,10 +1,3 @@ -use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; -use crate::error::ContractError; -use crate::msg::{ - DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, - QueryMsg, StakerBalanceResponse, -}; -use crate::state::Config; use cosmwasm_std::testing::{mock_dependencies, mock_env}; use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cw_controllers::ClaimsResponse; @@ -17,6 +10,14 @@ use dao_interface::voting::{ }; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; +use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; +use crate::error::ContractError; +use crate::msg::{ + DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, + QueryMsg, StakerBalanceResponse, +}; +use crate::state::Config; + const DAO_ADDR: &str = "dao"; const ADDR1: &str = "addr1"; const ADDR2: &str = "addr2"; diff --git a/packages/dao-hooks/src/lib.rs b/packages/dao-hooks/src/lib.rs index 89dda2b2c..f6f59c91b 100644 --- a/packages/dao-hooks/src/lib.rs +++ b/packages/dao-hooks/src/lib.rs @@ -1,7 +1,7 @@ #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] pub mod proposal; -// pub mod stake; +pub mod stake; pub mod vote; // TODO DAO hooks enum with test that contains all hooks diff --git a/packages/dao-hooks/src/stake.rs b/packages/dao-hooks/src/stake.rs index 91a2a8d96..3abdd15d1 100644 --- a/packages/dao-hooks/src/stake.rs +++ b/packages/dao-hooks/src/stake.rs @@ -1 +1,53 @@ -// TODO Stake hooks for NFTs and Tokens with unit tests +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; +use cw_hooks::Hooks; + +#[cw_serde] +pub enum StakeChangedHookMsg { + Stake { addr: Addr, amount: Uint128 }, + Unstake { addr: Addr, amount: Uint128 }, +} + +pub fn stake_hook_msgs( + hooks: Hooks, + storage: &dyn Storage, + addr: Addr, + amount: Uint128, +) -> StdResult> { + let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( + StakeChangedHookMsg::Stake { addr, amount }, + ))?; + hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.to_string(), + msg: msg.clone(), + funds: vec![], + }; + Ok(SubMsg::new(execute)) + }) +} + +pub fn unstake_hook_msgs( + hooks: Hooks, + storage: &dyn Storage, + addr: Addr, + amount: Uint128, +) -> StdResult> { + let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( + StakeChangedHookMsg::Unstake { addr, amount }, + ))?; + hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.to_string(), + msg: msg.clone(), + funds: vec![], + }; + Ok(SubMsg::new(execute)) + }) +} + +// This is just a helper to properly serialize the above message +#[cw_serde] +enum StakeChangedExecuteMsg { + StakeChangeHook(StakeChangedHookMsg), +} diff --git a/test-contracts/dao-proposal-hook-counter/src/contract.rs b/test-contracts/dao-proposal-hook-counter/src/contract.rs index 60353f935..0f216972e 100644 --- a/test-contracts/dao-proposal-hook-counter/src/contract.rs +++ b/test-contracts/dao-proposal-hook-counter/src/contract.rs @@ -2,11 +2,14 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; +use dao_hooks::stake::StakeChangedHookMsg; use dao_hooks::{proposal::ProposalHookMsg, vote::VoteHookMsg}; use crate::error::ContractError; use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{Config, CONFIG, PROPOSAL_COUNTER, STATUS_CHANGED_COUNTER, VOTE_COUNTER}; +use crate::state::{ + Config, CONFIG, PROPOSAL_COUNTER, STAKE_COUNTER, STATUS_CHANGED_COUNTER, VOTE_COUNTER, +}; const CONTRACT_NAME: &str = "crates.io:proposal-hooks-counter"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -46,6 +49,7 @@ pub fn execute( ExecuteMsg::ProposalHook(proposal_hook) => { execute_proposal_hook(deps, env, info, proposal_hook) } + ExecuteMsg::StakeHook(stake_hook) => execute_stake_hook(deps, env, info, stake_hook), ExecuteMsg::VoteHook(vote_hook) => execute_vote_hook(deps, env, info, vote_hook), } } @@ -72,6 +76,28 @@ pub fn execute_proposal_hook( Ok(Response::new().add_attribute("action", "proposal_hook")) } +pub fn execute_stake_hook( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + stake_hook: StakeChangedHookMsg, +) -> Result { + match stake_hook { + StakeChangedHookMsg::Stake { .. } => { + let mut count = STAKE_COUNTER.load(deps.storage)?; + count += 1; + STAKE_COUNTER.save(deps.storage, &count)?; + } + StakeChangedHookMsg::Unstake { .. } => { + let mut count = STAKE_COUNTER.load(deps.storage)?; + count += 1; + STAKE_COUNTER.save(deps.storage, &count)?; + } + } + + Ok(Response::new().add_attribute("action", "stake_hook")) +} + pub fn execute_vote_hook( deps: DepsMut, _env: Env, @@ -92,14 +118,17 @@ pub fn execute_vote_hook( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::VoteCounter {} => to_binary(&CountResponse { - count: VOTE_COUNTER.load(deps.storage)?, - }), QueryMsg::ProposalCounter {} => to_binary(&CountResponse { count: PROPOSAL_COUNTER.load(deps.storage)?, }), + QueryMsg::StakeCounter {} => to_binary(&CountResponse { + count: STAKE_COUNTER.load(deps.storage)?, + }), QueryMsg::StatusChangedCounter {} => to_binary(&CountResponse { count: STATUS_CHANGED_COUNTER.load(deps.storage)?, }), + QueryMsg::VoteCounter {} => to_binary(&CountResponse { + count: VOTE_COUNTER.load(deps.storage)?, + }), } } diff --git a/test-contracts/dao-proposal-hook-counter/src/msg.rs b/test-contracts/dao-proposal-hook-counter/src/msg.rs index 547f402ea..2c4557a33 100644 --- a/test-contracts/dao-proposal-hook-counter/src/msg.rs +++ b/test-contracts/dao-proposal-hook-counter/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use dao_hooks::{proposal::ProposalHookMsg, vote::VoteHookMsg}; +use dao_hooks::{proposal::ProposalHookMsg, stake::StakeChangedHookMsg, vote::VoteHookMsg}; #[cw_serde] pub struct InstantiateMsg { @@ -9,12 +9,15 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { ProposalHook(ProposalHookMsg), + StakeHook(StakeChangedHookMsg), VoteHook(VoteHookMsg), } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(u64)] + StakeCounter {}, #[returns(u64)] VoteCounter {}, #[returns(u64)] diff --git a/test-contracts/dao-proposal-hook-counter/src/state.rs b/test-contracts/dao-proposal-hook-counter/src/state.rs index 4f22cb0e7..7f763509b 100644 --- a/test-contracts/dao-proposal-hook-counter/src/state.rs +++ b/test-contracts/dao-proposal-hook-counter/src/state.rs @@ -6,6 +6,7 @@ pub struct Config { pub should_error: bool, } pub const CONFIG: Item = Item::new("config"); -pub const VOTE_COUNTER: Item = Item::new("vote_counter"); pub const PROPOSAL_COUNTER: Item = Item::new("proposal_counter"); +pub const STAKE_COUNTER: Item = Item::new("stake_counter"); pub const STATUS_CHANGED_COUNTER: Item = Item::new("stauts_changed_counter"); +pub const VOTE_COUNTER: Item = Item::new("vote_counter"); From 17928bc079425c3ba6ac7b6271cf14eb35faf638 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 17:34:30 -0700 Subject: [PATCH 25/59] Clean up types --- .../dao-voting-cw721-staked/src/hooks.rs | 2 + .../Cargo.toml | 1 + .../src/contract.rs | 100 ++++++++---------- .../src/hooks.rs | 52 --------- .../src/lib.rs | 1 - .../src/tests/multitest/mod.rs | 17 ++- 6 files changed, 55 insertions(+), 118 deletions(-) delete mode 100644 contracts/voting/dao-voting-token-factory-staked/src/hooks.rs diff --git a/contracts/voting/dao-voting-cw721-staked/src/hooks.rs b/contracts/voting/dao-voting-cw721-staked/src/hooks.rs index b8ec5f175..86352e4fb 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/hooks.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/hooks.rs @@ -1,3 +1,5 @@ +// TODO move this + use crate::state::HOOKS; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, WasmMsg}; diff --git a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml index d66450ba0..be76ba3e1 100644 --- a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml @@ -32,6 +32,7 @@ cw-controllers = { workspace = true } cw-hooks = { workspace = true } thiserror = { workspace = true } dao-dao-macros = { workspace = true } +dao-hooks = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } cw-paginate-storage = { workspace = true } 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 a8d8e8269..06e0e589a 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -12,14 +12,14 @@ use cw_tokenfactory_issuer::msg::{ DenomUnit, ExecuteMsg as IssuerExecuteMsg, InstantiateMsg as IssuerInstantiateMsg, Metadata, }; use cw_utils::{maybe_addr, must_pay, parse_reply_instantiate_data, Duration}; +use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; use dao_interface::voting::{ IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; use crate::error::ContractError; -use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; + use crate::msg::{ DenomResponse, ExecuteMsg, GetHooksResponse, InitialBalance, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, StakerBalanceResponse, TokenInfo, @@ -62,11 +62,11 @@ fn validate_duration(duration: Option) -> Result<(), ContractError> { #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg, -) -> Result, ContractError> { +) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; validate_duration(msg.unstaking_duration)?; @@ -118,7 +118,7 @@ pub fn instantiate( INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, ); - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "instantiate") .add_attribute("token", "existing_token") .add_attribute("denom", denom) @@ -140,7 +140,7 @@ pub fn instantiate( INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, ); - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "instantiate") .add_attribute("token", "new_token") .add_submessage(issuer_instantiate_msg)) @@ -149,7 +149,7 @@ pub fn instantiate( } pub fn assert_valid_absolute_count_threshold( - deps: Deps, + deps: Deps, token_denom: &str, count: Uint128, ) -> Result<(), ContractError> { @@ -165,11 +165,11 @@ pub fn assert_valid_absolute_count_threshold( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result, ContractError> { +) -> Result { match msg { ExecuteMsg::Stake {} => execute_stake(deps, env, info), ExecuteMsg::Unstake { amount } => execute_unstake(deps, env, info, amount), @@ -184,10 +184,10 @@ pub fn execute( } pub fn execute_stake( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, -) -> Result, ContractError> { +) -> Result { let denom = DENOM.load(deps.storage)?; let amount = must_pay(&info, &denom)?; @@ -204,9 +204,9 @@ pub fn execute_stake( )?; // Add stake hook messages - let hook_msgs = stake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + let hook_msgs = stake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; - Ok(Response::::new() + Ok(Response::new() .add_submessages(hook_msgs) .add_attribute("action", "stake") .add_attribute("amount", amount.to_string()) @@ -214,11 +214,11 @@ pub fn execute_stake( } pub fn execute_unstake( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128, -) -> Result, ContractError> { +) -> Result { if amount.is_zero() { return Err(ContractError::ZeroUnstake {}); } @@ -246,7 +246,7 @@ pub fn execute_unstake( )?; // Add unstake hook messages - let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), amount)?; + let hook_msgs = unstake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; let config = CONFIG.load(deps.storage)?; let denom = DENOM.load(deps.storage)?; @@ -256,7 +256,7 @@ pub fn execute_unstake( to_address: info.sender.to_string(), amount: coins(amount.u128(), denom), }); - Ok(Response::::new() + Ok(Response::new() .add_message(msg) .add_submessages(hook_msgs) .add_attribute("action", "unstake") @@ -276,7 +276,7 @@ pub fn execute_unstake( amount, duration.after(&env.block), )?; - Ok(Response::::new() + Ok(Response::new() .add_submessages(hook_msgs) .add_attribute("action", "unstake") .add_attribute("from", info.sender) @@ -287,10 +287,10 @@ pub fn execute_unstake( } pub fn execute_update_config( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, duration: Option, -) -> Result, ContractError> { +) -> Result { let mut config: Config = CONFIG.load(deps.storage)?; // Only the DAO can update the config @@ -304,26 +304,26 @@ pub fn execute_update_config( config.unstaking_duration = duration; CONFIG.save(deps.storage, &config)?; - Ok(Response::::new().add_attribute("action", "update_config")) + Ok(Response::new().add_attribute("action", "update_config")) } pub fn execute_claim( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, -) -> Result, ContractError> { +) -> Result { let release = CLAIMS.claim_tokens(deps.storage, &info.sender, &env.block, None)?; if release.is_zero() { return Err(ContractError::NothingToClaim {}); } let denom = DENOM.load(deps.storage)?; - let msg = CosmosMsg::::Bank(BankMsg::Send { + let msg = CosmosMsg::Bank(BankMsg::Send { to_address: info.sender.to_string(), amount: coins(release.u128(), denom), }); - Ok(Response::::new() + Ok(Response::new() .add_message(msg) .add_attribute("action", "claim") .add_attribute("from", info.sender) @@ -331,11 +331,11 @@ pub fn execute_claim( } pub fn execute_update_active_threshold( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, new_active_threshold: Option, -) -> Result, ContractError> { +) -> Result { let dao = DAO.load(deps.storage)?; if info.sender != dao { return Err(ContractError::Unauthorized {}); @@ -358,15 +358,15 @@ pub fn execute_update_active_threshold( ACTIVE_THRESHOLD.remove(deps.storage); } - Ok(Response::::new().add_attribute("action", "update_active_threshold")) + Ok(Response::new().add_attribute("action", "update_active_threshold")) } pub fn execute_add_hook( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, addr: String, -) -> Result, ContractError> { +) -> Result { let dao = DAO.load(deps.storage)?; if info.sender != dao { return Err(ContractError::Unauthorized {}); @@ -374,17 +374,17 @@ pub fn execute_add_hook( let hook = deps.api.addr_validate(&addr)?; HOOKS.add_hook(deps.storage, hook)?; - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "add_hook") .add_attribute("hook", addr)) } pub fn execute_remove_hook( - deps: DepsMut, + deps: DepsMut, _env: Env, info: MessageInfo, addr: String, -) -> Result, ContractError> { +) -> Result { let dao = DAO.load(deps.storage)?; if info.sender != dao { return Err(ContractError::Unauthorized {}); @@ -392,13 +392,13 @@ pub fn execute_remove_hook( let hook = deps.api.addr_validate(&addr)?; HOOKS.remove_hook(deps.storage, hook)?; - Ok(Response::::new() + Ok(Response::new() .add_attribute("action", "remove_hook") .add_attribute("hook", addr)) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::VotingPowerAtHeight { address, height } => { to_binary(&query_voting_power_at_height(deps, env, address, height)?) @@ -424,7 +424,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResul } pub fn query_voting_power_at_height( - deps: Deps, + deps: Deps, env: Env, address: String, height: Option, @@ -438,7 +438,7 @@ pub fn query_voting_power_at_height( } pub fn query_total_power_at_height( - deps: Deps, + deps: Deps, env: Env, height: Option, ) -> StdResult { @@ -449,22 +449,22 @@ pub fn query_total_power_at_height( Ok(TotalPowerAtHeightResponse { power, height }) } -pub fn query_info(deps: Deps) -> StdResult { +pub fn query_info(deps: Deps) -> StdResult { let info = cw2::get_contract_version(deps.storage)?; to_binary(&dao_interface::voting::InfoResponse { info }) } -pub fn query_dao(deps: Deps) -> StdResult { +pub fn query_dao(deps: Deps) -> StdResult { let dao = DAO.load(deps.storage)?; to_binary(&dao) } -pub fn query_claims(deps: Deps, address: String) -> StdResult { +pub fn query_claims(deps: Deps, address: String) -> StdResult { CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?) } pub fn query_list_stakers( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { @@ -486,7 +486,7 @@ pub fn query_list_stakers( to_binary(&ListStakersResponse { stakers }) } -pub fn query_is_active(deps: Deps) -> StdResult { +pub fn query_is_active(deps: Deps) -> StdResult { let threshold = ACTIVE_THRESHOLD.may_load(deps.storage)?; if let Some(threshold) = threshold { let denom = DENOM.load(deps.storage)?; @@ -548,24 +548,20 @@ pub fn query_is_active(deps: Deps) -> StdResult { } } -pub fn query_active_threshold(deps: Deps) -> StdResult { +pub fn query_active_threshold(deps: Deps) -> StdResult { to_binary(&ActiveThresholdResponse { active_threshold: ACTIVE_THRESHOLD.may_load(deps.storage)?, }) } -pub fn query_hooks(deps: Deps) -> StdResult { +pub fn query_hooks(deps: Deps) -> StdResult { Ok(GetHooksResponse { hooks: HOOKS.query_hooks(deps)?.hooks, }) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate( - deps: DepsMut, - _env: Env, - _msg: MigrateMsg, -) -> Result, ContractError> { +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { let storage_version: ContractVersion = get_contract_version(deps.storage)?; // Only migrate if newer @@ -578,11 +574,7 @@ pub fn migrate( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply( - deps: DepsMut, - env: Env, - msg: Reply, -) -> Result, ContractError> { +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { match msg.id { INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID => { // Parse and save address of cw-tokenfactory-issuer diff --git a/contracts/voting/dao-voting-token-factory-staked/src/hooks.rs b/contracts/voting/dao-voting-token-factory-staked/src/hooks.rs deleted file mode 100644 index b5941ce84..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/src/hooks.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::state::HOOKS; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; -use token_bindings::TokenFactoryMsg; - -#[cw_serde] -pub enum StakeChangedHookMsg { - Stake { addr: Addr, amount: Uint128 }, - Unstake { addr: Addr, amount: Uint128 }, -} - -pub fn stake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult>> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Stake { addr, amount }, - ))?; - HOOKS.prepare_hooks_custom_msg(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::::new(execute)) - }) -} - -pub fn unstake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - amount: Uint128, -) -> StdResult>> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Unstake { addr, amount }, - ))?; - HOOKS.prepare_hooks_custom_msg(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.to_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::::new(execute)) - }) -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum StakeChangedExecuteMsg { - StakeChangeHook(StakeChangedHookMsg), -} diff --git a/contracts/voting/dao-voting-token-factory-staked/src/lib.rs b/contracts/voting/dao-voting-token-factory-staked/src/lib.rs index 6c512e72b..d1800adbc 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/lib.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; mod error; -pub mod hooks; pub mod msg; pub mod state; diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs index eb46f700f..dd0bf890a 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs @@ -4,8 +4,8 @@ use crate::msg::{ StakerBalanceResponse, TokenInfo, }; use crate::state::Config; -use cosmwasm_std::testing::{mock_env, MockApi, MockQuerier, MockStorage}; -use cosmwasm_std::{coins, Addr, Coin, Decimal, OwnedDeps, Uint128}; +use cosmwasm_std::testing::{mock_dependencies, mock_env}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cw_controllers::ClaimsResponse; use cw_multi_test::{ next_block, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, @@ -15,7 +15,6 @@ use dao_interface::voting::{ InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -use std::marker::PhantomData; use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; use token_bindings_test::TokenFactoryApp as App; @@ -37,12 +36,13 @@ fn issuer_contract() -> Box> { } fn staking_contract() -> Box> { - let contract = ContractWrapper::new( + let contract = ContractWrapper::new_with_empty( crate::contract::execute, crate::contract::instantiate, crate::contract::query, ) - .with_reply(crate::contract::reply); + .with_reply_empty(crate::contract::reply) + .with_migrate_empty(crate::contract::migrate); Box::new(contract) } @@ -1356,12 +1356,7 @@ fn test_add_remove_hooks() { #[test] pub fn test_migrate_update_version() { - let mut deps = OwnedDeps { - storage: MockStorage::default(), - api: MockApi::default(), - querier: MockQuerier::default(), - custom_query_type: PhantomData::, - }; + let mut deps = mock_dependencies(); cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); From 51c79c0b67529db89d6fa97b2bbba751eac83167 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 18:55:29 -0700 Subject: [PATCH 26/59] Remove need for unused TokenFactoryQuery, use shared stake hooks --- Cargo.lock | 37 +- Cargo.toml | 1 - .../cw-tokenfactory-issuer/src/contract.rs | 22 +- .../cw-tokenfactory-issuer/src/execute.rs | 26 +- .../cw-tokenfactory-issuer/src/helpers.rs | 21 +- .../cw-tokenfactory-issuer/src/hooks.rs | 3 +- .../cw-tokenfactory-issuer/src/queries.rs | 41 +- .../Cargo.toml | 1 - .../src/tests/multitest/mod.rs | 1367 +---------------- .../src/tests/multitest/tests.rs | 1366 ++++++++++++++++ .../src/tests/multitest/tf_module_mock.rs | 108 ++ 11 files changed, 1521 insertions(+), 1472 deletions(-) create mode 100644 contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs create mode 100644 contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs diff --git a/Cargo.lock b/Cargo.lock index 1c5b2aa77..422e229ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -848,7 +848,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "derivative", - "itertools 0.10.5", + "itertools", "k256", "prost 0.9.0", "schemars", @@ -2161,6 +2161,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.1.0", "dao-dao-macros", + "dao-hooks", "dao-interface", "dao-testing", "dao-voting 2.2.0", @@ -2169,7 +2170,6 @@ dependencies = [ "serde", "thiserror", "token-bindings 0.11.0 (git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954)", - "token-bindings-test", ] [[package]] @@ -2850,15 +2850,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" @@ -3126,7 +3117,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f47f0b2f22adb341bb59e5a3a1b464dde033181954bd055b9ae86d6511ba465b" dependencies = [ - "itertools 0.10.5", + "itertools", "proc-macro2", "prost-types", "quote", @@ -3138,7 +3129,7 @@ name = "osmosis-std-derive" version = "0.16.2" source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=autobuild-v17.0.0-rc0#36ef03c6d61b860c6bd25cac56bc52144c4e6b9c" dependencies = [ - "itertools 0.10.5", + "itertools", "proc-macro2", "prost-types", "quote", @@ -3364,7 +3355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2", "quote", "syn 1.0.109", @@ -3377,7 +3368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2", "quote", "syn 1.0.109", @@ -4399,22 +4390,6 @@ dependencies = [ "serde", ] -[[package]] -name = "token-bindings-test" -version = "0.11.0" -source = "git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954#0cd084b68172ffc9af29eb37fb915392ce351954" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus 0.16.0", - "itertools 0.11.0", - "schemars", - "serde", - "thiserror", - "token-bindings 0.11.0 (git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954)", -] - [[package]] name = "tokio" version = "1.32.0" diff --git a/Cargo.toml b/Cargo.toml index dab3d3b44..c3185f336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,6 @@ test-context = "0.1" thiserror = { version = "1.0" } # TODO use upstream when PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 token-bindings = { git = "https://github.com/CosmosContracts/token-bindings.git", rev = "0cd084b68172ffc9af29eb37fb915392ce351954" } -token-bindings-test = { git = "https://github.com/CosmosContracts/token-bindings.git", rev = "0cd084b68172ffc9af29eb37fb915392ce351954" } wynd-utils = "0.4" # One commit ahead of version 0.3.0. Allows initialization with an diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index b5e024a37..d3264c8f3 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -10,7 +10,7 @@ use cw2::{get_contract_version, set_contract_version, ContractVersion}; use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ MsgCreateDenom, MsgCreateDenomResponse, MsgSetBeforeSendHook, }; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; +use token_bindings::TokenFactoryMsg; use crate::error::ContractError; use crate::execute; @@ -28,7 +28,7 @@ const BEFORE_SEND_HOOK_REPLY_ID: u64 = 2; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: InstantiateMsg, @@ -73,7 +73,7 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, @@ -117,11 +117,7 @@ pub fn execute( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn sudo( - deps: DepsMut, - _env: Env, - msg: SudoMsg, -) -> Result { +pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result { match msg { SudoMsg::BlockBeforeSend { from, to, amount } => { hooks::beforesend_hook(deps, from, to, amount) @@ -130,7 +126,7 @@ pub fn sudo( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Allowlist { start_after, limit } => { to_binary(&queries::query_allowlist(deps, start_after, limit)?) @@ -162,11 +158,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResu } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate( - deps: DepsMut, - _env: Env, - _msg: MigrateMsg, -) -> Result { +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { let storage_version: ContractVersion = get_contract_version(deps.storage)?; // Only migrate if newer @@ -180,7 +172,7 @@ pub fn migrate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn reply( - deps: DepsMut, + deps: DepsMut, env: Env, msg: Reply, ) -> Result, ContractError> { diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 50866b9bd..8d3725a08 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -3,7 +3,7 @@ use osmosis_std::types::cosmos::bank::v1beta1::Metadata; use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ MsgBurn, MsgForceTransfer, MsgSetBeforeSendHook, MsgSetDenomMetadata, }; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; +use token_bindings::TokenFactoryMsg; use crate::error::ContractError; use crate::helpers::{check_before_send_hook_features_enabled, check_is_contract_owner}; @@ -13,7 +13,7 @@ use crate::state::{ }; pub fn mint( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, to_address: String, @@ -69,7 +69,7 @@ pub fn mint( } pub fn burn( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128, @@ -119,7 +119,7 @@ pub fn burn( } pub fn update_contract_owner( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, new_owner: String, ) -> Result, ContractError> { @@ -140,7 +140,7 @@ pub fn update_contract_owner( } pub fn update_tokenfactory_admin( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, new_admin: String, ) -> Result, ContractError> { @@ -165,7 +165,7 @@ pub fn update_tokenfactory_admin( } pub fn set_denom_metadata( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, metadata: Metadata, @@ -182,7 +182,7 @@ pub fn set_denom_metadata( } pub fn set_before_send_hook( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, ) -> Result, ContractError> { @@ -216,7 +216,7 @@ pub fn set_before_send_hook( } pub fn set_burner( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, address: String, allowance: Uint128, @@ -241,7 +241,7 @@ pub fn set_burner( } pub fn set_minter( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, address: String, allowance: Uint128, @@ -266,7 +266,7 @@ pub fn set_minter( } pub fn freeze( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, status: bool, ) -> Result, ContractError> { @@ -285,7 +285,7 @@ pub fn freeze( } pub fn deny( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, address: String, @@ -319,7 +319,7 @@ pub fn deny( } pub fn allow( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, address: String, status: bool, @@ -347,7 +347,7 @@ pub fn allow( } pub fn force_transfer( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128, diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index c1bf2fc9f..bcb306b75 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -3,12 +3,8 @@ use crate::state::{ }; use crate::ContractError; use cosmwasm_std::{Addr, Deps}; -use token_bindings::TokenFactoryQuery; -pub fn check_is_contract_owner( - deps: Deps, - sender: Addr, -) -> Result<(), ContractError> { +pub fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractError> { let owner = OWNER.load(deps.storage)?; if owner != sender { Err(ContractError::Unauthorized {}) @@ -17,9 +13,7 @@ pub fn check_is_contract_owner( } } -pub fn check_before_send_hook_features_enabled( - deps: Deps, -) -> Result<(), ContractError> { +pub fn check_before_send_hook_features_enabled(deps: Deps) -> Result<(), ContractError> { let enabled = BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage)?; if !enabled { Err(ContractError::BeforeSendHookFeaturesDisabled {}) @@ -28,10 +22,7 @@ pub fn check_before_send_hook_features_enabled( } } -pub fn check_is_not_denied( - deps: Deps, - address: String, -) -> Result<(), ContractError> { +pub fn check_is_not_denied(deps: Deps, address: String) -> Result<(), ContractError> { let addr = deps.api.addr_validate(&address)?; if let Some(is_denied) = DENYLIST.may_load(deps.storage, &addr)? { if is_denied { @@ -41,11 +32,7 @@ pub fn check_is_not_denied( Ok(()) } -pub fn check_is_not_frozen( - deps: Deps, - address: &str, - denom: &str, -) -> Result<(), ContractError> { +pub fn check_is_not_frozen(deps: Deps, address: &str, denom: &str) -> Result<(), ContractError> { let is_frozen = IS_FROZEN.load(deps.storage)?; let contract_denom = DENOM.load(deps.storage)?; diff --git a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs index 2d552692d..81adf650d 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs @@ -1,11 +1,10 @@ use cosmwasm_std::{Coin, DepsMut, Response}; -use token_bindings::TokenFactoryQuery; use crate::error::ContractError; use crate::helpers::{check_is_not_denied, check_is_not_frozen}; pub fn beforesend_hook( - deps: DepsMut, + deps: DepsMut, from: String, to: String, coin: Coin, diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index bb8913c60..b96311061 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{Addr, Deps, Order, StdResult, Uint128}; use cw_storage_plus::{Bound, Map}; -use token_bindings::TokenFactoryQuery; use crate::msg::{ AllowanceInfo, AllowanceResponse, AllowancesResponse, AllowlistResponse, DenomResponse, @@ -15,37 +14,31 @@ use crate::state::{ const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; -pub fn query_denom(deps: Deps) -> StdResult { +pub fn query_denom(deps: Deps) -> StdResult { let denom = DENOM.load(deps.storage)?; Ok(DenomResponse { denom }) } -pub fn query_is_frozen(deps: Deps) -> StdResult { +pub fn query_is_frozen(deps: Deps) -> StdResult { let is_frozen = IS_FROZEN.load(deps.storage)?; Ok(IsFrozenResponse { is_frozen }) } -pub fn query_owner(deps: Deps) -> StdResult { +pub fn query_owner(deps: Deps) -> StdResult { let owner = OWNER.load(deps.storage)?; Ok(OwnerResponse { address: owner.into_string(), }) } -pub fn query_mint_allowance( - deps: Deps, - address: String, -) -> StdResult { +pub fn query_mint_allowance(deps: Deps, address: String) -> StdResult { let allowance = MINTER_ALLOWANCES .may_load(deps.storage, &deps.api.addr_validate(&address)?)? .unwrap_or_else(Uint128::zero); Ok(AllowanceResponse { allowance }) } -pub fn query_burn_allowance( - deps: Deps, - address: String, -) -> StdResult { +pub fn query_burn_allowance(deps: Deps, address: String) -> StdResult { let allowance = BURNER_ALLOWANCES .may_load(deps.storage, &deps.api.addr_validate(&address)?)? .unwrap_or_else(Uint128::zero); @@ -53,7 +46,7 @@ pub fn query_burn_allowance( } pub fn query_allowances( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, allowances: Map<&Addr, Uint128>, @@ -84,7 +77,7 @@ pub fn query_allowances( } pub fn query_mint_allowances( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { @@ -94,7 +87,7 @@ pub fn query_mint_allowances( } pub fn query_burn_allowances( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { @@ -103,32 +96,26 @@ pub fn query_burn_allowances( }) } -pub fn query_is_denied( - deps: Deps, - address: String, -) -> StdResult { +pub fn query_is_denied(deps: Deps, address: String) -> StdResult { let status = DENYLIST .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } -pub fn query_is_allowed( - deps: Deps, - address: String, -) -> StdResult { +pub fn query_is_allowed(deps: Deps, address: String) -> StdResult { let status = ALLOWLIST .load(deps.storage, &deps.api.addr_validate(&address)?) .unwrap_or(false); Ok(StatusResponse { status }) } -pub fn query_before_send_hook_features(deps: Deps) -> StdResult { +pub fn query_before_send_hook_features(deps: Deps) -> StdResult { BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage) } pub fn query_status_map( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, map: Map<&Addr, bool>, @@ -156,7 +143,7 @@ pub fn query_status_map( } pub fn query_allowlist( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { @@ -166,7 +153,7 @@ pub fn query_allowlist( } pub fn query_denylist( - deps: Deps, + deps: Deps, start_after: Option, limit: Option, ) -> StdResult { diff --git a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml index be76ba3e1..131a716dc 100644 --- a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml @@ -48,4 +48,3 @@ dao-testing = { workspace = true, features = ["test-tube"] } osmosis-std = { workpsace = true } osmosis-test-tube = { workspace = true } serde = { workspace = true } -token-bindings-test = { workspace = true } diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs index dd0bf890a..d6b447afb 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs @@ -1,1365 +1,2 @@ -use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; -use crate::msg::{ - ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, - StakerBalanceResponse, TokenInfo, -}; -use crate::state::Config; -use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; -use cw_controllers::ClaimsResponse; -use cw_multi_test::{ - next_block, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, -}; -use cw_utils::Duration; -use dao_interface::voting::{ - InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, -}; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; -use token_bindings_test::TokenFactoryApp as App; - -const DAO_ADDR: &str = "dao"; -const ADDR1: &str = "addr1"; -const ADDR2: &str = "addr2"; -const DENOM: &str = "ujuno"; -const INVALID_DENOM: &str = "uinvalid"; -const ODD_DENOM: &str = "uodd"; - -fn issuer_contract() -> Box> { - let contract = ContractWrapper::new( - cw_tokenfactory_issuer::contract::execute, - cw_tokenfactory_issuer::contract::instantiate, - cw_tokenfactory_issuer::contract::query, - ) - .with_reply(cw_tokenfactory_issuer::contract::reply); - Box::new(contract) -} - -fn staking_contract() -> Box> { - let contract = ContractWrapper::new_with_empty( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply_empty(crate::contract::reply) - .with_migrate_empty(crate::contract::migrate); - Box::new(contract) -} - -fn mock_app() -> App { - let mut app = App::new(); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: DAO_ADDR.to_string(), - amount: vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - })) - .unwrap(); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: ADDR1.to_string(), - amount: vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: ODD_DENOM.to_string(), - amount: Uint128::new(5), - }, - ], - })) - .unwrap(); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: ADDR2.to_string(), - amount: vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - })) - .unwrap(); - app -} - -fn instantiate_staking(app: &mut App, staking_id: u64, msg: InstantiateMsg) -> Addr { - app.instantiate_contract( - staking_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "Staking", - None, - ) - .unwrap() -} - -fn stake_tokens( - app: &mut App, - staking_addr: Addr, - sender: &str, - amount: u128, - denom: &str, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Stake {}, - &coins(amount, denom), - ) -} - -fn unstake_tokens( - app: &mut App, - staking_addr: Addr, - sender: &str, - amount: u128, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Unstake { - amount: Uint128::new(amount), - }, - &[], - ) -} - -fn claim(app: &mut App, staking_addr: Addr, sender: &str) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Claim {}, - &[], - ) -} - -fn update_config( - app: &mut App, - staking_addr: Addr, - sender: &str, - duration: Option, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::UpdateConfig { duration }, - &[], - ) -} - -fn get_voting_power_at_height( - app: &mut App, - staking_addr: Addr, - address: String, - height: Option, -) -> VotingPowerAtHeightResponse { - app.wrap() - .query_wasm_smart( - staking_addr, - &QueryMsg::VotingPowerAtHeight { address, height }, - ) - .unwrap() -} - -fn get_total_power_at_height( - app: &mut App, - staking_addr: Addr, - height: Option, -) -> TotalPowerAtHeightResponse { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::TotalPowerAtHeight { height }) - .unwrap() -} - -fn get_config(app: &mut App, staking_addr: Addr) -> Config { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::GetConfig {}) - .unwrap() -} - -fn get_claims(app: &mut App, staking_addr: Addr, address: String) -> ClaimsResponse { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::Claims { address }) - .unwrap() -} - -fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { - app.wrap().query_balance(address, denom).unwrap().amount -} - -#[test] -fn test_instantiate_existing() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - // Populated fields - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Non populated fields - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: None, - active_threshold: None, - }, - ); - - let token_contract: Addr = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::TokenContract {}) - .unwrap(); - assert_eq!(token_contract, Addr::unchecked("contract3")); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_instantiate_invalid_unstaking_duration_height() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - - // Populated fields - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(0)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(1), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_instantiate_invalid_unstaking_duration_time() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - - // Populated fields with height - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Time(0)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(1), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Must send reserve token 'ujuno'")] -fn test_stake_invalid_denom() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Try and stake an invalid denom - stake_tokens(&mut app, addr, ADDR1, 100, INVALID_DENOM).unwrap(); -} - -#[test] -fn test_stake_valid_denom() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Try and stake an valid denom - stake_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); -} - -#[test] -#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] -fn test_unstake_none_staked() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - unstake_tokens(&mut app, addr, ADDR1, 100).unwrap(); -} - -#[test] -#[should_panic(expected = "Amount being unstaked must be non-zero")] -fn test_unstake_zero_tokens() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - unstake_tokens(&mut app, addr, ADDR1, 0).unwrap(); -} - -#[test] -#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] -fn test_unstake_invalid_balance() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Try and unstake too many - unstake_tokens(&mut app, addr, ADDR1, 200).unwrap(); -} - -#[test] -fn test_unstake() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - - // Query claims - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 1); - app.update_block(next_block); - - // Unstake the rest - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - - // Query claims - let claims = get_claims(&mut app, addr, ADDR1.to_string()); - assert_eq!(claims.claims.len(), 2); -} - -#[test] -fn test_unstake_no_unstaking_duration() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: None, - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some tokens - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - - app.update_block(next_block); - - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 - assert_eq!(balance, Uint128::new(9975)); - - // Unstake the rest - unstake_tokens(&mut app, addr, ADDR1, 25).unwrap(); - - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 - assert_eq!(balance, Uint128::new(10000)) -} - -#[test] -#[should_panic(expected = "Nothing to claim")] -fn test_claim_no_claims() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - claim(&mut app, addr, ADDR1).unwrap(); -} - -#[test] -#[should_panic(expected = "Nothing to claim")] -fn test_claim_claim_not_reached() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake them to create the claims - unstake_tokens(&mut app, addr.clone(), ADDR1, 100).unwrap(); - app.update_block(next_block); - - // We have a claim but it isnt reached yet so this will still fail - claim(&mut app, addr, ADDR1).unwrap(); -} - -#[test] -fn test_claim() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some to create the claims - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - app.update_block(|b| { - b.height += 5; - b.time = b.time.plus_seconds(25); - }); - - // Claim - claim(&mut app, addr.clone(), ADDR1).unwrap(); - - // Query balance - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 - assert_eq!(balance, Uint128::new(9975)); - - // Unstake the rest - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(|b| { - b.height += 10; - b.time = b.time.plus_seconds(50); - }); - - // Claim - claim(&mut app, addr, ADDR1).unwrap(); - - // Query balance - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 - assert_eq!(balance, Uint128::new(10000)); -} - -#[test] -#[should_panic(expected = "Unauthorized")] -fn test_update_config_invalid_sender() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // From ADDR2, so not owner or manager - update_config(&mut app, addr, ADDR2, Some(Duration::Height(10))).unwrap(); -} - -#[test] -fn test_update_config_as_owner() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Swap owner and manager, change duration - update_config(&mut app, addr.clone(), DAO_ADDR, Some(Duration::Height(10))).unwrap(); - - let config = get_config(&mut app, addr); - assert_eq!( - Config { - unstaking_duration: Some(Duration::Height(10)), - }, - config - ); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_update_config_invalid_duration() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Change duration and manager as manager cannot change owner - update_config(&mut app, addr, DAO_ADDR, Some(Duration::Height(0))).unwrap(); -} - -#[test] -fn test_query_dao() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::Dao {}; - let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(dao, Addr::unchecked(DAO_ADDR)); -} - -#[test] -fn test_query_info() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::Info {}; - let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!( - resp.info.contract, - "crates.io:dao-voting-token-factory-staked" - ); -} - -#[test] -fn test_query_token_contract() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::TokenContract {}; - let res: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(res, Addr::unchecked("contract1")); -} - -#[test] -fn test_query_claims() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 0); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some tokens - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(next_block); - - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 1); - - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(next_block); - - let claims = get_claims(&mut app, addr, ADDR1.to_string()); - assert_eq!(claims.claims.len(), 2); -} - -#[test] -fn test_query_get_config() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let config = get_config(&mut app, addr); - assert_eq!( - config, - Config { - unstaking_duration: Some(Duration::Height(5)), - } - ) -} - -#[test] -fn test_voting_power_queries() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Total power is 0 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert!(resp.power.is_zero()); - - // ADDR1 has no power, none staked - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert!(resp.power.is_zero()); - - // ADDR1 stakes - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 100 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); - assert!(resp.power.is_zero()); - - // ADDR2 stakes - stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); - app.update_block(next_block); - let prev_height = app.block_info().height - 1; - - // Query the previous height, total 100, ADDR1 100, ADDR2 0 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 100 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); - assert!(resp.power.is_zero()); - - // For current height, total 150, ADDR1 100, ADDR2 50 - // Total power is 150 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(150)); - - // ADDR1 has 100 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 now has 50 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); - - // ADDR1 unstakes half - unstake_tokens(&mut app, addr.clone(), ADDR1, 50).unwrap(); - app.update_block(next_block); - let prev_height = app.block_info().height - 1; - - // Query the previous height, total 150, ADDR1 100, ADDR2 50 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(150)); - - // ADDR1 has 100 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(50)); - - // For current height, total 100, ADDR1 50, ADDR2 50 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 50 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); - - // ADDR2 now has 50 power - let resp = get_voting_power_at_height(&mut app, addr, ADDR2.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); -} - -#[test] -fn test_query_list_stakers() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // ADDR1 stakes - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - - // ADDR2 stakes - stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); - - // check entire result set - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr.clone(), - &QueryMsg::ListStakers { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let test_res = ListStakersResponse { - stakers: vec![ - StakerBalanceResponse { - address: ADDR1.to_string(), - balance: Uint128::new(100), - }, - StakerBalanceResponse { - address: ADDR2.to_string(), - balance: Uint128::new(50), - }, - ], - }; - - assert_eq!(stakers, test_res); - - // skipped 1, check result - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr.clone(), - &QueryMsg::ListStakers { - start_after: Some(ADDR1.to_string()), - limit: None, - }, - ) - .unwrap(); - - let test_res = ListStakersResponse { - stakers: vec![StakerBalanceResponse { - address: ADDR2.to_string(), - balance: Uint128::new(50), - }], - }; - - assert_eq!(stakers, test_res); - - // skipped 2, check result. should be nothing - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr, - &QueryMsg::ListStakers { - start_after: Some(ADDR2.to_string()), - limit: None, - }, - ) - .unwrap(); - - assert_eq!(stakers, ListStakersResponse { stakers: vec![] }); -} - -#[test] -#[should_panic(expected = "Active threshold count must be greater than zero")] -fn test_instantiate_zero_active_threshold_count() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::zero(), - }), - }, - ); -} - -#[test] -fn test_active_threshold_absolute_count() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 100 tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Active as enough staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_percent() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(20), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 6000 tokens, now active - stake_tokens(&mut app, addr.clone(), ADDR1, 6000, DENOM).unwrap(); - app.update_block(next_block); - - // Active as enough staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_percent_rounds_up() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: ODD_DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(50), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 2 tokens, should not be active. - stake_tokens(&mut app, addr.clone(), ADDR1, 2, ODD_DENOM).unwrap(); - app.update_block(next_block); - - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 1 more token, should now be active. - stake_tokens(&mut app, addr.clone(), ADDR1, 1, ODD_DENOM).unwrap(); - app.update_block(next_block); - - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_none() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Active as no threshold - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_update_active_threshold() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!(resp.active_threshold, None); - - let msg = ExecuteMsg::UpdateActiveThreshold { - new_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - }; - - // Expect failure as sender is not the DAO - app.execute_contract(Addr::unchecked(ADDR1), addr.clone(), &msg, &[]) - .unwrap_err(); - - // Expect success as sender is the DAO - app.execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) - .unwrap(); - - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!( - resp.active_threshold, - Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100) - }) - ); -} - -#[test] -#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] -fn test_active_threshold_percentage_gt_100() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(120), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] -fn test_active_threshold_percentage_lte_0() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(0), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Absolute count threshold cannot be greater than the total token supply")] -fn test_active_threshold_absolute_count_invalid() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(30001), - }), - }, - ); -} - -#[test] -fn test_add_remove_hooks() { - let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // No hooks exist. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, Vec::::new()); - - // Add a hook. - app.execute_contract( - Addr::unchecked(DAO_ADDR), - addr.clone(), - &ExecuteMsg::AddHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap(); - - // One hook exists. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, vec!["hook".to_string()]); - - // Remove hook. - app.execute_contract( - Addr::unchecked(DAO_ADDR), - addr.clone(), - &ExecuteMsg::RemoveHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap(); - - // No hook exists. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, Vec::::new()); -} - -#[test] -pub fn test_migrate_update_version() { - let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); - migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); - let version = cw2::get_contract_version(&deps.storage).unwrap(); - assert_eq!(version.version, CONTRACT_VERSION); - assert_eq!(version.contract, CONTRACT_NAME); -} +mod tests; +mod tf_module_mock; diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs new file mode 100644 index 000000000..b5f9671ea --- /dev/null +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs @@ -0,0 +1,1366 @@ +use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; +use crate::msg::{ + ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, + StakerBalanceResponse, TokenInfo, +}; +use crate::state::Config; +use cosmwasm_std::testing::{mock_dependencies, mock_env}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; +use cw_controllers::ClaimsResponse; +use cw_multi_test::{ + next_block, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, +}; +use cw_utils::Duration; +use dao_interface::voting::{ + InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; +use token_bindings::TokenFactoryMsg; + +use super::tf_module_mock::TokenFactoryApp as App; + +const DAO_ADDR: &str = "dao"; +const ADDR1: &str = "addr1"; +const ADDR2: &str = "addr2"; +const DENOM: &str = "ujuno"; +const INVALID_DENOM: &str = "uinvalid"; +const ODD_DENOM: &str = "uodd"; + +fn issuer_contract() -> Box> { + let contract = ContractWrapper::new( + cw_tokenfactory_issuer::contract::execute, + cw_tokenfactory_issuer::contract::instantiate, + cw_tokenfactory_issuer::contract::query, + ) + .with_reply(cw_tokenfactory_issuer::contract::reply); + Box::new(contract) +} + +fn staking_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ) + .with_reply_empty(crate::contract::reply) + .with_migrate_empty(crate::contract::migrate); + Box::new(contract) +} + +fn mock_app() -> App { + let mut app = App::new(); + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: DAO_ADDR.to_string(), + amount: vec![ + Coin { + denom: DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: Uint128::new(10000), + }, + ], + })) + .unwrap(); + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: ADDR1.to_string(), + amount: vec![ + Coin { + denom: DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: ODD_DENOM.to_string(), + amount: Uint128::new(5), + }, + ], + })) + .unwrap(); + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: ADDR2.to_string(), + amount: vec![ + Coin { + denom: DENOM.to_string(), + amount: Uint128::new(10000), + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: Uint128::new(10000), + }, + ], + })) + .unwrap(); + app +} + +fn instantiate_staking(app: &mut App, staking_id: u64, msg: InstantiateMsg) -> Addr { + app.instantiate_contract( + staking_id, + Addr::unchecked(DAO_ADDR), + &msg, + &[], + "Staking", + None, + ) + .unwrap() +} + +fn stake_tokens( + app: &mut App, + staking_addr: Addr, + sender: &str, + amount: u128, + denom: &str, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + staking_addr, + &ExecuteMsg::Stake {}, + &coins(amount, denom), + ) +} + +fn unstake_tokens( + app: &mut App, + staking_addr: Addr, + sender: &str, + amount: u128, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + staking_addr, + &ExecuteMsg::Unstake { + amount: Uint128::new(amount), + }, + &[], + ) +} + +fn claim(app: &mut App, staking_addr: Addr, sender: &str) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + staking_addr, + &ExecuteMsg::Claim {}, + &[], + ) +} + +fn update_config( + app: &mut App, + staking_addr: Addr, + sender: &str, + duration: Option, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + staking_addr, + &ExecuteMsg::UpdateConfig { duration }, + &[], + ) +} + +fn get_voting_power_at_height( + app: &mut App, + staking_addr: Addr, + address: String, + height: Option, +) -> VotingPowerAtHeightResponse { + app.wrap() + .query_wasm_smart( + staking_addr, + &QueryMsg::VotingPowerAtHeight { address, height }, + ) + .unwrap() +} + +fn get_total_power_at_height( + app: &mut App, + staking_addr: Addr, + height: Option, +) -> TotalPowerAtHeightResponse { + app.wrap() + .query_wasm_smart(staking_addr, &QueryMsg::TotalPowerAtHeight { height }) + .unwrap() +} + +fn get_config(app: &mut App, staking_addr: Addr) -> Config { + app.wrap() + .query_wasm_smart(staking_addr, &QueryMsg::GetConfig {}) + .unwrap() +} + +fn get_claims(app: &mut App, staking_addr: Addr, address: String) -> ClaimsResponse { + app.wrap() + .query_wasm_smart(staking_addr, &QueryMsg::Claims { address }) + .unwrap() +} + +fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { + app.wrap().query_balance(address, denom).unwrap().amount +} + +#[test] +fn test_instantiate_existing() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + // Populated fields + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Non populated fields + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: None, + active_threshold: None, + }, + ); + + let token_contract: Addr = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::TokenContract {}) + .unwrap(); + assert_eq!(token_contract, Addr::unchecked("contract3")); +} + +#[test] +#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] +fn test_instantiate_invalid_unstaking_duration_height() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + + // Populated fields + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(0)), + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(1), + }), + }, + ); +} + +#[test] +#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] +fn test_instantiate_invalid_unstaking_duration_time() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + + // Populated fields with height + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Time(0)), + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(1), + }), + }, + ); +} + +#[test] +#[should_panic(expected = "Must send reserve token 'ujuno'")] +fn test_stake_invalid_denom() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Try and stake an invalid denom + stake_tokens(&mut app, addr, ADDR1, 100, INVALID_DENOM).unwrap(); +} + +#[test] +fn test_stake_valid_denom() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Try and stake an valid denom + stake_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); +} + +#[test] +#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] +fn test_unstake_none_staked() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + unstake_tokens(&mut app, addr, ADDR1, 100).unwrap(); +} + +#[test] +#[should_panic(expected = "Amount being unstaked must be non-zero")] +fn test_unstake_zero_tokens() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + unstake_tokens(&mut app, addr, ADDR1, 0).unwrap(); +} + +#[test] +#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] +fn test_unstake_invalid_balance() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Stake some tokens + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Try and unstake too many + unstake_tokens(&mut app, addr, ADDR1, 200).unwrap(); +} + +#[test] +fn test_unstake() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Stake some tokens + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Unstake some + unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); + + // Query claims + let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); + assert_eq!(claims.claims.len(), 1); + app.update_block(next_block); + + // Unstake the rest + unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); + + // Query claims + let claims = get_claims(&mut app, addr, ADDR1.to_string()); + assert_eq!(claims.claims.len(), 2); +} + +#[test] +fn test_unstake_no_unstaking_duration() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: None, + active_threshold: None, + }, + ); + + // Stake some tokens + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Unstake some tokens + unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); + + app.update_block(next_block); + + let balance = get_balance(&mut app, ADDR1, DENOM); + // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 + assert_eq!(balance, Uint128::new(9975)); + + // Unstake the rest + unstake_tokens(&mut app, addr, ADDR1, 25).unwrap(); + + let balance = get_balance(&mut app, ADDR1, DENOM); + // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 + assert_eq!(balance, Uint128::new(10000)) +} + +#[test] +#[should_panic(expected = "Nothing to claim")] +fn test_claim_no_claims() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + claim(&mut app, addr, ADDR1).unwrap(); +} + +#[test] +#[should_panic(expected = "Nothing to claim")] +fn test_claim_claim_not_reached() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Stake some tokens + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Unstake them to create the claims + unstake_tokens(&mut app, addr.clone(), ADDR1, 100).unwrap(); + app.update_block(next_block); + + // We have a claim but it isnt reached yet so this will still fail + claim(&mut app, addr, ADDR1).unwrap(); +} + +#[test] +fn test_claim() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Stake some tokens + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Unstake some to create the claims + unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); + app.update_block(|b| { + b.height += 5; + b.time = b.time.plus_seconds(25); + }); + + // Claim + claim(&mut app, addr.clone(), ADDR1).unwrap(); + + // Query balance + let balance = get_balance(&mut app, ADDR1, DENOM); + // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 + assert_eq!(balance, Uint128::new(9975)); + + // Unstake the rest + unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); + app.update_block(|b| { + b.height += 10; + b.time = b.time.plus_seconds(50); + }); + + // Claim + claim(&mut app, addr, ADDR1).unwrap(); + + // Query balance + let balance = get_balance(&mut app, ADDR1, DENOM); + // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 + assert_eq!(balance, Uint128::new(10000)); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_update_config_invalid_sender() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // From ADDR2, so not owner or manager + update_config(&mut app, addr, ADDR2, Some(Duration::Height(10))).unwrap(); +} + +#[test] +fn test_update_config_as_owner() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Swap owner and manager, change duration + update_config(&mut app, addr.clone(), DAO_ADDR, Some(Duration::Height(10))).unwrap(); + + let config = get_config(&mut app, addr); + assert_eq!( + Config { + unstaking_duration: Some(Duration::Height(10)), + }, + config + ); +} + +#[test] +#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] +fn test_update_config_invalid_duration() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Change duration and manager as manager cannot change owner + update_config(&mut app, addr, DAO_ADDR, Some(Duration::Height(0))).unwrap(); +} + +#[test] +fn test_query_dao() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + let msg = QueryMsg::Dao {}; + let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(dao, Addr::unchecked(DAO_ADDR)); +} + +#[test] +fn test_query_info() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + let msg = QueryMsg::Info {}; + let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!( + resp.info.contract, + "crates.io:dao-voting-token-factory-staked" + ); +} + +#[test] +fn test_query_token_contract() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + let msg = QueryMsg::TokenContract {}; + let res: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(res, Addr::unchecked("contract1")); +} + +#[test] +fn test_query_claims() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); + assert_eq!(claims.claims.len(), 0); + + // Stake some tokens + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Unstake some tokens + unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); + app.update_block(next_block); + + let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); + assert_eq!(claims.claims.len(), 1); + + unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); + app.update_block(next_block); + + let claims = get_claims(&mut app, addr, ADDR1.to_string()); + assert_eq!(claims.claims.len(), 2); +} + +#[test] +fn test_query_get_config() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + let config = get_config(&mut app, addr); + assert_eq!( + config, + Config { + unstaking_duration: Some(Duration::Height(5)), + } + ) +} + +#[test] +fn test_voting_power_queries() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Total power is 0 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert!(resp.power.is_zero()); + + // ADDR1 has no power, none staked + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert!(resp.power.is_zero()); + + // ADDR1 stakes + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR1 has 100 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 still has 0 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); + assert!(resp.power.is_zero()); + + // ADDR2 stakes + stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); + app.update_block(next_block); + let prev_height = app.block_info().height - 1; + + // Query the previous height, total 100, ADDR1 100, ADDR2 0 + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR1 has 100 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 still has 0 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); + assert!(resp.power.is_zero()); + + // For current height, total 150, ADDR1 100, ADDR2 50 + // Total power is 150 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert_eq!(resp.power, Uint128::new(150)); + + // ADDR1 has 100 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 now has 50 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); + assert_eq!(resp.power, Uint128::new(50)); + + // ADDR1 unstakes half + unstake_tokens(&mut app, addr.clone(), ADDR1, 50).unwrap(); + app.update_block(next_block); + let prev_height = app.block_info().height - 1; + + // Query the previous height, total 150, ADDR1 100, ADDR2 50 + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(150)); + + // ADDR1 has 100 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 still has 0 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(50)); + + // For current height, total 100, ADDR1 50, ADDR2 50 + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR1 has 50 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert_eq!(resp.power, Uint128::new(50)); + + // ADDR2 now has 50 power + let resp = get_voting_power_at_height(&mut app, addr, ADDR2.to_string(), None); + assert_eq!(resp.power, Uint128::new(50)); +} + +#[test] +fn test_query_list_stakers() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // ADDR1 stakes + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + + // ADDR2 stakes + stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); + + // check entire result set + let stakers: ListStakersResponse = app + .wrap() + .query_wasm_smart( + addr.clone(), + &QueryMsg::ListStakers { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let test_res = ListStakersResponse { + stakers: vec![ + StakerBalanceResponse { + address: ADDR1.to_string(), + balance: Uint128::new(100), + }, + StakerBalanceResponse { + address: ADDR2.to_string(), + balance: Uint128::new(50), + }, + ], + }; + + assert_eq!(stakers, test_res); + + // skipped 1, check result + let stakers: ListStakersResponse = app + .wrap() + .query_wasm_smart( + addr.clone(), + &QueryMsg::ListStakers { + start_after: Some(ADDR1.to_string()), + limit: None, + }, + ) + .unwrap(); + + let test_res = ListStakersResponse { + stakers: vec![StakerBalanceResponse { + address: ADDR2.to_string(), + balance: Uint128::new(50), + }], + }; + + assert_eq!(stakers, test_res); + + // skipped 2, check result. should be nothing + let stakers: ListStakersResponse = app + .wrap() + .query_wasm_smart( + addr, + &QueryMsg::ListStakers { + start_after: Some(ADDR2.to_string()), + limit: None, + }, + ) + .unwrap(); + + assert_eq!(stakers, ListStakersResponse { stakers: vec![] }); +} + +#[test] +#[should_panic(expected = "Active threshold count must be greater than zero")] +fn test_instantiate_zero_active_threshold_count() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::zero(), + }), + }, + ); +} + +#[test] +fn test_active_threshold_absolute_count() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(100), + }), + }, + ); + + // Not active as none staked + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) + .unwrap(); + assert!(!is_active.active); + + // Stake 100 tokens + stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Active as enough staked + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::IsActive {}) + .unwrap(); + assert!(is_active.active); +} + +#[test] +fn test_active_threshold_percent() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: Some(ActiveThreshold::Percentage { + percent: Decimal::percent(20), + }), + }, + ); + + // Not active as none staked + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) + .unwrap(); + assert!(!is_active.active); + + // Stake 6000 tokens, now active + stake_tokens(&mut app, addr.clone(), ADDR1, 6000, DENOM).unwrap(); + app.update_block(next_block); + + // Active as enough staked + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::IsActive {}) + .unwrap(); + assert!(is_active.active); +} + +#[test] +fn test_active_threshold_percent_rounds_up() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: ODD_DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: Some(ActiveThreshold::Percentage { + percent: Decimal::percent(50), + }), + }, + ); + + // Not active as none staked + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) + .unwrap(); + assert!(!is_active.active); + + // Stake 2 tokens, should not be active. + stake_tokens(&mut app, addr.clone(), ADDR1, 2, ODD_DENOM).unwrap(); + app.update_block(next_block); + + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) + .unwrap(); + assert!(!is_active.active); + + // Stake 1 more token, should now be active. + stake_tokens(&mut app, addr.clone(), ADDR1, 1, ODD_DENOM).unwrap(); + app.update_block(next_block); + + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::IsActive {}) + .unwrap(); + assert!(is_active.active); +} + +#[test] +fn test_active_threshold_none() { + let mut app = App::default(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Active as no threshold + let is_active: IsActiveResponse = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::IsActive {}) + .unwrap(); + assert!(is_active.active); +} + +#[test] +fn test_update_active_threshold() { + let mut app = mock_app(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + let resp: ActiveThresholdResponse = app + .wrap() + .query_wasm_smart(addr.clone(), &QueryMsg::ActiveThreshold {}) + .unwrap(); + assert_eq!(resp.active_threshold, None); + + let msg = ExecuteMsg::UpdateActiveThreshold { + new_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(100), + }), + }; + + // Expect failure as sender is not the DAO + app.execute_contract(Addr::unchecked(ADDR1), addr.clone(), &msg, &[]) + .unwrap_err(); + + // Expect success as sender is the DAO + app.execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) + .unwrap(); + + let resp: ActiveThresholdResponse = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::ActiveThreshold {}) + .unwrap(); + assert_eq!( + resp.active_threshold, + Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(100) + }) + ); +} + +#[test] +#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] +fn test_active_threshold_percentage_gt_100() { + let mut app = App::default(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: Some(ActiveThreshold::Percentage { + percent: Decimal::percent(120), + }), + }, + ); +} + +#[test] +#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] +fn test_active_threshold_percentage_lte_0() { + let mut app = App::default(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: Some(ActiveThreshold::Percentage { + percent: Decimal::percent(0), + }), + }, + ); +} + +#[test] +#[should_panic(expected = "Absolute count threshold cannot be greater than the total token supply")] +fn test_active_threshold_absolute_count_invalid() { + let mut app = App::default(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(30001), + }), + }, + ); +} + +#[test] +fn test_add_remove_hooks() { + let mut app = App::default(); + let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // No hooks exist. + let resp: GetHooksResponse = app + .wrap() + .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) + .unwrap(); + assert_eq!(resp.hooks, Vec::::new()); + + // Add a hook. + app.execute_contract( + Addr::unchecked(DAO_ADDR), + addr.clone(), + &ExecuteMsg::AddHook { + addr: "hook".to_string(), + }, + &[], + ) + .unwrap(); + + // One hook exists. + let resp: GetHooksResponse = app + .wrap() + .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) + .unwrap(); + assert_eq!(resp.hooks, vec!["hook".to_string()]); + + // Remove hook. + app.execute_contract( + Addr::unchecked(DAO_ADDR), + addr.clone(), + &ExecuteMsg::RemoveHook { + addr: "hook".to_string(), + }, + &[], + ) + .unwrap(); + + // No hook exists. + let resp: GetHooksResponse = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::GetHooks {}) + .unwrap(); + assert_eq!(resp.hooks, Vec::::new()); +} + +#[test] +pub fn test_migrate_update_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); + let version = cw2::get_contract_version(&deps.storage).unwrap(); + assert_eq!(version.version, CONTRACT_VERSION); + assert_eq!(version.contract, CONTRACT_NAME); +} diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs new file mode 100644 index 000000000..33e0e1dc9 --- /dev/null +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs @@ -0,0 +1,108 @@ +use anyhow::{bail, Result as AnyResult}; +use cosmwasm_schema::schemars::JsonSchema; +use cosmwasm_std::testing::{MockApi, MockStorage}; +use cosmwasm_std::{ + Addr, Api, Binary, BlockInfo, CustomQuery, Empty, Querier, QuerierResult, StdError, Storage, +}; +use cw_multi_test::{ + App, AppResponse, BankKeeper, BasicAppBuilder, CosmosRouter, Module, WasmKeeper, +}; +use serde::de::DeserializeOwned; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut}; +use token_bindings::TokenFactoryMsg; + +// A Mock cw-multi-test module for token factory +pub struct TokenFactoryModule {} + +impl Module for TokenFactoryModule { + type ExecT = TokenFactoryMsg; + type QueryT = Empty; + type SudoT = Empty; + + // Builds a mock rust implementation of the expected Token Factory functionality for testing + fn execute( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _sender: Addr, + msg: Self::ExecT, + ) -> AnyResult + where + ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + match msg { + _ => bail!("execute not implemented for TokenFactoryModule"), + } + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + bail!("sudo not implemented for TokenFactoryModule") + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + _request: Self::QueryT, + ) -> anyhow::Result { + bail!("query not implemented for TokenFactoryModule") + } +} + +pub type TokenFactoryAppWrapped = + App>; + +pub struct TokenFactoryApp(TokenFactoryAppWrapped); + +impl Deref for TokenFactoryApp { + type Target = TokenFactoryAppWrapped; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for TokenFactoryApp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Querier for TokenFactoryApp { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + self.0.raw_query(bin_request) + } +} + +impl Default for TokenFactoryApp { + fn default() -> Self { + Self::new() + } +} + +impl TokenFactoryApp { + pub fn new() -> Self { + Self( + BasicAppBuilder::::new_custom() + .with_custom(TokenFactoryModule {}) + .build(|_router, _, _storage| {}), + ) + } +} From f20b1cb7a0145ed8891eb9c6cbf6d4d65d736127 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 19:20:44 -0700 Subject: [PATCH 27/59] Fix stake hook tests, update schema --- Cargo.lock | 2 + Cargo.toml | 1 + .../schema/cw-tokenfactory-issuer.json | 1017 +++++++++++++++++ .../schema/dao-voting-cw721-staked.json | 2 +- .../dao-voting-native-staked/Cargo.toml | 3 +- .../dao-voting-native-staked/src/tests.rs | 31 +- .../Cargo.toml | 1 + .../src/tests/multitest/tests.rs | 71 +- .../src/tests/multitest/tf_module_mock.rs | 2 +- .../dao-proposal-hook-counter/src/contract.rs | 15 +- .../dao-proposal-hook-counter/src/msg.rs | 5 +- .../dao-proposal-hook-counter/src/state.rs | 3 +- 12 files changed, 1135 insertions(+), 18 deletions(-) create mode 100644 contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json diff --git a/Cargo.lock b/Cargo.lock index 422e229ec..2a477d833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2140,6 +2140,7 @@ dependencies = [ "dao-dao-macros", "dao-hooks", "dao-interface", + "dao-proposal-hook-counter", "dao-voting 2.2.0", "thiserror", ] @@ -2163,6 +2164,7 @@ dependencies = [ "dao-dao-macros", "dao-hooks", "dao-interface", + "dao-proposal-hook-counter", "dao-testing", "dao-voting 2.2.0", "osmosis-std 0.17.0-rc0", diff --git a/Cargo.toml b/Cargo.toml index c3185f336..b85fed60b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ dao-proposal-condorcet = { path = "./contracts/proposal/dao-proposal-condorcet", dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", version = "2.2.0" } dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.2.0" } dao-proposal-sudo = { path = "./test-contracts/dao-proposal-sudo", version = "2.2.0" } +dao-proposal-hook-counter = { path = "./test-contracts/dao-proposal-hook-counter", version = "2.2.0" } dao-testing = { path = "./packages/dao-testing", version = "2.2.0" } dao-voting = { path = "./packages/dao-voting", version = "2.2.0" } dao-voting-cw20-balance = { path = "./test-contracts/dao-voting-cw20-balance", version = "2.2.0" } diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json new file mode 100644 index 000000000..1b92dc669 --- /dev/null +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -0,0 +1,1017 @@ +{ + "contract_name": "cw-tokenfactory-issuer", + "contract_version": "2.2.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "oneOf": [ + { + "description": "`NewToken` will create a new token when instantiate the contract. Newly created token will have full denom as `factory//`. It will be attached to the contract setup the beforesend listener automatically.", + "type": "object", + "required": [ + "new_token" + ], + "properties": { + "new_token": { + "type": "object", + "required": [ + "subdenom" + ], + "properties": { + "subdenom": { + "description": "component of fulldenom (`factory//`).", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`ExistingToken` will use already created token. So to set this up, Token Factory admin for the existing token needs trasfer admin over to this contract, and optionally set the `BeforeSendHook` manually.", + "type": "object", + "required": [ + "existing_token" + ], + "properties": { + "existing_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Allow adds the target address to the allowlist to be able to send tokens even if the token is frozen.", + "type": "object", + "required": [ + "allow" + ], + "properties": { + "allow": { + "type": "object", + "required": [ + "address", + "status" + ], + "properties": { + "address": { + "type": "string" + }, + "status": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Burn token to address. Burn allowance is required and wiil be deducted after successful burn.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount", + "from_address" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Mint token to address. Mint allowance is required and wiil be deducted after successful mint.", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "to_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Deny adds the target address to the denylist, whis prevents them from sending/receiving the token attached to this contract tokenfactory's BeforeSendHook listener must be set to this contract in order for this feature to work as intended.", + "type": "object", + "required": [ + "deny" + ], + "properties": { + "deny": { + "type": "object", + "required": [ + "address", + "status" + ], + "properties": { + "address": { + "type": "string" + }, + "status": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Block every token transfers of the token attached to this contract. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature to work as intended.", + "type": "object", + "required": [ + "freeze" + ], + "properties": { + "freeze": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Force transfer token from one address to another.", + "type": "object", + "required": [ + "force_transfer" + ], + "properties": { + "force_transfer": { + "type": "object", + "required": [ + "amount", + "from_address", + "to_address" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from_address": { + "type": "string" + }, + "to_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Attempt to SetBeforeSendHook on the token attached to this contract. This will fail if the token already has a SetBeforeSendHook or the chain still does not support it.", + "type": "object", + "required": [ + "set_before_send_hook" + ], + "properties": { + "set_before_send_hook": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Grant/revoke burn allowance.", + "type": "object", + "required": [ + "set_burner_allowance" + ], + "properties": { + "set_burner_allowance": { + "type": "object", + "required": [ + "address", + "allowance" + ], + "properties": { + "address": { + "type": "string" + }, + "allowance": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Set denom metadata. see: https://docs.cosmos.network/main/modules/bank#denom-metadata.", + "type": "object", + "required": [ + "set_denom_metadata" + ], + "properties": { + "set_denom_metadata": { + "type": "object", + "required": [ + "metadata" + ], + "properties": { + "metadata": { + "$ref": "#/definitions/Metadata" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Grant/revoke mint allowance.", + "type": "object", + "required": [ + "set_minter_allowance" + ], + "properties": { + "set_minter_allowance": { + "type": "object", + "required": [ + "address", + "allowance" + ], + "properties": { + "address": { + "type": "string" + }, + "allowance": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the admin of the Token Factory token. Normally this is the cw-tokenfactory-issuer contract itself. This is intended to be used only if you seek to transfer ownership of the Token somewhere else (i.e. to another management contract).", + "type": "object", + "required": [ + "update_token_factory_admin" + ], + "properties": { + "update_token_factory_admin": { + "type": "object", + "required": [ + "new_admin" + ], + "properties": { + "new_admin": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the owner of this contract who is allowed to call privileged methods. NOTE: this is separate from the Token Factory token admin, for this contract to work at all, it needs to the be the Token Factory token admin.\n\nNormally, the contract owner will be a DAO.", + "type": "object", + "required": [ + "update_contract_owner" + ], + "properties": { + "update_contract_owner": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "DenomUnit": { + "description": "DenomUnit represents a struct that describes a given denomination unit of the basic token.", + "type": "object", + "required": [ + "aliases", + "denom", + "exponent" + ], + "properties": { + "aliases": { + "description": "aliases is a list of string aliases for the given denom", + "type": "array", + "items": { + "type": "string" + } + }, + "denom": { + "description": "denom represents the string name of the given denom unit (e.g uatom).", + "type": "string" + }, + "exponent": { + "description": "exponent represents power of 10 exponent that one must raise the base_denom to in order to equal the given DenomUnit's denom 1 denom = 1^exponent base_denom (e.g. with a base_denom of uatom, one can create a DenomUnit of 'atom' with exponent = 6, thus: 1 atom = 10^6 uatom).", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "Metadata": { + "description": "Metadata represents a struct that describes a basic token.", + "type": "object", + "required": [ + "base", + "denom_units", + "description", + "display", + "name", + "symbol" + ], + "properties": { + "base": { + "description": "base represents the base denom (should be the DenomUnit with exponent = 0).", + "type": "string" + }, + "denom_units": { + "description": "denom_units represents the list of DenomUnit's for a given coin", + "type": "array", + "items": { + "$ref": "#/definitions/DenomUnit" + } + }, + "description": { + "type": "string" + }, + "display": { + "description": "display indicates the suggested denom that should be displayed in clients.", + "type": "string" + }, + "name": { + "description": "name defines the name of the token (eg: Cosmos Atom)\n\nSince: cosmos-sdk 0.43", + "type": "string" + }, + "symbol": { + "description": "symbol is the token symbol usually shown on exchanges (eg: ATOM). This can be the same as the display.\n\nSince: cosmos-sdk 0.43", + "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" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse", + "type": "object", + "required": [ + "is_frozen" + ], + "properties": { + "is_frozen": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Denom returns the token denom that this contract is the admin for. Response: DenomResponse", + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Owner returns the owner of the contract. Response: OwnerResponse", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allowance returns the allowance of the specified address. Response: AllowanceResponse", + "type": "object", + "required": [ + "burn_allowance" + ], + "properties": { + "burn_allowance": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allowances Enumerates over all allownances. Response: Vec", + "type": "object", + "required": [ + "burn_allowances" + ], + "properties": { + "burn_allowances": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allowance returns the allowance of the specified user. Response: AllowanceResponse", + "type": "object", + "required": [ + "mint_allowance" + ], + "properties": { + "mint_allowance": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allowances Enumerates over all allownances. Response: AllowancesResponse", + "type": "object", + "required": [ + "mint_allowances" + ], + "properties": { + "mint_allowances": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "IsDenied returns wether the user is on denylist or not. Response: StatusResponse", + "type": "object", + "required": [ + "is_denied" + ], + "properties": { + "is_denied": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Denylist enumerates over all addresses on the denylist. Response: DenylistResponse", + "type": "object", + "required": [ + "denylist" + ], + "properties": { + "denylist": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "IsAllowed returns wether the user is on the allowlist or not. Response: StatusResponse", + "type": "object", + "required": [ + "is_allowed" + ], + "properties": { + "is_allowed": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allowlist enumerates over all addresses on the allowlist. Response: AllowlistResponse", + "type": "object", + "required": [ + "allowlist" + ], + "properties": { + "allowlist": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns whether features that require MsgBeforeSendHook are enabled Most Cosmos chains do not support this feature yet.", + "type": "object", + "required": [ + "before_send_hook_features_enabled" + ], + "properties": { + "before_send_hook_features_enabled": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SudoMsg", + "description": "SudoMsg is only exposed for internal Cosmos SDK modules to call. This is showing how we can expose \"admin\" functionality than can not be called by external users or contracts, but only trusted (native/Go) code in the blockchain", + "oneOf": [ + { + "type": "object", + "required": [ + "block_before_send" + ], + "properties": { + "block_before_send": { + "type": "object", + "required": [ + "amount", + "from", + "to" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "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" + } + } + }, + "responses": { + "allowlist": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllowlistResponse", + "type": "object", + "required": [ + "allowlist" + ], + "properties": { + "allowlist": { + "type": "array", + "items": { + "$ref": "#/definitions/StatusInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "StatusInfo": { + "type": "object", + "required": [ + "address", + "status" + ], + "properties": { + "address": { + "type": "string" + }, + "status": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "before_send_hook_features_enabled": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" + }, + "burn_allowance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllowanceResponse", + "type": "object", + "required": [ + "allowance" + ], + "properties": { + "allowance": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "burn_allowances": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllowancesResponse", + "type": "object", + "required": [ + "allowances" + ], + "properties": { + "allowances": { + "type": "array", + "items": { + "$ref": "#/definitions/AllowanceInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "AllowanceInfo": { + "type": "object", + "required": [ + "address", + "allowance" + ], + "properties": { + "address": { + "type": "string" + }, + "allowance": { + "$ref": "#/definitions/Uint128" + } + }, + "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" + } + } + }, + "denom": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DenomResponse", + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "denylist": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DenylistResponse", + "type": "object", + "required": [ + "denylist" + ], + "properties": { + "denylist": { + "type": "array", + "items": { + "$ref": "#/definitions/StatusInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "StatusInfo": { + "type": "object", + "required": [ + "address", + "status" + ], + "properties": { + "address": { + "type": "string" + }, + "status": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "is_allowed": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "StatusResponse", + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "is_denied": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "StatusResponse", + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "is_frozen": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsFrozenResponse", + "type": "object", + "required": [ + "is_frozen" + ], + "properties": { + "is_frozen": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "mint_allowance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllowanceResponse", + "type": "object", + "required": [ + "allowance" + ], + "properties": { + "allowance": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "mint_allowances": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllowancesResponse", + "type": "object", + "required": [ + "allowances" + ], + "properties": { + "allowances": { + "type": "array", + "items": { + "$ref": "#/definitions/AllowanceInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "AllowanceInfo": { + "type": "object", + "required": [ + "address", + "allowance" + ], + "properties": { + "address": { + "type": "string" + }, + "allowance": { + "$ref": "#/definitions/Uint128" + } + }, + "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" + } + } + }, + "owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerResponse", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json index 776398bd6..c1e16bcf3 100644 --- a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json +++ b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json @@ -30,7 +30,7 @@ ] }, "owner": { - "description": "May change unstaking duration and add hooks.", + "description": "TODO use cw_ownable May change unstaking duration and add hooks.", "anyOf": [ { "$ref": "#/definitions/Admin" diff --git a/contracts/voting/dao-voting-native-staked/Cargo.toml b/contracts/voting/dao-voting-native-staked/Cargo.toml index 43afb4bd1..25eac3906 100644 --- a/contracts/voting/dao-voting-native-staked/Cargo.toml +++ b/contracts/voting/dao-voting-native-staked/Cargo.toml @@ -33,5 +33,6 @@ dao-voting = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -cw-multi-test = { workspace = true } anyhow = { workspace = true } +cw-multi-test = { workspace = true } +dao-proposal-hook-counter = { workspace = true } diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs index c350af173..b88ec2806 100644 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ b/contracts/voting/dao-voting-native-staked/src/tests.rs @@ -34,6 +34,15 @@ fn staking_contract() -> Box> { Box::new(contract) } +fn hook_counter_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + dao_proposal_hook_counter::contract::execute, + dao_proposal_hook_counter::contract::instantiate, + dao_proposal_hook_counter::contract::query, + ); + Box::new(contract) +} + fn mock_app() -> App { custom_app(|r, _a, s| { r.bank @@ -1284,6 +1293,21 @@ fn test_add_remove_hooks() { fn test_staking_hooks() { let mut app = mock_app(); let staking_id = app.store_code(staking_contract()); + let hook_id = app.store_code(hook_counter_contract()); + + let hook = app + .instantiate_contract( + hook_id, + Addr::unchecked(DAO_ADDR), + &dao_proposal_hook_counter::msg::InstantiateMsg { + should_error: false, + }, + &[], + "hook counter".to_string(), + None, + ) + .unwrap(); + let addr = instantiate_staking( &mut app, staking_id, @@ -1299,18 +1323,17 @@ fn test_staking_hooks() { Addr::unchecked(DAO_ADDR), addr.clone(), &ExecuteMsg::AddHook { - addr: "hook".to_string(), + addr: hook.to_string(), }, &[], ) .unwrap(); - // TODO need a contract to recieve the message // Stake some tokens let res = stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); // Make sure hook is included in response - println!("stake hooks {:?}", res); + assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); app.update_block(next_block); @@ -1318,7 +1341,7 @@ fn test_staking_hooks() { let res = unstake_tokens(&mut app, addr, ADDR1, 75).unwrap(); // Make sure hook is included in response - println!("unstake hooks {:?}", res); + assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); } #[test] diff --git a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml index 131a716dc..ca9e89902 100644 --- a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml @@ -44,6 +44,7 @@ anyhow = { workspace = true } # TODO use upstream when PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 cw-multi-test = { git = "https://github.com/JakeHartnell/cw-multi-test.git", branch = "bank-supply-support" } cw-tokenfactory-issuer = { workspace = true } +dao-proposal-hook-counter = { workspace = true } dao-testing = { workspace = true, features = ["test-tube"] } osmosis-std = { workpsace = true } osmosis-test-tube = { workspace = true } diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs index b5f9671ea..f0091e70f 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs @@ -36,6 +36,15 @@ fn issuer_contract() -> Box> { Box::new(contract) } +fn hook_counter_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + dao_proposal_hook_counter::contract::execute, + dao_proposal_hook_counter::contract::instantiate, + dao_proposal_hook_counter::contract::query, + ); + Box::new(contract) +} + fn staking_contract() -> Box> { let contract = ContractWrapper::new_with_empty( crate::contract::execute, @@ -1298,6 +1307,7 @@ fn test_add_remove_hooks() { let mut app = App::default(); let issuer_id = app.store_code(issuer_contract()); let staking_id = app.store_code(staking_contract()); + let addr = instantiate_staking( &mut app, staking_id, @@ -1355,10 +1365,69 @@ fn test_add_remove_hooks() { assert_eq!(resp.hooks, Vec::::new()); } +#[test] +fn test_staking_hooks() { + let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); + let issuer_id = app.store_code(issuer_contract()); + let hook_id = app.store_code(hook_counter_contract()); + + let hook = app + .instantiate_contract( + hook_id, + Addr::unchecked(DAO_ADDR), + &dao_proposal_hook_counter::msg::InstantiateMsg { + should_error: false, + }, + &[], + "hook counter".to_string(), + None, + ) + .unwrap(); + + let addr = instantiate_staking( + &mut app, + staking_id, + InstantiateMsg { + token_issuer_code_id: issuer_id, + token_info: TokenInfo::Existing { + denom: DENOM.to_string(), + }, + unstaking_duration: Some(Duration::Height(5)), + active_threshold: None, + }, + ); + + // Add a staking hook. + app.execute_contract( + Addr::unchecked(DAO_ADDR), + addr.clone(), + &ExecuteMsg::AddHook { + addr: hook.to_string(), + }, + &[], + ) + .unwrap(); + + // Stake some tokens + let res = stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + + // Make sure hook is included in response + assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); + + app.update_block(next_block); + + // Unstake some + let res = unstake_tokens(&mut app, addr, ADDR1, 75).unwrap(); + + // Make sure hook is included in response + assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); +} + #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs index 33e0e1dc9..8d0c4a135 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result as AnyResult}; use cosmwasm_schema::schemars::JsonSchema; use cosmwasm_std::testing::{MockApi, MockStorage}; use cosmwasm_std::{ - Addr, Api, Binary, BlockInfo, CustomQuery, Empty, Querier, QuerierResult, StdError, Storage, + Addr, Api, Binary, BlockInfo, CustomQuery, Empty, Querier, QuerierResult, Storage, }; use cw_multi_test::{ App, AppResponse, BankKeeper, BasicAppBuilder, CosmosRouter, Module, WasmKeeper, diff --git a/test-contracts/dao-proposal-hook-counter/src/contract.rs b/test-contracts/dao-proposal-hook-counter/src/contract.rs index 0f216972e..be3cddc8d 100644 --- a/test-contracts/dao-proposal-hook-counter/src/contract.rs +++ b/test-contracts/dao-proposal-hook-counter/src/contract.rs @@ -1,6 +1,8 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, +}; use cw2::set_contract_version; use dao_hooks::stake::StakeChangedHookMsg; use dao_hooks::{proposal::ProposalHookMsg, vote::VoteHookMsg}; @@ -28,6 +30,7 @@ pub fn instantiate( }; CONFIG.save(deps.storage, &config)?; PROPOSAL_COUNTER.save(deps.storage, &0)?; + STAKE_COUNTER.save(deps.storage, &Uint128::zero())?; VOTE_COUNTER.save(deps.storage, &0)?; STATUS_CHANGED_COUNTER.save(deps.storage, &0)?; Ok(Response::new().add_attribute("action", "instantiate")) @@ -49,7 +52,7 @@ pub fn execute( ExecuteMsg::ProposalHook(proposal_hook) => { execute_proposal_hook(deps, env, info, proposal_hook) } - ExecuteMsg::StakeHook(stake_hook) => execute_stake_hook(deps, env, info, stake_hook), + ExecuteMsg::StakeChangeHook(stake_hook) => execute_stake_hook(deps, env, info, stake_hook), ExecuteMsg::VoteHook(vote_hook) => execute_vote_hook(deps, env, info, vote_hook), } } @@ -85,12 +88,12 @@ pub fn execute_stake_hook( match stake_hook { StakeChangedHookMsg::Stake { .. } => { let mut count = STAKE_COUNTER.load(deps.storage)?; - count += 1; + count += Uint128::new(1); STAKE_COUNTER.save(deps.storage, &count)?; } StakeChangedHookMsg::Unstake { .. } => { let mut count = STAKE_COUNTER.load(deps.storage)?; - count += 1; + count += Uint128::new(1); STAKE_COUNTER.save(deps.storage, &count)?; } } @@ -121,9 +124,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::ProposalCounter {} => to_binary(&CountResponse { count: PROPOSAL_COUNTER.load(deps.storage)?, }), - QueryMsg::StakeCounter {} => to_binary(&CountResponse { - count: STAKE_COUNTER.load(deps.storage)?, - }), + QueryMsg::StakeCounter {} => to_binary(&STAKE_COUNTER.load(deps.storage)?), QueryMsg::StatusChangedCounter {} => to_binary(&CountResponse { count: STATUS_CHANGED_COUNTER.load(deps.storage)?, }), diff --git a/test-contracts/dao-proposal-hook-counter/src/msg.rs b/test-contracts/dao-proposal-hook-counter/src/msg.rs index 2c4557a33..ad6048228 100644 --- a/test-contracts/dao-proposal-hook-counter/src/msg.rs +++ b/test-contracts/dao-proposal-hook-counter/src/msg.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Uint128; use dao_hooks::{proposal::ProposalHookMsg, stake::StakeChangedHookMsg, vote::VoteHookMsg}; #[cw_serde] @@ -9,14 +10,14 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { ProposalHook(ProposalHookMsg), - StakeHook(StakeChangedHookMsg), + StakeChangeHook(StakeChangedHookMsg), VoteHook(VoteHookMsg), } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(u64)] + #[returns(Uint128)] StakeCounter {}, #[returns(u64)] VoteCounter {}, diff --git a/test-contracts/dao-proposal-hook-counter/src/state.rs b/test-contracts/dao-proposal-hook-counter/src/state.rs index 7f763509b..6bdbdfa23 100644 --- a/test-contracts/dao-proposal-hook-counter/src/state.rs +++ b/test-contracts/dao-proposal-hook-counter/src/state.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; use cw_storage_plus::Item; #[cw_serde] @@ -7,6 +8,6 @@ pub struct Config { } pub const CONFIG: Item = Item::new("config"); pub const PROPOSAL_COUNTER: Item = Item::new("proposal_counter"); -pub const STAKE_COUNTER: Item = Item::new("stake_counter"); +pub const STAKE_COUNTER: Item = Item::new("stake_counter"); pub const STATUS_CHANGED_COUNTER: Item = Item::new("stauts_changed_counter"); pub const VOTE_COUNTER: Item = Item::new("vote_counter"); From dc48b30872eb1036189663a5b27692554def3948 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 22:50:54 -0700 Subject: [PATCH 28/59] Incorporate NFT staking hooks into dao-hooks package --- Cargo.lock | 1 + .../voting/dao-voting-cw721-staked/Cargo.toml | 1 + .../dao-voting-cw721-staked/src/contract.rs | 25 ++- .../dao-voting-cw721-staked/src/error.rs | 2 +- .../dao-voting-cw721-staked/src/hooks.rs | 158 ------------------ .../voting/dao-voting-cw721-staked/src/lib.rs | 1 - .../dao-voting-cw721-staked/src/state.rs | 2 +- .../src/testing/hooks.rs | 107 ++++++++++++ .../src/testing/mod.rs | 1 + packages/dao-hooks/README.md | 5 +- packages/dao-hooks/src/all_hooks.rs | 19 +++ packages/dao-hooks/src/lib.rs | 4 +- packages/dao-hooks/src/nft_stake.rs | 53 ++++++ packages/dao-hooks/src/stake.rs | 3 +- packages/dao-hooks/src/vote.rs | 1 - 15 files changed, 206 insertions(+), 177 deletions(-) delete mode 100644 contracts/voting/dao-voting-cw721-staked/src/hooks.rs create mode 100644 contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs create mode 100644 packages/dao-hooks/src/all_hooks.rs create mode 100644 packages/dao-hooks/src/nft_stake.rs diff --git a/Cargo.lock b/Cargo.lock index 2a477d833..00d2b40c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2102,6 +2102,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.0", + "cw-hooks", "cw-multi-test", "cw-paginate-storage 2.2.0", "cw-storage-plus 1.1.0", diff --git a/contracts/voting/dao-voting-cw721-staked/Cargo.toml b/contracts/voting/dao-voting-cw721-staked/Cargo.toml index ea5e67529..a52107cca 100644 --- a/contracts/voting/dao-voting-cw721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-staked/Cargo.toml @@ -19,6 +19,7 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } +cw-hooks = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } cw721-controllers = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 8f351d62b..5f20ce27a 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -1,10 +1,3 @@ -use crate::hooks::{stake_hook_msgs, unstake_hook_msgs}; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; -use crate::state::{ - register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, - INITIAL_NFTS, MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, -}; -use crate::ContractError; use cosmwasm_schema::cw_serde; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -16,10 +9,18 @@ use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw721::{Cw721QueryMsg, Cw721ReceiveMsg, NumTokensResponse}; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; +use dao_hooks::nft_stake::{stake_nft_hook_msgs, unstake_nft_hook_msgs}; use dao_interface::state::Admin; use dao_interface::voting::IsActiveResponse; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; +use crate::state::{ + register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, + INITIAL_NFTS, MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, +}; +use crate::ContractError; + pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-cw721-staked"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -239,7 +240,12 @@ pub fn execute_stake( } let staker = deps.api.addr_validate(&wrapper.sender)?; register_staked_nft(deps.storage, env.block.height, &staker, &wrapper.token_id)?; - let hook_msgs = stake_hook_msgs(deps.storage, staker.clone(), wrapper.token_id.clone())?; + let hook_msgs = stake_nft_hook_msgs( + HOOKS, + deps.storage, + staker.clone(), + wrapper.token_id.clone(), + )?; Ok(Response::default() .add_submessages(hook_msgs) .add_attribute("action", "stake") @@ -290,7 +296,8 @@ pub fn execute_unstake( // so if we reach this point in execution, we may safely create // claims. - let hook_msgs = unstake_hook_msgs(deps.storage, info.sender.clone(), token_ids.clone())?; + let hook_msgs = + unstake_nft_hook_msgs(HOOKS, deps.storage, info.sender.clone(), token_ids.clone())?; let config = CONFIG.load(deps.storage)?; match config.unstaking_duration { diff --git a/contracts/voting/dao-voting-cw721-staked/src/error.rs b/contracts/voting/dao-voting-cw721-staked/src/error.rs index 6135b1fa8..15e2cecea 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/error.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/error.rs @@ -10,7 +10,7 @@ pub enum ContractError { AlreadyStaked {}, #[error(transparent)] - HookError(#[from] cw_controllers::HookError), + HookError(#[from] cw_hooks::HookError), #[error("Active threshold count is greater than supply")] InvalidActiveCount {}, diff --git a/contracts/voting/dao-voting-cw721-staked/src/hooks.rs b/contracts/voting/dao-voting-cw721-staked/src/hooks.rs deleted file mode 100644 index 86352e4fb..000000000 --- a/contracts/voting/dao-voting-cw721-staked/src/hooks.rs +++ /dev/null @@ -1,158 +0,0 @@ -// TODO move this - -use crate::state::HOOKS; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, WasmMsg}; - -// This is just a helper to properly serialize the above message -#[cw_serde] -pub enum StakeChangedHookMsg { - Stake { addr: Addr, token_id: String }, - Unstake { addr: Addr, token_ids: Vec }, -} - -pub fn stake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - token_id: String, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Stake { addr, token_id }, - ))?; - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.into_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -pub fn unstake_hook_msgs( - storage: &dyn Storage, - addr: Addr, - token_ids: Vec, -) -> StdResult> { - let msg = to_binary(&StakeChangedExecuteMsg::StakeChangeHook( - StakeChangedHookMsg::Unstake { addr, token_ids }, - ))?; - - HOOKS.prepare_hooks(storage, |a| { - let execute = WasmMsg::Execute { - contract_addr: a.into_string(), - msg: msg.clone(), - funds: vec![], - }; - Ok(SubMsg::new(execute)) - }) -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum StakeChangedExecuteMsg { - StakeChangeHook(StakeChangedHookMsg), -} - -#[cfg(test)] -mod tests { - use crate::{ - contract::execute, - state::{Config, CONFIG}, - }; - - use super::*; - - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - - #[test] - fn test_hooks() { - let mut deps = mock_dependencies(); - - let messages = stake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - "ekez-token".to_string(), - ) - .unwrap(); - assert_eq!(messages.len(), 0); - - let messages = unstake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - vec!["ekez-token".to_string()], - ) - .unwrap(); - assert_eq!(messages.len(), 0); - - // Save a config for the execute messages we're testing. - CONFIG - .save( - deps.as_mut().storage, - &Config { - owner: Some(Addr::unchecked("ekez")), - nft_address: Addr::unchecked("ekez-token"), - unstaking_duration: None, - }, - ) - .unwrap(); - - let env = mock_env(); - let info = mock_info("ekez", &[]); - - execute( - deps.as_mut(), - env, - info, - crate::msg::ExecuteMsg::AddHook { - addr: "ekez".to_string(), - }, - ) - .unwrap(); - - let messages = stake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - "ekez-token".to_string(), - ) - .unwrap(); - assert_eq!(messages.len(), 1); - - let messages = unstake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - vec!["ekez-token".to_string()], - ) - .unwrap(); - assert_eq!(messages.len(), 1); - - let env = mock_env(); - let info = mock_info("ekez", &[]); - - execute( - deps.as_mut(), - env, - info, - crate::msg::ExecuteMsg::RemoveHook { - addr: "ekez".to_string(), - }, - ) - .unwrap(); - - let messages = stake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - "ekez-token".to_string(), - ) - .unwrap(); - assert_eq!(messages.len(), 0); - - let messages = unstake_hook_msgs( - &deps.storage, - Addr::unchecked("ekez"), - vec!["ekez-token".to_string()], - ) - .unwrap(); - assert_eq!(messages.len(), 0); - } -} diff --git a/contracts/voting/dao-voting-cw721-staked/src/lib.rs b/contracts/voting/dao-voting-cw721-staked/src/lib.rs index 51ae5c619..d4a73c5be 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/lib.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; mod error; -pub mod hooks; pub mod msg; pub mod state; diff --git a/contracts/voting/dao-voting-cw721-staked/src/state.rs b/contracts/voting/dao-voting-cw721-staked/src/state.rs index bf8e2ef5f..a3cded8ba 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/state.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Binary, Empty, StdError, StdResult, Storage, Uint128}; use cw721_controllers::NftClaims; -use cw_controllers::Hooks; +use cw_hooks::Hooks; use cw_storage_plus::{Item, Map, SnapshotItem, SnapshotMap, Strategy}; use cw_utils::Duration; use dao_voting::threshold::ActiveThreshold; diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs new file mode 100644 index 000000000..e97faf5fb --- /dev/null +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs @@ -0,0 +1,107 @@ +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + Addr, +}; +use dao_hooks::nft_stake::{stake_nft_hook_msgs, unstake_nft_hook_msgs}; + +use crate::{ + contract::execute, + state::{Config, CONFIG, HOOKS}, +}; + +#[test] +fn test_hooks() { + let mut deps = mock_dependencies(); + + let messages = stake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + "ekez-token".to_string(), + ) + .unwrap(); + assert_eq!(messages.len(), 0); + + let messages = unstake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + vec!["ekez-token".to_string()], + ) + .unwrap(); + assert_eq!(messages.len(), 0); + + // Save a config for the execute messages we're testing. + CONFIG + .save( + deps.as_mut().storage, + &Config { + owner: Some(Addr::unchecked("ekez")), + nft_address: Addr::unchecked("ekez-token"), + unstaking_duration: None, + }, + ) + .unwrap(); + + let env = mock_env(); + let info = mock_info("ekez", &[]); + + execute( + deps.as_mut(), + env, + info, + crate::msg::ExecuteMsg::AddHook { + addr: "ekez".to_string(), + }, + ) + .unwrap(); + + let messages = stake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + "ekez-token".to_string(), + ) + .unwrap(); + assert_eq!(messages.len(), 1); + + let messages = unstake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + vec!["ekez-token".to_string()], + ) + .unwrap(); + assert_eq!(messages.len(), 1); + + let env = mock_env(); + let info = mock_info("ekez", &[]); + + execute( + deps.as_mut(), + env, + info, + crate::msg::ExecuteMsg::RemoveHook { + addr: "ekez".to_string(), + }, + ) + .unwrap(); + + let messages = stake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + "ekez-token".to_string(), + ) + .unwrap(); + assert_eq!(messages.len(), 0); + + let messages = unstake_nft_hook_msgs( + HOOKS, + &deps.storage, + Addr::unchecked("ekez"), + vec!["ekez-token".to_string()], + ) + .unwrap(); + assert_eq!(messages.len(), 0); +} diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs index d28f52201..7fbae3de5 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs @@ -1,5 +1,6 @@ mod adversarial; mod execute; +mod hooks; mod instantiate; mod queries; mod tests; diff --git a/packages/dao-hooks/README.md b/packages/dao-hooks/README.md index b83076cf5..b2e8686c8 100644 --- a/packages/dao-hooks/README.md +++ b/packages/dao-hooks/README.md @@ -2,6 +2,9 @@ This package provides an interface for managing and dispatching proposal, staking, and voting related hooks. +### NFT Stake Hooks +Staking hooks are fired when NFTs are staked or unstaked in a DAO. + ### Proposal Hooks There are two types of proposal hooks: - **New Proposal Hook:** fired when a new proposal is created. @@ -12,8 +15,6 @@ Our wiki contains more info on [Proposal Hooks](https://github.com/DA0-DA0/dao-c ### Stake Hooks Staking hooks are fired when tokens are staked or unstaked in a DAO. -TODO document types of staking hooks - ### Vote Hooks Vote hooks are fired when new votes are cast. diff --git a/packages/dao-hooks/src/all_hooks.rs b/packages/dao-hooks/src/all_hooks.rs new file mode 100644 index 000000000..361075e84 --- /dev/null +++ b/packages/dao-hooks/src/all_hooks.rs @@ -0,0 +1,19 @@ +use cosmwasm_schema::cw_serde; + +use crate::nft_stake::NftStakeChangedHookMsg; +use crate::proposal::ProposalHookMsg; +use crate::stake::StakeChangedHookMsg; +use crate::vote::VoteHookMsg; + +/// An enum representing all possible DAO hooks. +#[cw_serde] +pub enum DaoHooks { + /// Called when NFTs are staked or unstaked. + NftStakeChangeHook(NftStakeChangedHookMsg), + /// Called when a proposal status changes. + ProposalHook(ProposalHookMsg), + /// Called when tokens are staked or unstaked. + StakeChangeHook(StakeChangedHookMsg), + /// Called when a vote is cast. + VoteHook(VoteHookMsg), +} diff --git a/packages/dao-hooks/src/lib.rs b/packages/dao-hooks/src/lib.rs index f6f59c91b..3f9239868 100644 --- a/packages/dao-hooks/src/lib.rs +++ b/packages/dao-hooks/src/lib.rs @@ -1,7 +1,7 @@ #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] +mod all_hooks; +pub mod nft_stake; pub mod proposal; pub mod stake; pub mod vote; - -// TODO DAO hooks enum with test that contains all hooks diff --git a/packages/dao-hooks/src/nft_stake.rs b/packages/dao-hooks/src/nft_stake.rs new file mode 100644 index 000000000..fd7a221d0 --- /dev/null +++ b/packages/dao-hooks/src/nft_stake.rs @@ -0,0 +1,53 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, WasmMsg}; +use cw_hooks::Hooks; + +#[cw_serde] +pub enum NftStakeChangedHookMsg { + Stake { addr: Addr, token_id: String }, + Unstake { addr: Addr, token_ids: Vec }, +} + +pub fn stake_nft_hook_msgs( + hooks: Hooks, + storage: &dyn Storage, + addr: Addr, + token_id: String, +) -> StdResult> { + let msg = to_binary(&NftStakeChangedExecuteMsg::StakeChangeHook( + NftStakeChangedHookMsg::Stake { addr, token_id }, + ))?; + hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.into_string(), + msg: msg.clone(), + funds: vec![], + }; + Ok(SubMsg::new(execute)) + }) +} + +pub fn unstake_nft_hook_msgs( + hooks: Hooks, + storage: &dyn Storage, + addr: Addr, + token_ids: Vec, +) -> StdResult> { + let msg = to_binary(&NftStakeChangedExecuteMsg::StakeChangeHook( + NftStakeChangedHookMsg::Unstake { addr, token_ids }, + ))?; + + hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.into_string(), + msg: msg.clone(), + funds: vec![], + }; + Ok(SubMsg::new(execute)) + }) +} + +#[cw_serde] +pub enum NftStakeChangedExecuteMsg { + NftStakeChangeHook(NftStakeChangedHookMsg), +} diff --git a/packages/dao-hooks/src/stake.rs b/packages/dao-hooks/src/stake.rs index 3abdd15d1..6f675071b 100644 --- a/packages/dao-hooks/src/stake.rs +++ b/packages/dao-hooks/src/stake.rs @@ -46,8 +46,7 @@ pub fn unstake_hook_msgs( }) } -// This is just a helper to properly serialize the above message #[cw_serde] -enum StakeChangedExecuteMsg { +pub enum StakeChangedExecuteMsg { StakeChangeHook(StakeChangedHookMsg), } diff --git a/packages/dao-hooks/src/vote.rs b/packages/dao-hooks/src/vote.rs index b60eb5aee..7da7aa6c1 100644 --- a/packages/dao-hooks/src/vote.rs +++ b/packages/dao-hooks/src/vote.rs @@ -12,7 +12,6 @@ pub enum VoteHookMsg { }, } -// This is just a helper to properly serialize the above message #[cw_serde] pub enum VoteHookExecuteMsg { VoteHook(VoteHookMsg), From 2b04dbca314ce22348156b031e887139ed1ea9d2 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 23:06:21 -0700 Subject: [PATCH 29/59] Add cw4::MemberChangedHookMsg and clean up dao-hooks pkg --- Cargo.lock | 1 + packages/dao-hooks/Cargo.toml | 1 + packages/dao-hooks/src/all_hooks.rs | 4 ++++ packages/dao-hooks/src/lib.rs | 3 +++ packages/dao-hooks/src/nft_stake.rs | 9 +++++++-- packages/dao-hooks/src/proposal.rs | 14 ++++++++------ packages/dao-hooks/src/stake.rs | 5 +++++ packages/dao-hooks/src/vote.rs | 11 ++++++----- 8 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00d2b40c3..25b31873b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1635,6 +1635,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-hooks", + "cw4 1.1.0", "dao-voting 2.2.0", ] diff --git a/packages/dao-hooks/Cargo.toml b/packages/dao-hooks/Cargo.toml index d675dc65a..45ea0f640 100644 --- a/packages/dao-hooks/Cargo.toml +++ b/packages/dao-hooks/Cargo.toml @@ -10,5 +10,6 @@ version = { workspace = true } [dependencies] cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } +cw4 = { workspace = true } cw-hooks = { workspace = true } dao-voting = { workspace = true } diff --git a/packages/dao-hooks/src/all_hooks.rs b/packages/dao-hooks/src/all_hooks.rs index 361075e84..3dfe9ea4a 100644 --- a/packages/dao-hooks/src/all_hooks.rs +++ b/packages/dao-hooks/src/all_hooks.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::cw_serde; +use cw4::MemberChangedHookMsg; use crate::nft_stake::NftStakeChangedHookMsg; use crate::proposal::ProposalHookMsg; @@ -8,6 +9,9 @@ use crate::vote::VoteHookMsg; /// An enum representing all possible DAO hooks. #[cw_serde] pub enum DaoHooks { + /// Called when a member is added or removed + /// to a cw4-groups or cw721-roles contract. + MemberChangedHook(MemberChangedHookMsg), /// Called when NFTs are staked or unstaked. NftStakeChangeHook(NftStakeChangedHookMsg), /// Called when a proposal status changes. diff --git a/packages/dao-hooks/src/lib.rs b/packages/dao-hooks/src/lib.rs index 3f9239868..41e5ef970 100644 --- a/packages/dao-hooks/src/lib.rs +++ b/packages/dao-hooks/src/lib.rs @@ -5,3 +5,6 @@ pub mod nft_stake; pub mod proposal; pub mod stake; pub mod vote; + +pub use all_hooks::DaoHooks; +pub use cw4::MemberChangedHookMsg; diff --git a/packages/dao-hooks/src/nft_stake.rs b/packages/dao-hooks/src/nft_stake.rs index fd7a221d0..9cebad29b 100644 --- a/packages/dao-hooks/src/nft_stake.rs +++ b/packages/dao-hooks/src/nft_stake.rs @@ -2,19 +2,22 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; +/// An enum representing NFT staking hooks. #[cw_serde] pub enum NftStakeChangedHookMsg { Stake { addr: Addr, token_id: String }, Unstake { addr: Addr, token_ids: Vec }, } +/// Prepares NftStakeChangedHookMsg::Stake hook SubMsgs, +/// containing the address and the token_id staked. pub fn stake_nft_hook_msgs( hooks: Hooks, storage: &dyn Storage, addr: Addr, token_id: String, ) -> StdResult> { - let msg = to_binary(&NftStakeChangedExecuteMsg::StakeChangeHook( + let msg = to_binary(&NftStakeChangedExecuteMsg::NftStakeChangeHook( NftStakeChangedHookMsg::Stake { addr, token_id }, ))?; hooks.prepare_hooks(storage, |a| { @@ -27,13 +30,15 @@ pub fn stake_nft_hook_msgs( }) } +/// Prepares NftStakeChangedHookMsg::Unstake hook SubMsgs, +/// containing the address and the token_ids unstaked. pub fn unstake_nft_hook_msgs( hooks: Hooks, storage: &dyn Storage, addr: Addr, token_ids: Vec, ) -> StdResult> { - let msg = to_binary(&NftStakeChangedExecuteMsg::StakeChangeHook( + let msg = to_binary(&NftStakeChangedExecuteMsg::NftStakeChangeHook( NftStakeChangedHookMsg::Unstake { addr, token_ids }, ))?; diff --git a/packages/dao-hooks/src/proposal.rs b/packages/dao-hooks/src/proposal.rs index 9db23fcdb..0b95758ad 100644 --- a/packages/dao-hooks/src/proposal.rs +++ b/packages/dao-hooks/src/proposal.rs @@ -3,6 +3,9 @@ use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; use dao_voting::reply::mask_proposal_hook_index; +/// An enum representing proposal hook messages. +/// Either a new propsoal hook, fired when a new proposal is created, +/// or a proposal status hook, fired when a proposal changes status. #[cw_serde] pub enum ProposalHookMsg { NewProposal { @@ -16,12 +19,6 @@ pub enum ProposalHookMsg { }, } -// This is just a helper to properly serialize the above message -#[cw_serde] -pub enum ProposalHookExecuteMsg { - ProposalHook(ProposalHookMsg), -} - /// Prepares new proposal hook messages. These messages reply on error /// and have even reply IDs. /// IDs are set to even numbers to then be interleaved with the vote hooks. @@ -90,3 +87,8 @@ pub fn proposal_status_changed_hooks( Ok(messages) } + +#[cw_serde] +pub enum ProposalHookExecuteMsg { + ProposalHook(ProposalHookMsg), +} diff --git a/packages/dao-hooks/src/stake.rs b/packages/dao-hooks/src/stake.rs index 6f675071b..6161bf2c9 100644 --- a/packages/dao-hooks/src/stake.rs +++ b/packages/dao-hooks/src/stake.rs @@ -2,12 +2,15 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, StdResult, Storage, SubMsg, Uint128, WasmMsg}; use cw_hooks::Hooks; +/// An enum representing staking hooks. #[cw_serde] pub enum StakeChangedHookMsg { Stake { addr: Addr, amount: Uint128 }, Unstake { addr: Addr, amount: Uint128 }, } +/// Prepares StakeChangedHookMsg::Stake hook SubMsgs, +/// containing the address and the amount staked. pub fn stake_hook_msgs( hooks: Hooks, storage: &dyn Storage, @@ -27,6 +30,8 @@ pub fn stake_hook_msgs( }) } +/// Prepares StakeChangedHookMsg::Unstake hook SubMsgs, +/// containing the address and the amount unstaked. pub fn unstake_hook_msgs( hooks: Hooks, storage: &dyn Storage, diff --git a/packages/dao-hooks/src/vote.rs b/packages/dao-hooks/src/vote.rs index 7da7aa6c1..b8a0dd772 100644 --- a/packages/dao-hooks/src/vote.rs +++ b/packages/dao-hooks/src/vote.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; use dao_voting::reply::mask_vote_hook_index; +/// An enum representing vote hooks, fired when new votes are cast. #[cw_serde] pub enum VoteHookMsg { NewVote { @@ -12,11 +13,6 @@ pub enum VoteHookMsg { }, } -#[cw_serde] -pub enum VoteHookExecuteMsg { - VoteHook(VoteHookMsg), -} - /// Prepares new vote hook messages. These messages reply on error /// and have even reply IDs. /// IDs are set to odd numbers to then be interleaved with the proposal hooks. @@ -45,3 +41,8 @@ pub fn new_vote_hooks( Ok(tmp) }) } + +#[cw_serde] +pub enum VoteHookExecuteMsg { + VoteHook(VoteHookMsg), +} From e48211fc1d0e673356a3f15d3416053ea038b7eb Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 23:16:18 -0700 Subject: [PATCH 30/59] Make clippy happy, use only migrate newer across all voting contracts --- .../cw-tokenfactory-issuer/src/contract.rs | 2 +- .../schema/cw20-stake-external-rewards.json | 1 + .../voting/dao-voting-cw20-staked/src/contract.rs | 14 ++++++++++---- .../voting/dao-voting-cw20-staked/src/tests.rs | 2 +- contracts/voting/dao-voting-cw4/src/contract.rs | 2 +- .../voting/dao-voting-cw721-staked/src/contract.rs | 2 +- .../dao-voting-native-staked/src/contract.rs | 2 +- .../src/contract.rs | 2 +- .../src/tests/multitest/tf_module_mock.rs | 6 ++---- 9 files changed, 19 insertions(+), 14 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index d3264c8f3..7de8b8f7b 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -162,7 +162,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(Response::default()) + let storage_version: ContractVersion = get_contract_version(deps.storage)?; + + // Only migrate if newer + if storage_version.version.as_str() < CONTRACT_VERSION { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new().add_attribute("action", "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/voting/dao-voting-cw20-staked/src/tests.rs b/contracts/voting/dao-voting-cw20-staked/src/tests.rs index b6d664604..bab1b65a4 100644 --- a/contracts/voting/dao-voting-cw20-staked/src/tests.rs +++ b/contracts/voting/dao-voting-cw20-staked/src/tests.rs @@ -1391,7 +1391,7 @@ fn test_migrate() { #[test] pub fn test_migrate_update_version() { let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); let version = cw2::get_contract_version(&deps.storage).unwrap(); assert_eq!(version.version, CONTRACT_VERSION); diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index 246889713..cf6b1e249 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -173,7 +173,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result Result Result, _block: &BlockInfo, _sender: Addr, - msg: Self::ExecT, + _msg: Self::ExecT, ) -> AnyResult where ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, QueryC: CustomQuery + DeserializeOwned + 'static, { - match msg { - _ => bail!("execute not implemented for TokenFactoryModule"), - } + bail!("execute not implemented for TokenFactoryModule") } fn sudo( From 426d139cb45269589324fc795f4313eb46361df6 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 23:32:48 -0700 Subject: [PATCH 31/59] cargo fmt --- ci/integration-tests/src/helpers/helper.rs | 17 +++++++++-------- .../cw-vesting/src/suite_tests/tests.rs | 11 +++++++++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ci/integration-tests/src/helpers/helper.rs b/ci/integration-tests/src/helpers/helper.rs index 3c685c22d..4d30c1670 100644 --- a/ci/integration-tests/src/helpers/helper.rs +++ b/ci/integration-tests/src/helpers/helper.rs @@ -118,15 +118,16 @@ pub fn create_dao( .unwrap(); let ProposalCreationPolicy::Module { addr: pre_propose } = chain - .orc - .query( - "dao_proposal_single", - &dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {} - ).unwrap() - .data() - .unwrap() + .orc + .query( + "dao_proposal_single", + &dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {}, + ) + .unwrap() + .data() + .unwrap() else { - panic!("expected pre-propose module") + panic!("expected pre-propose module") }; chain .orc diff --git a/contracts/external/cw-vesting/src/suite_tests/tests.rs b/contracts/external/cw-vesting/src/suite_tests/tests.rs index f93656c64..2738a3539 100644 --- a/contracts/external/cw-vesting/src/suite_tests/tests.rs +++ b/contracts/external/cw-vesting/src/suite_tests/tests.rs @@ -376,7 +376,12 @@ fn test_slash_while_cancelled_counts_against_owner() { assert_eq!(balance, distributable); let vest = suite.query_vest(); - let Status::Canceled { owner_withdrawable: pre_slash } = vest.status else { panic!("should be canceled") }; + let Status::Canceled { + owner_withdrawable: pre_slash, + } = vest.status + else { + panic!("should be canceled") + }; // register the slash. even though the time of the slash was // during the vest, the contract should deduct this from @@ -390,7 +395,9 @@ fn test_slash_while_cancelled_counts_against_owner() { .unwrap(); let vest = suite.query_vest(); - let Status::Canceled { owner_withdrawable } = vest.status else { panic!("should be canceled") }; + let Status::Canceled { owner_withdrawable } = vest.status else { + panic!("should be canceled") + }; assert_eq!(pre_slash - Uint128::new(10_000_000), owner_withdrawable); } From c2c230dca6c2c59cab30cae70cfd7f91c4a17705 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Sat, 2 Sep 2023 23:50:20 -0700 Subject: [PATCH 32/59] clippy on latest rust version --- .../proposal/dao-proposal-multiple/src/testing/do_votes.rs | 2 +- packages/dao-dao-macros/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs index 32eb43168..087324320 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs @@ -702,7 +702,7 @@ where let one_sum: u64 = one.iter().sum(); let none_sum: u64 = none.iter().sum(); - let mut sums = vec![zero_sum, one_sum, none_sum]; + let mut sums = [zero_sum, one_sum, none_sum]; sums.sort_unstable(); // If none of the above wins or there is a tie between second and first choice. diff --git a/packages/dao-dao-macros/src/lib.rs b/packages/dao-dao-macros/src/lib.rs index 4c11c8b71..8b2dddda9 100644 --- a/packages/dao-dao-macros/src/lib.rs +++ b/packages/dao-dao-macros/src/lib.rs @@ -26,7 +26,7 @@ fn merge_variants(metadata: TokenStream, left: TokenStream, right: TokenStream) }), ) = (&mut left.data, right.data) { - variants.extend(to_add.into_iter()); + variants.extend(to_add); quote! { #left }.into() } else { From 318cea59ec3d0dd43dc8ad189f186e4815bb2f48 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 4 Sep 2023 19:56:19 -0700 Subject: [PATCH 33/59] Improve unstaking_duration validation reuse --- Cargo.lock | 1 + contracts/staking/cw20-stake/Cargo.toml | 1 + contracts/staking/cw20-stake/src/contract.rs | 44 ++++++------------- contracts/staking/cw20-stake/src/error.rs | 31 ++++++++----- contracts/staking/cw20-stake/src/tests.rs | 31 +++++++------ .../dao-voting-native-staked/src/contract.rs | 23 ++-------- .../dao-voting-native-staked/src/error.rs | 6 +-- .../src/contract.rs | 24 ++-------- .../src/error.rs | 6 +-- packages/dao-voting/src/duration.rs | 26 +++++++++++ packages/dao-voting/src/lib.rs | 1 + 11 files changed, 95 insertions(+), 99 deletions(-) create mode 100644 packages/dao-voting/src/duration.rs 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; From 8c1b31ffce4914a73d515f6be179fc2cab5691d7 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 11:03:51 -0700 Subject: [PATCH 34/59] Allowlist should apply to transfers from *or to* an address For example, a DAO may wish to white list a Token Staking contract (to allow users to stake their tokens in the DAO) or a Merkle Drop contract (to allow users to claim their tokens). --- .../cw-tokenfactory-issuer/src/helpers.rs | 30 +++++++---- .../cw-tokenfactory-issuer/src/hooks.rs | 7 +-- .../cw-tokenfactory-issuer/src/msg.rs | 8 ++- .../tests/cases/beforesend.rs | 53 ++++++++++++++++--- 4 files changed, 77 insertions(+), 21 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index bcb306b75..7ce69c17f 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -32,7 +32,12 @@ pub fn check_is_not_denied(deps: Deps, address: String) -> Result<(), ContractEr Ok(()) } -pub fn check_is_not_frozen(deps: Deps, address: &str, denom: &str) -> Result<(), ContractError> { +pub fn check_is_not_frozen( + deps: Deps, + from_address: &str, + to_address: &str, + denom: &str, +) -> Result<(), ContractError> { let is_frozen = IS_FROZEN.load(deps.storage)?; let contract_denom = DENOM.load(deps.storage)?; @@ -42,16 +47,21 @@ pub fn check_is_not_frozen(deps: Deps, address: &str, denom: &str) -> Result<(), // contract's denom. let is_denom_frozen = is_frozen && denom == contract_denom; if is_denom_frozen { - let addr = deps.api.addr_validate(address)?; - if let Some(is_allowed) = ALLOWLIST.may_load(deps.storage, &addr)? { - if is_allowed { - return Ok(()); - } - }; + let from = deps.api.addr_validate(from_address)?; + let to = deps.api.addr_validate(to_address)?; - return Err(ContractError::ContractFrozen { - denom: contract_denom, - }); + // If either the from address or the to_address is allowed, then transaction proceeds + let is_from_allowed = ALLOWLIST.may_load(deps.storage, &from)?; + let is_to_allowed = ALLOWLIST.may_load(deps.storage, &to)?; + match (is_from_allowed, is_to_allowed) { + (Some(true), _) => return Ok(()), + (_, Some(true)) => return Ok(()), + _ => { + return Err(ContractError::ContractFrozen { + denom: contract_denom, + }) + } + } } Ok(()) diff --git a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs index 81adf650d..7aff000b7 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs @@ -9,10 +9,11 @@ pub fn beforesend_hook( to: String, coin: Coin, ) -> Result { - // assert that denom of this contract is not frozen - check_is_not_frozen(deps.as_ref(), &from, &coin.denom)?; + // Assert that denom of this contract is not frozen + // If it is frozen, check whether either 'from' or 'to' address is allowed + check_is_not_frozen(deps.as_ref(), &from, &to, &coin.denom)?; - // assert that neither 'from' or 'to' address is denylist + // Assert that neither 'from' or 'to' address is denylist check_is_not_denied(deps.as_ref(), from)?; check_is_not_denied(deps.as_ref(), to)?; diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index be37f4b42..20070f41c 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -22,7 +22,13 @@ pub struct MigrateMsg {} #[cw_serde] pub enum ExecuteMsg { - /// Allow adds the target address to the allowlist to be able to send tokens even if the token is frozen. + /// Allow adds the target address to the allowlist to be able to send or recieve tokens even if the token + /// is frozen. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature + /// to work. + /// + /// This functionality is intedended for DAOs who do not wish to have a their tokens liquid while bootstrapping + /// their DAO. For example, a DAO may wish to white list a Token Staking contract (to allow users to stake their + /// tokens in the DAO) or a Merkle Drop contract (to allow users to claim their tokens). Allow { address: String, status: bool }, /// Burn token to address. Burn allowance is required and wiil be deducted after successful burn. diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs index 76cdff00e..164a5b973 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs @@ -51,15 +51,20 @@ fn allowlisted_addresses_can_transfer_when_token_frozen() { let allowlistee = &env.test_accs[1]; let other = &env.test_accs[2]; + // Mint to owner and allowlistee + env.cw_tokenfactory_issuer + .set_minter(&owner.address(), 100000, owner) + .unwrap(); + env.cw_tokenfactory_issuer + .mint(&owner.address(), 10000, owner) + .unwrap(); + env.cw_tokenfactory_issuer + .mint(&allowlistee.address(), 10000, owner) + .unwrap(); + // Freeze env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - // Bank send should fail - let err = env - .send_tokens(allowlistee.address(), coins(10000, denom.clone()), owner) - .unwrap_err(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); - // Allowlist address env.cw_tokenfactory_issuer .allow(&allowlistee.address(), true, owner) @@ -67,12 +72,46 @@ fn allowlisted_addresses_can_transfer_when_token_frozen() { // Bank send should pass env.send_tokens(other.address(), coins(10000, denom.clone()), allowlistee) - .unwrap_err(); + .unwrap(); + // Non allowlist address can't transfer, bank send should fail let err = env .send_tokens(other.address(), coins(10000, denom.clone()), owner) .unwrap_err(); assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); + + // Other assets are not affected + env.send_tokens(other.address(), coins(10000, "uosmo"), owner) + .unwrap(); +} + +#[test] +fn non_allowlisted_accounts_can_transfer_to_allowlisted_address_frozen() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + let allowlistee = &env.test_accs[1]; + let other = &env.test_accs[2]; + + // Mint to other + env.cw_tokenfactory_issuer + .set_minter(&owner.address(), 100000, owner) + .unwrap(); + env.cw_tokenfactory_issuer + .mint(&other.address(), 10000, owner) + .unwrap(); + + // Freeze + env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); + + // Allowlist address + env.cw_tokenfactory_issuer + .allow(&allowlistee.address(), true, owner) + .unwrap(); + + // Bank send to allow listed address should pass + env.send_tokens(allowlistee.address(), coins(10000, denom.clone()), other) + .unwrap(); } #[test] From 8883b75240f5e77038ba402ca830870de998b60d Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 11:05:30 -0700 Subject: [PATCH 35/59] Add info on renouncing Token Factory Admin --- .../external/cw-tokenfactory-issuer/README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/external/cw-tokenfactory-issuer/README.md b/contracts/external/cw-tokenfactory-issuer/README.md index 9e6ec9f78..31588df06 100644 --- a/contracts/external/cw-tokenfactory-issuer/README.md +++ b/contracts/external/cw-tokenfactory-issuer/README.md @@ -7,7 +7,7 @@ This repo contains a set of contracts that when used in conjunction with the x/t - Creating a new Token Factory token or using an existing one - Granting and revoking allowances for the minting and burning of tokens - Updating token metadata -- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to continue to transfer +- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to allow transfer to or from - Denylist to prevent certain addresses from transferring - Force transfering tokens via the contract owner - Updating the contract owner or Token Factory admin @@ -53,3 +53,20 @@ Example instantiate message: } } ``` + +## Renouncing Token Factory Admin +Some DAOs or protocols after the initial setup phase may wish to render their tokens immutable, permanently disabling features of this contract. + +To do so, they must execute a `ExcuteMessage::UpdateTokenFactoryAdmin {}` method, setting the Admin to a null address or the bank module for your respective chain. + +For example, on Juno this could be: + +``` json +{ + "update_token_factory_admin": { + "new_admin": "juno1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + } +} +``` + +The Token Factory standard requires a Token Factory admin per token, by setting to a null address the Token is rendered immutable and the `cw-tokenfactory-issuer` will be unable to make future updates. This is secure as the cryptography that underlies the chain enforces that even with the largest super computers in the world it would take an astonomically large amount of time to compute the private key for this address. From a8683f47fc3e526dec2d7ba2c049ade664c8387e Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 11:52:19 -0700 Subject: [PATCH 36/59] Remove outdated comments --- contracts/external/cw-tokenfactory-issuer/src/execute.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index 8d3725a08..b4d863949 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -126,8 +126,6 @@ pub fn update_contract_owner( // Only allow current contract owner to change owner check_is_contract_owner(deps.as_ref(), info.sender)?; - // TODO make sure it's possible to renounce ownership all together - // TODO add test for NO OWNER // Validate that new owner is a valid address let new_owner_addr = deps.api.addr_validate(&new_owner)?; @@ -147,8 +145,6 @@ pub fn update_tokenfactory_admin( // Only allow current contract owner to change tokenfactory admin check_is_contract_owner(deps.as_ref(), info.sender)?; - // TODO make sure it's possible to renounce ownership all together - // TODO add test for NO ADMIN // Validate that the new admin is a valid address let new_admin_addr = deps.api.addr_validate(&new_admin)?; From 2cb2d598f216e0ade82010a803ce6a493e88a028 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 11:22:04 -0700 Subject: [PATCH 37/59] Remove commented out metadata test Osmosis test tube doesn't support metadata queries anymore, we assume metadata is set properly by the Cosmos SDK and this is well tested upstream. --- .../tests/cases/denom_metadata.rs | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs index 706c61a72..35968133f 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs @@ -1,7 +1,4 @@ use cw_tokenfactory_issuer::{msg::InstantiateMsg, ContractError}; -// use osmosis_test_tube::osmosis_std::types::cosmos::bank::v1beta1::{ -// DenomUnit, Metadata, QueryDenomMetadataRequest, -// }; use crate::test_env::{TestEnv, TokenfactoryIssuer}; @@ -112,32 +109,4 @@ fn set_denom_metadata_with_base_denom_unit_should_overides_default_base_denom_un env.cw_tokenfactory_issuer .set_denom_metadata(metadata.clone(), owner) .unwrap(); - - // // TODO fix up this metadata test - // Should update metadata - // assert_eq!( - // env.bank() - // .query_denom_metadata(&QueryDenomMetadataRequest { - // denom: denom.clone() - // }) - // .unwrap() - // .metadata - // .unwrap(), - // Metadata { - // description: metadata.description, - // denom_units: metadata - // .denom_units - // .into_iter() - // .map(|d| DenomUnit { - // denom: d.denom, - // exponent: d.exponent, - // aliases: d.aliases, - // }) - // .collect(), - // base: denom, - // display: metadata.display, - // name: metadata.name, - // symbol: metadata.symbol, - // } - // ); } From f2d4ac351d9b1228de20cd3a1e65600e3d7bb7a5 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 11:25:10 -0700 Subject: [PATCH 38/59] More informative error to address BlockBeforeSend hook executing on minting or burning This is intended functionality. If a token is frozen, a DAO needs to both grant a minter allowance as well as adding the minter to the allowlist to allow for token transfers when the token is frozen. The new error message should make next steps clear if this edge case is encountered. --- contracts/external/cw-tokenfactory-issuer/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/error.rs b/contracts/external/cw-tokenfactory-issuer/src/error.rs index 1f6ef792a..ac2a3eedc 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/error.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/error.rs @@ -18,7 +18,7 @@ pub enum ContractError { #[error("Cannot denylist the issuer contract itself")] CannotDenylistSelf {}, - #[error("The contract is frozen for denom {denom:?}")] + #[error("The contract is frozen for denom {denom:?}. Addresses need to be added to the allowlist to enable transfers to or from an account.")] ContractFrozen { denom: String }, #[error("Invalid subdenom: {subdenom:?}")] From 5953169611981aad111a189a949856347a2ef253 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 12:06:33 -0700 Subject: [PATCH 39/59] Use tagged versions or upstream git repos for deps --- Cargo.lock | 19 ++++--------------- Cargo.toml | 7 +++---- .../dao-pre-propose-multiple/src/tests.rs | 2 +- .../dao-pre-propose-single/src/tests.rs | 2 +- .../Cargo.toml | 4 ++-- packages/dao-testing/Cargo.toml | 2 +- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f4c50e03..b8807dbfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,7 +841,7 @@ dependencies = [ [[package]] name = "cw-multi-test" version = "0.16.5" -source = "git+https://github.com/JakeHartnell/cw-multi-test.git?branch=bank-supply-support#8c618c8dcc014dacf2cc10627805b36bbc0bfc6e" +source = "git+https://github.com/CosmWasm/cw-multi-test.git?rev=d38db7752b9f054c395d6108453f8b321e4cab02#d38db7752b9f054c395d6108453f8b321e4cab02" dependencies = [ "anyhow", "cosmwasm-std", @@ -1034,7 +1034,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "token-bindings 0.11.0 (git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954)", + "token-bindings", ] [[package]] @@ -1986,7 +1986,7 @@ dependencies = [ "serde", "serde_json", "stake-cw20", - "token-bindings 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "token-bindings", ] [[package]] @@ -2174,7 +2174,7 @@ dependencies = [ "osmosis-test-tube", "serde", "thiserror", - "token-bindings 0.11.0 (git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954)", + "token-bindings", ] [[package]] @@ -4384,17 +4384,6 @@ dependencies = [ "serde", ] -[[package]] -name = "token-bindings" -version = "0.11.0" -source = "git+https://github.com/CosmosContracts/token-bindings.git?rev=0cd084b68172ffc9af29eb37fb915392ce351954#0cd084b68172ffc9af29eb37fb915392ce351954" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "tokio" version = "1.32.0" diff --git a/Cargo.toml b/Cargo.toml index b85fed60b..be9eab9a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,8 +71,7 @@ sg-multi-test = "3.1.0" syn = { version = "1.0", features = ["derive"] } test-context = "0.1" thiserror = { version = "1.0" } -# TODO use upstream when PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 -token-bindings = { git = "https://github.com/CosmosContracts/token-bindings.git", rev = "0cd084b68172ffc9af29eb37fb915392ce351954" } +token-bindings = "0.11.0" wynd-utils = "0.4" # One commit ahead of version 0.3.0. Allows initialization with an @@ -128,6 +127,6 @@ cw4-voting-v1 = { package = "cw4-voting", version = "0.1.0" } voting-v1 = { package = "dao-voting", version = "0.1.0" } stake-cw20-v03 = { package = "stake-cw20", version = "0.2.6" } -# TODO remove when upstream PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 +# TODO remove when new release is tagged upstream [patch.crates-io] -cw-multi-test = { git = "https://github.com/JakeHartnell/cw-multi-test.git", branch = "bank-supply-support" } +cw-multi-test = { git = "https://github.com/CosmWasm/cw-multi-test.git", rev = "d38db7752b9f054c395d6108453f8b321e4cab02" } diff --git a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs index e256a1f47..3d7352515 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs @@ -1021,7 +1021,7 @@ fn test_execute_extension_does_nothing() { assert_eq!(res.events[0].attributes.len(), 1); assert_eq!( res.events[0].attributes[0].key, - "_contract_addr".to_string() + "_contract_address".to_string() ) } diff --git a/contracts/pre-propose/dao-pre-propose-single/src/tests.rs b/contracts/pre-propose/dao-pre-propose-single/src/tests.rs index 6a8b48e9f..078ea2d4b 100644 --- a/contracts/pre-propose/dao-pre-propose-single/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-single/src/tests.rs @@ -957,7 +957,7 @@ fn test_execute_extension_does_nothing() { assert_eq!(res.events[0].attributes.len(), 1); assert_eq!( res.events[0].attributes[0].key, - "_contract_addr".to_string() + "_contract_address".to_string() ) } diff --git a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml index ca9e89902..2ad870d50 100644 --- a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-factory-staked/Cargo.toml @@ -41,8 +41,8 @@ token-bindings = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -# TODO use upstream when PR merged and new release tagged: https://github.com/CosmWasm/cw-multi-test/pull/51 -cw-multi-test = { git = "https://github.com/JakeHartnell/cw-multi-test.git", branch = "bank-supply-support" } +# TODO use upstream when new release is tagged +cw-multi-test = { git = "https://github.com/CosmWasm/cw-multi-test.git", rev = "d38db7752b9f054c395d6108453f8b321e4cab02" } cw-tokenfactory-issuer = { workspace = true } dao-proposal-hook-counter = { workspace = true } dao-testing = { workspace = true, features = ["test-tube"] } diff --git a/packages/dao-testing/Cargo.toml b/packages/dao-testing/Cargo.toml index d7a9d0c10..a153e57e0 100644 --- a/packages/dao-testing/Cargo.toml +++ b/packages/dao-testing/Cargo.toml @@ -33,6 +33,7 @@ osmosis-test-tube = { workspace = true } rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +token-bindings = { workpsace = true } cw-core-v1 = { workspace = true, features = ["library"] } cw-hooks = { workspace = true } @@ -58,4 +59,3 @@ dao-voting-native-staked = { workspace = true } dao-voting-token-factory-staked = { workspace = true } voting-v1 = { workspace = true } stake-cw20-v03 = { workspace = true } -token-bindings = { workpsace = true } From dd93317df19552f3cb6b18abe420a09e8ef4b4ee Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 13:29:45 -0700 Subject: [PATCH 40/59] Better code documentation for cw-tokenfactory-issuer --- .../schema/cw-tokenfactory-issuer.json | 44 ++++++++---- .../cw-tokenfactory-issuer/src/contract.rs | 4 +- .../cw-tokenfactory-issuer/src/execute.rs | 60 ++++++++++++++++ .../cw-tokenfactory-issuer/src/helpers.rs | 9 ++- .../cw-tokenfactory-issuer/src/hooks.rs | 4 ++ .../cw-tokenfactory-issuer/src/lib.rs | 13 ++++ .../cw-tokenfactory-issuer/src/msg.rs | 71 ++++++++++++------- .../cw-tokenfactory-issuer/src/queries.rs | 15 ++++ .../cw-tokenfactory-issuer/src/state.rs | 7 +- 9 files changed, 183 insertions(+), 44 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json index 1b92dc669..6fe4ba54c 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -5,6 +5,7 @@ "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", + "description": "The message used to create a new instance of this smart contract.", "oneOf": [ { "description": "`NewToken` will create a new token when instantiate the contract. Newly created token will have full denom as `factory//`. It will be attached to the contract setup the beforesend listener automatically.", @@ -56,9 +57,10 @@ "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", + "description": "State changing methods available to this smart contract.", "oneOf": [ { - "description": "Allow adds the target address to the allowlist to be able to send tokens even if the token is frozen.", + "description": "Allow adds the target address to the allowlist to be able to send or recieve tokens even if the token is frozen. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature to work.\n\nThis functionality is intedended for DAOs who do not wish to have a their tokens liquid while bootstrapping their DAO. For example, a DAO may wish to white list a Token Staking contract (to allow users to stake their tokens in the DAO) or a Merkle Drop contract (to allow users to claim their tokens).", "type": "object", "required": [ "allow" @@ -424,9 +426,10 @@ "query": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", + "description": "Queries supported by this smart contract.", "oneOf": [ { - "description": "IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse", + "description": "Returns if token transfer is disabled. Response: IsFrozenResponse", "type": "object", "required": [ "is_frozen" @@ -440,7 +443,7 @@ "additionalProperties": false }, { - "description": "Denom returns the token denom that this contract is the admin for. Response: DenomResponse", + "description": "Returns the token denom that this contract is the admin for. Response: DenomResponse", "type": "object", "required": [ "denom" @@ -454,7 +457,7 @@ "additionalProperties": false }, { - "description": "Owner returns the owner of the contract. Response: OwnerResponse", + "description": "Returns the owner of the contract. Response: OwnerResponse", "type": "object", "required": [ "owner" @@ -468,7 +471,7 @@ "additionalProperties": false }, { - "description": "Allowance returns the allowance of the specified address. Response: AllowanceResponse", + "description": "Returns the burn allowance of the specified address. Response: AllowanceResponse", "type": "object", "required": [ "burn_allowance" @@ -490,7 +493,7 @@ "additionalProperties": false }, { - "description": "Allowances Enumerates over all allownances. Response: Vec", + "description": "Enumerates over all burn allownances. Response: AllowancesResponse", "type": "object", "required": [ "burn_allowances" @@ -520,7 +523,7 @@ "additionalProperties": false }, { - "description": "Allowance returns the allowance of the specified user. Response: AllowanceResponse", + "description": "Returns the mint allowance of the specified user. Response: AllowanceResponse", "type": "object", "required": [ "mint_allowance" @@ -542,7 +545,7 @@ "additionalProperties": false }, { - "description": "Allowances Enumerates over all allownances. Response: AllowancesResponse", + "description": "Enumerates over all mint allownances. Response: AllowancesResponse", "type": "object", "required": [ "mint_allowances" @@ -572,7 +575,7 @@ "additionalProperties": false }, { - "description": "IsDenied returns wether the user is on denylist or not. Response: StatusResponse", + "description": "Returns wether the user is on denylist or not. Response: StatusResponse", "type": "object", "required": [ "is_denied" @@ -594,7 +597,7 @@ "additionalProperties": false }, { - "description": "Denylist enumerates over all addresses on the denylist. Response: DenylistResponse", + "description": "Enumerates over all addresses on the denylist. Response: DenylistResponse", "type": "object", "required": [ "denylist" @@ -624,7 +627,7 @@ "additionalProperties": false }, { - "description": "IsAllowed returns wether the user is on the allowlist or not. Response: StatusResponse", + "description": "Returns wether the user is on the allowlist or not. Response: StatusResponse", "type": "object", "required": [ "is_allowed" @@ -646,7 +649,7 @@ "additionalProperties": false }, { - "description": "Allowlist enumerates over all addresses on the allowlist. Response: AllowlistResponse", + "description": "Enumerates over all addresses on the allowlist. Response: AllowlistResponse", "type": "object", "required": [ "allowlist" @@ -676,7 +679,7 @@ "additionalProperties": false }, { - "description": "Returns whether features that require MsgBeforeSendHook are enabled Most Cosmos chains do not support this feature yet.", + "description": "Returns whether features that require MsgBeforeSendHook are enabled. Most Cosmos chains do not support this feature yet.", "type": "object", "required": [ "before_send_hook_features_enabled" @@ -753,6 +756,7 @@ "allowlist": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowlistResponse", + "description": "Returns a list of addresses currently on the allowlist", "type": "object", "required": [ "allowlist" @@ -768,6 +772,7 @@ "additionalProperties": false, "definitions": { "StatusInfo": { + "description": "Account info for list queries related to allowlist and denylist", "type": "object", "required": [ "address", @@ -793,6 +798,7 @@ "burn_allowance": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowanceResponse", + "description": "Returns a mint or burn allowance for a particular address, representing the amount of tokens the account is allowed to mint or burn", "type": "object", "required": [ "allowance" @@ -813,6 +819,7 @@ "burn_allowances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowancesResponse", + "description": "Returns a list of all mint or burn allowances", "type": "object", "required": [ "allowances" @@ -828,6 +835,7 @@ "additionalProperties": false, "definitions": { "AllowanceInfo": { + "description": "Information about a particular account and its mint / burn allowances. Used in list queries.", "type": "object", "required": [ "address", @@ -852,6 +860,7 @@ "denom": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "DenomResponse", + "description": "Returns the full denomination for the Token Factory token. For example: `factory/{contract address}/{subdenom}`", "type": "object", "required": [ "denom" @@ -866,6 +875,7 @@ "denylist": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "DenylistResponse", + "description": "Returns a list of addresses currently on the denylist.", "type": "object", "required": [ "denylist" @@ -881,6 +891,7 @@ "additionalProperties": false, "definitions": { "StatusInfo": { + "description": "Account info for list queries related to allowlist and denylist", "type": "object", "required": [ "address", @@ -901,6 +912,7 @@ "is_allowed": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", + "description": "Whether a particular account is allowed or denied", "type": "object", "required": [ "status" @@ -915,6 +927,7 @@ "is_denied": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "StatusResponse", + "description": "Whether a particular account is allowed or denied", "type": "object", "required": [ "status" @@ -929,6 +942,7 @@ "is_frozen": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "IsFrozenResponse", + "description": "Returns whether or not the Token Factory token is frozen and transfers are disabled.", "type": "object", "required": [ "is_frozen" @@ -943,6 +957,7 @@ "mint_allowance": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowanceResponse", + "description": "Returns a mint or burn allowance for a particular address, representing the amount of tokens the account is allowed to mint or burn", "type": "object", "required": [ "allowance" @@ -963,6 +978,7 @@ "mint_allowances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllowancesResponse", + "description": "Returns a list of all mint or burn allowances", "type": "object", "required": [ "allowances" @@ -978,6 +994,7 @@ "additionalProperties": false, "definitions": { "AllowanceInfo": { + "description": "Information about a particular account and its mint / burn allowances. Used in list queries.", "type": "object", "required": [ "address", @@ -1002,6 +1019,7 @@ "owner": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "OwnerResponse", + "description": "Returns the current owner of this issuer contract who is allowed to call priviledged methods.", "type": "object", "required": [ "address" diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 7de8b8f7b..9c3d023a8 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -19,8 +19,8 @@ use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; use crate::queries; use crate::state::{BEFORE_SEND_HOOK_FEATURES_ENABLED, DENOM, IS_FROZEN, OWNER}; -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:cw-tokenfactory-issuer"; +// Version info for migration +const CONTRACT_NAME: &str = "CARGO_PKG_NAME"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const CREATE_DENOM_REPLY_ID: u64 = 1; diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index b4d863949..c93ac41fa 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -12,6 +12,9 @@ use crate::state::{ MINTER_ALLOWANCES, OWNER, }; +/// Mints new tokens. To mint new tokens, the address calling this method must +/// have an allowance of tokens to mint. This allowance is set by the contract through +/// the `ExecuteMsg::SetMinter { .. }` message. pub fn mint( deps: DepsMut, env: Env, @@ -68,6 +71,11 @@ pub fn mint( .add_attribute("amount", amount)) } +/// Burns tokens. To burn tokens, the address calling this method must +/// have an allowance of tokens to burn and the tokens to burn must belong +/// to the `cw_tokenfactory_issuer` contract itself. The allowance is set by +/// the contract through the `ExecuteMsg::SetBurner { .. }` message, and funds +/// to be burnt must be sent to this contract prior to burning. pub fn burn( deps: DepsMut, env: Env, @@ -118,6 +126,8 @@ pub fn burn( .add_attribute("amount", amount)) } +/// Updates the contract owner, must be the current contract owner to call +/// this method. pub fn update_contract_owner( deps: DepsMut, info: MessageInfo, @@ -137,6 +147,11 @@ pub fn update_contract_owner( .add_attribute("new_owner", new_owner)) } +/// Updates the Token Factory token admin. To set no admin, specify the `new_admin` +/// argument to be either a null address or the address of the Cosmos SDK bank module +/// for the chain. +/// +/// Must be the contract owner to call this method. pub fn update_tokenfactory_admin( deps: DepsMut, info: MessageInfo, @@ -160,6 +175,9 @@ pub fn update_tokenfactory_admin( .add_attribute("new_admin", new_admin)) } +/// Sets metadata related to the Token Factory denom. +/// +/// Must be the contract owner to call this method. pub fn set_denom_metadata( deps: DepsMut, env: Env, @@ -177,6 +195,11 @@ pub fn set_denom_metadata( })) } +/// Calls `MsgSetBeforeSendHook` and enables BeforeSendHook related features. +/// As not all chains support the `BeforeSendHook` in the bank module, this +/// is intended to be called should chains add this feature at a later date. +/// +/// Must be the contract owner to call this method. pub fn set_before_send_hook( deps: DepsMut, env: Env, @@ -211,6 +234,10 @@ pub fn set_before_send_hook( .add_message(msg_set_beforesend_hook)) } +/// Specifies and sets a burn allowance to allow for the burning of tokens. +/// To remove previously granted burn allowances, set this to zero. +/// +/// Must be the contract owner to call this method. pub fn set_burner( deps: DepsMut, info: MessageInfo, @@ -236,6 +263,10 @@ pub fn set_burner( .add_attribute("allowance", allowance)) } +/// Specifies and sets a burn allowance to allow for the minting of tokens. +/// To remove previously granted mint allowances, set this to zero. +/// +/// Must be the contract owner to call this method. pub fn set_minter( deps: DepsMut, info: MessageInfo, @@ -261,6 +292,18 @@ pub fn set_minter( .add_attribute("allowance", allowance)) } +/// Freezes / unfreezes token transfers, meaning that address will not be +/// able to send tokens until the token is unfrozen. This feature is dependent +/// on the BeforeSendHook. +/// +/// This feature works in conjunction with this contract's allowlist. For example, +/// a DAO may wish to prevent its token from being liquid during its bootstrapping +/// phase. It may wish to add its staking contract to the allowlist to allow users +/// to stake their tokens (thus users would be able to transfer to the staking +/// contract), or add an airdrop contract to the allowlist so users can claim +/// their tokens (but not yet trade them). +/// +/// Must be the contract owner to call this method. pub fn freeze( deps: DepsMut, info: MessageInfo, @@ -280,6 +323,11 @@ pub fn freeze( .add_attribute("status", status.to_string())) } +/// Adds or removes an address from the denylist, meaning they will not +/// be able to transfer their tokens. This feature is dependent on +/// the BeforeSendHook. +/// +/// Must be the contract owner to call this method. pub fn deny( deps: DepsMut, env: Env, @@ -314,6 +362,13 @@ pub fn deny( .add_attribute("status", status.to_string())) } +/// Relevant only when the token is frozen. Addresses on the allowlist can +/// transfer tokens as well as have tokens sent to them. This feature is +/// dependent on the BeforeSendHook. +/// +/// See the `freeze` method for more information. +/// +/// Must be the contract owner to call this method. pub fn allow( deps: DepsMut, info: MessageInfo, @@ -342,6 +397,11 @@ pub fn allow( .add_attribute("status", status.to_string())) } +/// Force transfers tokens from one account to another. To disable this, +/// DAOs will need to renounce Token Factory admin by setting the token +/// admin to be a null address or the address of the bank module. +/// +/// Must be the contract owner to call this method. pub fn force_transfer( deps: DepsMut, env: Env, diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index 7ce69c17f..a00d736d5 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -4,6 +4,7 @@ use crate::state::{ use crate::ContractError; use cosmwasm_std::{Addr, Deps}; +/// Checks wether the sender is the owner of the contract pub fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractError> { let owner = OWNER.load(deps.storage)?; if owner != sender { @@ -13,6 +14,7 @@ pub fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractE } } +/// Checks wether the BeforeSendHookFeatures gated features are enabled pub fn check_before_send_hook_features_enabled(deps: Deps) -> Result<(), ContractError> { let enabled = BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage)?; if !enabled { @@ -22,6 +24,7 @@ pub fn check_before_send_hook_features_enabled(deps: Deps) -> Result<(), Contrac } } +/// Checks wether the given address is on the denylist pub fn check_is_not_denied(deps: Deps, address: String) -> Result<(), ContractError> { let addr = deps.api.addr_validate(&address)?; if let Some(is_denied) = DENYLIST.may_load(deps.storage, &addr)? { @@ -32,6 +35,9 @@ pub fn check_is_not_denied(deps: Deps, address: String) -> Result<(), ContractEr Ok(()) } +/// Checks wether the contract is frozen for the given denom, in which case +/// token transfers will not be allowed unless the to or from address is on +/// the allowlist pub fn check_is_not_frozen( deps: Deps, from_address: &str, @@ -41,8 +47,9 @@ pub fn check_is_not_frozen( let is_frozen = IS_FROZEN.load(deps.storage)?; let contract_denom = DENOM.load(deps.storage)?; - // check if issuer is configured to be frozen and the arriving denom is the same + // Check if issuer is configured to be frozen and the arriving denom is the same // as this contract denom. + // // Denom can be different since setting beforesend listener doesn't check // contract's denom. let is_denom_frozen = is_frozen && denom == contract_denom; diff --git a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs index 7aff000b7..2b344f3e0 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/hooks.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/hooks.rs @@ -3,6 +3,10 @@ use cosmwasm_std::{Coin, DepsMut, Response}; use crate::error::ContractError; use crate::helpers::{check_is_not_denied, check_is_not_frozen}; +/// The before send hook is called before every token transfer on chains that +/// support MsgSetBeforeSendHook. +/// +/// It is called by the bank module. pub fn beforesend_hook( deps: DepsMut, from: String, diff --git a/contracts/external/cw-tokenfactory-issuer/src/lib.rs b/contracts/external/cw-tokenfactory-issuer/src/lib.rs index f10d4f9af..73e94be76 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/lib.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/lib.rs @@ -1,10 +1,23 @@ +#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] + +/// The smart contract itself, including the execute, instantiate, query, migrate +/// and reply entry points pub mod contract; +/// Private error module, ContractError is re-exported in the public interface mod error; +/// Contract methods that can be executed and alter state pub mod execute; +/// Helper functions used for validation and checks pub mod helpers; +/// Contract hooks pub mod hooks; +/// Contract messages describing the API of the contract as well as responses +/// from contract queries pub mod msg; +/// Contract queries pub mod queries; +/// The contract state pub mod state; +/// Error messages used in this contract pub use crate::error::ContractError; diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 20070f41c..311357ba4 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -2,6 +2,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Uint128}; pub use osmosis_std::types::cosmos::bank::v1beta1::{DenomUnit, Metadata}; +/// The message used to create a new instance of this smart contract. #[cw_serde] pub enum InstantiateMsg { /// `NewToken` will create a new token when instantiate the contract. @@ -17,9 +18,7 @@ pub enum InstantiateMsg { ExistingToken { denom: String }, } -#[cw_serde] -pub struct MigrateMsg {} - +/// State changing methods available to this smart contract. #[cw_serde] pub enum ExecuteMsg { /// Allow adds the target address to the allowlist to be able to send or recieve tokens even if the token @@ -85,132 +84,150 @@ pub enum ExecuteMsg { UpdateContractOwner { new_owner: String }, } -/// SudoMsg is only exposed for internal Cosmos SDK modules to call. -/// This is showing how we can expose "admin" functionality than can not be called by -/// external users or contracts, but only trusted (native/Go) code in the blockchain +/// Used for smart contract migration. #[cw_serde] -pub enum SudoMsg { - BlockBeforeSend { - from: String, - to: String, - amount: Coin, - }, -} +pub struct MigrateMsg {} +/// Queries supported by this smart contract. #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// IsFrozen returns if the entire token transfer functionality is frozen. Response: IsFrozenResponse + /// Returns if token transfer is disabled. Response: IsFrozenResponse #[returns(IsFrozenResponse)] IsFrozen {}, - /// Denom returns the token denom that this contract is the admin for. Response: DenomResponse + /// Returns the token denom that this contract is the admin for. Response: DenomResponse #[returns(DenomResponse)] Denom {}, - /// Owner returns the owner of the contract. Response: OwnerResponse + /// Returns the owner of the contract. Response: OwnerResponse #[returns(OwnerResponse)] Owner {}, - /// Allowance returns the allowance of the specified address. Response: AllowanceResponse + /// Returns the burn allowance of the specified address. Response: AllowanceResponse #[returns(AllowanceResponse)] BurnAllowance { address: String }, - /// Allowances Enumerates over all allownances. Response: Vec + /// Enumerates over all burn allownances. Response: AllowancesResponse #[returns(AllowancesResponse)] BurnAllowances { start_after: Option, limit: Option, }, - /// Allowance returns the allowance of the specified user. Response: AllowanceResponse + /// Returns the mint allowance of the specified user. Response: AllowanceResponse #[returns(AllowanceResponse)] MintAllowance { address: String }, - /// Allowances Enumerates over all allownances. Response: AllowancesResponse + /// Enumerates over all mint allownances. Response: AllowancesResponse #[returns(AllowancesResponse)] MintAllowances { start_after: Option, limit: Option, }, - /// IsDenied returns wether the user is on denylist or not. Response: StatusResponse + /// Returns wether the user is on denylist or not. Response: StatusResponse #[returns(StatusResponse)] IsDenied { address: String }, - /// Denylist enumerates over all addresses on the denylist. Response: DenylistResponse + /// Enumerates over all addresses on the denylist. Response: DenylistResponse #[returns(DenylistResponse)] Denylist { start_after: Option, limit: Option, }, - /// IsAllowed returns wether the user is on the allowlist or not. Response: StatusResponse + /// Returns wether the user is on the allowlist or not. Response: StatusResponse #[returns(StatusResponse)] IsAllowed { address: String }, - /// Allowlist enumerates over all addresses on the allowlist. Response: AllowlistResponse + /// Enumerates over all addresses on the allowlist. Response: AllowlistResponse #[returns(AllowlistResponse)] Allowlist { start_after: Option, limit: Option, }, - /// Returns whether features that require MsgBeforeSendHook are enabled + /// Returns whether features that require MsgBeforeSendHook are enabled. /// Most Cosmos chains do not support this feature yet. #[returns(bool)] BeforeSendHookFeaturesEnabled {}, } -// We define a custom struct for each query response +/// SudoMsg is only exposed for internal Cosmos SDK modules to call. +/// This is showing how we can expose "admin" functionality than can not be called by +/// external users or contracts, but only trusted (native/Go) code in the blockchain +#[cw_serde] +pub enum SudoMsg { + BlockBeforeSend { + from: String, + to: String, + amount: Coin, + }, +} + +/// Returns whether or not the Token Factory token is frozen and transfers +/// are disabled. #[cw_serde] pub struct IsFrozenResponse { pub is_frozen: bool, } -// We define a custom struct for each query response +/// Returns the full denomination for the Token Factory token. For example: +/// `factory/{contract address}/{subdenom}` #[cw_serde] pub struct DenomResponse { pub denom: String, } +/// Returns the current owner of this issuer contract who is allowed to +/// call priviledged methods. #[cw_serde] pub struct OwnerResponse { pub address: String, } +/// Returns a mint or burn allowance for a particular address, representing +/// the amount of tokens the account is allowed to mint or burn #[cw_serde] pub struct AllowanceResponse { pub allowance: Uint128, } +/// Information about a particular account and its mint / burn allowances. +/// Used in list queries. #[cw_serde] pub struct AllowanceInfo { pub address: String, pub allowance: Uint128, } +/// Returns a list of all mint or burn allowances #[cw_serde] pub struct AllowancesResponse { pub allowances: Vec, } +/// Whether a particular account is allowed or denied #[cw_serde] pub struct StatusResponse { pub status: bool, } +/// Account info for list queries related to allowlist and denylist #[cw_serde] pub struct StatusInfo { pub address: String, pub status: bool, } +/// Returns a list of addresses currently on the denylist. #[cw_serde] pub struct DenylistResponse { pub denylist: Vec, } +/// Returns a list of addresses currently on the allowlist #[cw_serde] pub struct AllowlistResponse { pub allowlist: Vec, diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index b96311061..4fd69487d 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -14,16 +14,19 @@ use crate::state::{ const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; +/// Returns the token denom that this contract is the admin for. Response: DenomResponse pub fn query_denom(deps: Deps) -> StdResult { let denom = DENOM.load(deps.storage)?; Ok(DenomResponse { denom }) } +/// Returns if token transfer is disabled. Response: IsFrozenResponse pub fn query_is_frozen(deps: Deps) -> StdResult { let is_frozen = IS_FROZEN.load(deps.storage)?; Ok(IsFrozenResponse { is_frozen }) } +/// Returns the owner of the contract. Response: OwnerResponse pub fn query_owner(deps: Deps) -> StdResult { let owner = OWNER.load(deps.storage)?; Ok(OwnerResponse { @@ -31,6 +34,7 @@ pub fn query_owner(deps: Deps) -> StdResult { }) } +/// Returns the mint allowance of the specified user. Response: AllowanceResponse pub fn query_mint_allowance(deps: Deps, address: String) -> StdResult { let allowance = MINTER_ALLOWANCES .may_load(deps.storage, &deps.api.addr_validate(&address)?)? @@ -38,6 +42,7 @@ pub fn query_mint_allowance(deps: Deps, address: String) -> StdResult StdResult { let allowance = BURNER_ALLOWANCES .may_load(deps.storage, &deps.api.addr_validate(&address)?)? @@ -45,6 +50,7 @@ pub fn query_burn_allowance(deps: Deps, address: String) -> StdResult, @@ -76,6 +82,7 @@ pub fn query_allowances( .collect() } +/// Enumerates over all allownances. Response: AllowancesResponse pub fn query_mint_allowances( deps: Deps, start_after: Option, @@ -86,6 +93,7 @@ pub fn query_mint_allowances( }) } +/// Enumerates over all burn allownances. Response: AllowancesResponse pub fn query_burn_allowances( deps: Deps, start_after: Option, @@ -96,6 +104,7 @@ pub fn query_burn_allowances( }) } +/// Returns wether the user is on denylist or not. Response: StatusResponse pub fn query_is_denied(deps: Deps, address: String) -> StdResult { let status = DENYLIST .load(deps.storage, &deps.api.addr_validate(&address)?) @@ -103,6 +112,7 @@ pub fn query_is_denied(deps: Deps, address: String) -> StdResult Ok(StatusResponse { status }) } +/// Returns wether the user is on the allowlist or not. Response: StatusResponse pub fn query_is_allowed(deps: Deps, address: String) -> StdResult { let status = ALLOWLIST .load(deps.storage, &deps.api.addr_validate(&address)?) @@ -110,10 +120,13 @@ pub fn query_is_allowed(deps: Deps, address: String) -> StdResult StdResult { BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage) } +/// A helper function used in list queries pub fn query_status_map( deps: Deps, start_after: Option, @@ -142,6 +155,7 @@ pub fn query_status_map( .collect() } +/// Enumerates over all addresses on the allowlist. Response: AllowlistResponse pub fn query_allowlist( deps: Deps, start_after: Option, @@ -152,6 +166,7 @@ pub fn query_allowlist( }) } +/// Enumerates over all addresses on the denylist. Response: DenylistResponse pub fn query_denylist( deps: Deps, start_after: Option, diff --git a/contracts/external/cw-tokenfactory-issuer/src/state.rs b/contracts/external/cw-tokenfactory-issuer/src/state.rs index c688949ac..1b7199d2c 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/state.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/state.rs @@ -1,7 +1,10 @@ use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; +/// Holds the owner of this contract pub const OWNER: Item = Item::new("owner"); + +/// Holds the Token Factory denom managed by this contract pub const DENOM: Item = Item::new("denom"); /// Denylist addresses prevented from transferring tokens @@ -17,6 +20,8 @@ pub const BEFORE_SEND_HOOK_FEATURES_ENABLED: Item = Item::new("hook_featur /// Whether or not token transfers are frozen pub const IS_FROZEN: Item = Item::new("is_frozen"); -/// Allowances +/// Allowances for burning pub const BURNER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("burner_allowances"); + +/// Allowances for minting pub const MINTER_ALLOWANCES: Map<&Addr, Uint128> = Map::new("minter_allowances"); From 7633b3eae56b2490e6e512d5eb730e39c0935830 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 14:54:23 -0700 Subject: [PATCH 41/59] Make it possible to unset BeforeSendHook, or set to a different contract DAOs may wish to disable the BeforeSendHook at some point, or potentially set it to a custom cosmwasm contract if they wish to customize functionality a bit more. Modifies the SetBeforeSendHook method to allow for this, and adds tests that it's indeed possible to set it to nil. --- .../cw-tokenfactory-issuer/src/contract.rs | 4 +- .../cw-tokenfactory-issuer/src/error.rs | 5 +- .../cw-tokenfactory-issuer/src/execute.rs | 29 ++++++++--- .../cw-tokenfactory-issuer/src/msg.rs | 10 +++- .../tests/cases/beforesend.rs | 4 +- .../tests/cases/instantiate.rs | 6 +-- .../tests/cases/set_before_update_hook.rs | 52 +++++++++++++++---- .../cw-tokenfactory-issuer/tests/test_env.rs | 7 ++- 8 files changed, 87 insertions(+), 30 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 9c3d023a8..cbb9a30bb 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -109,7 +109,9 @@ pub fn execute( ExecuteMsg::SetBurnerAllowance { address, allowance } => { execute::set_burner(deps, info, address, allowance) } - ExecuteMsg::SetBeforeSendHook {} => execute::set_before_send_hook(deps, env, info), + ExecuteMsg::SetBeforeSendHook { cosmwasm_address } => { + execute::set_before_send_hook(deps, env, info, cosmwasm_address) + } ExecuteMsg::SetDenomMetadata { metadata } => { execute::set_denom_metadata(deps, env, info, metadata) } diff --git a/contracts/external/cw-tokenfactory-issuer/src/error.rs b/contracts/external/cw-tokenfactory-issuer/src/error.rs index ac2a3eedc..92a6ce562 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/error.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/error.rs @@ -6,12 +6,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), - #[error("The chain you are using does not support MsgBeforeSendHook at this time. Features requiring it are disabled.")] + #[error("BeforeSendHook not set. Features requiring it are disabled.")] BeforeSendHookFeaturesDisabled {}, - #[error("MsgBeforeSendHook is already configured. Features requiring it are already enabled.")] - BeforeSendHookAlreadyEnabled {}, - #[error("The address '{address}' is denied transfer abilities")] Denied { address: String }, diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index c93ac41fa..db8735f65 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -196,6 +196,10 @@ pub fn set_denom_metadata( } /// Calls `MsgSetBeforeSendHook` and enables BeforeSendHook related features. +/// Takes a `cosmwasm_address` argument which is the address of the contract enforcing +/// the hook. Normally this will be the cw_tokenfactory_issuer contract address, but could +/// be a 3rd party address for more advanced use cases. +/// /// As not all chains support the `BeforeSendHook` in the bank module, this /// is intended to be called should chains add this feature at a later date. /// @@ -204,13 +208,27 @@ pub fn set_before_send_hook( deps: DepsMut, env: Env, info: MessageInfo, + cosmwasm_address: String, ) -> Result, ContractError> { // Only allow current contract owner check_is_contract_owner(deps.as_ref(), info.sender)?; - // Return error if BeforeSendHook already enabled - if BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage)? { - return Err(ContractError::BeforeSendHookAlreadyEnabled {}); + // The `cosmwasm_address` can be an empty string if setting the value to nil to + // disable the hook. If an empty string, we disable before send hook features. + // Otherwise, we validate the `cosmwasm_address` enable before send hook features. + // + // TODO if the address is not the same as the cw_tokenfactory_issuer contract address, + // before send hook features are also disabled. We should have a query that returns more + // contextual information. + if cosmwasm_address.is_empty() { + // Disable BeforeSendHook features + BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; + } else { + // Validate that address is a valid address + deps.api.addr_validate(&cosmwasm_address)?; + + // Enable BeforeSendHook features + BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; } // Load the Token Factory denom @@ -222,13 +240,10 @@ pub fn set_before_send_hook( let msg_set_beforesend_hook: CosmosMsg = MsgSetBeforeSendHook { sender: env.contract.address.to_string(), denom, - cosmwasm_address: env.contract.address.to_string(), + cosmwasm_address, } .into(); - // Enable BeforeSendHook features - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; - Ok(Response::new() .add_attribute("action", "set_before_send_hook") .add_message(msg_set_beforesend_hook)) diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 311357ba4..40fcc0fc0 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -59,7 +59,15 @@ pub enum ExecuteMsg { /// Attempt to SetBeforeSendHook on the token attached to this contract. /// This will fail if the token already has a SetBeforeSendHook or the chain /// still does not support it. - SetBeforeSendHook {}, + /// + /// This takes a cosmwasm_address as an argument, which is the address of the + /// contract that will be called before every token transfer. Normally, this + /// will be the issuer contract itself. + /// + /// Setting the address to an empty string will remove the SetBeforeSendHook. + /// + /// This method can only be called by the contract owner. + SetBeforeSendHook { cosmwasm_address: String }, /// Grant/revoke burn allowance. SetBurnerAllowance { address: String, allowance: Uint128 }, diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs index 164a5b973..023998141 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs @@ -40,7 +40,7 @@ fn before_send_should_block_on_frozen() { ) .unwrap_err(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\". Addresses need to be added to the allowlist to enable transfers to or from an account.: execute wasm contract failed") }); } #[test] @@ -78,7 +78,7 @@ fn allowlisted_addresses_can_transfer_when_token_frozen() { let err = env .send_tokens(other.address(), coins(10000, denom.clone()), owner) .unwrap_err(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\". Addresses need to be added to the allowlist to enable transfers to or from an account.: execute wasm contract failed") }); // Other assets are not affected env.send_tokens(other.address(), coins(10000, "uosmo"), owner) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs index 0742b2431..eff961336 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs @@ -5,7 +5,7 @@ use osmosis_test_tube::{Account, OsmosisTestApp, RunnerError}; use crate::test_env::{TestEnv, TokenfactoryIssuer}; #[test] -fn instantiate_with_new_token_shoud_set_initial_state_correctly() { +fn instantiate_with_new_token_should_set_initial_state_correctly() { let subdenom = "uthb".to_string(); let env = TestEnv::new( InstantiateMsg::NewToken { @@ -52,7 +52,7 @@ fn instantiate_with_new_token_shoud_set_initial_state_correctly() { } #[test] -fn instantiate_with_new_token_shoud_set_hook_correctly() { +fn instantiate_with_new_token_should_set_hook_correctly() { let subdenom = "uthb".to_string(); let env = TestEnv::new( InstantiateMsg::NewToken { @@ -80,7 +80,7 @@ fn instantiate_with_new_token_shoud_set_hook_correctly() { ) .unwrap_err(); - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\": execute wasm contract failed") }); + assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\". Addresses need to be added to the allowlist to enable transfers to or from an account.: execute wasm contract failed") }); } #[test] diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs index eeb7a9795..4feceab68 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs @@ -1,10 +1,11 @@ use cw_tokenfactory_issuer::msg::QueryMsg; use cw_tokenfactory_issuer::ContractError; +use osmosis_test_tube::RunnerError; use crate::test_env::{TestEnv, TokenfactoryIssuer}; #[test] -fn test_set_before_update_hook() { +fn test_set_before_send_hook() { let env = TestEnv::default(); let owner = &env.test_accs[0]; let non_owner = &env.test_accs[1]; @@ -12,7 +13,7 @@ fn test_set_before_update_hook() { // Non-owner cannot set before update hook let err = env .cw_tokenfactory_issuer - .set_before_send_hook(non_owner) + .set_before_send_hook(non_owner, env.cw_tokenfactory_issuer.contract_addr.clone()) .unwrap_err(); assert_eq!( @@ -21,15 +22,9 @@ fn test_set_before_update_hook() { ); // Owner can set before update hook, but hook is already set - let err = env - .cw_tokenfactory_issuer - .set_before_send_hook(owner) - .unwrap_err(); - - assert_eq!( - err, - TokenfactoryIssuer::execute_error(ContractError::BeforeSendHookAlreadyEnabled {}) - ); + env.cw_tokenfactory_issuer + .set_before_send_hook(owner, env.cw_tokenfactory_issuer.contract_addr.clone()) + .unwrap(); // Query before update hook let enabled: bool = env @@ -38,3 +33,38 @@ fn test_set_before_update_hook() { .unwrap(); assert!(enabled); } + +#[test] +fn test_set_before_send_hook_nil() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + + // Owner can set before update hook to nil + env.cw_tokenfactory_issuer + .set_before_send_hook(owner, "".to_string()) + .unwrap(); + + // Query before update hook, should now be disabled + let disabled: bool = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookFeaturesEnabled {}) + .unwrap(); + assert!(disabled); +} + +#[test] +fn test_set_before_send_hook_invalid_address_fails() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + + // Invalid address fails + let err = env + .cw_tokenfactory_issuer + .set_before_send_hook(owner, "invalid".to_string()) + .unwrap_err(); + + assert_eq!( + err, + RunnerError::ExecuteError { msg: "failed to execute message; message index: 0: Generic error: addr_validate errored: decoding bech32 failed: invalid bech32 string length 7: execute wasm contract failed".to_string() } + ); +} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs index f7476b7f5..568f321ed 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs @@ -270,8 +270,13 @@ impl TokenfactoryIssuer { pub fn set_before_send_hook( &self, signer: &SigningAccount, + cosmwasm_address: String, ) -> RunnerExecuteResult { - self.execute(&ExecuteMsg::SetBeforeSendHook {}, &[], signer) + self.execute( + &ExecuteMsg::SetBeforeSendHook { cosmwasm_address }, + &[], + signer, + ) } pub fn force_transfer( From 0115fe73dfed72516672e6f974623835cd3cb1d9 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 14:57:15 -0700 Subject: [PATCH 42/59] Default to BeforeSendHook features being disabled These are powerful features, but many may not want them as they can be abused. DAOs that want these features will have to explicitly enable them via a governance prop. --- .../cw-tokenfactory-issuer/src/contract.rs | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index cbb9a30bb..ab7f1887e 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -24,7 +24,6 @@ const CONTRACT_NAME: &str = "CARGO_PKG_NAME"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const CREATE_DENOM_REPLY_ID: u64 = 1; -const BEFORE_SEND_HOOK_REPLY_ID: u64 = 2; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -35,7 +34,11 @@ pub fn instantiate( ) -> Result, ContractError> { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + // Owner is the sender of the initial InstantiateMsg OWNER.save(deps.storage, &info.sender)?; + + // BeforeSendHook features are disabled by default. + BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &false)?; IS_FROZEN.save(deps.storage, &false)?; match msg { @@ -59,10 +62,6 @@ pub fn instantiate( InstantiateMsg::ExistingToken { denom } => { DENOM.save(deps.storage, &denom)?; - // BeforeSendHook cannot be set with existing tokens - // features that rely on it are disabled - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &false)?; - Ok(Response::new() .add_attribute("action", "instantiate") .add_attribute("owner", info.sender) @@ -175,7 +174,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result, ContractError> { match msg.id { @@ -183,37 +182,8 @@ pub fn reply( let MsgCreateDenomResponse { new_token_denom } = msg.result.try_into()?; DENOM.save(deps.storage, &new_token_denom)?; - // SetBeforeSendHook to this contract - // this will trigger sudo endpoint before any bank send - // which makes denylisting / freezing possible - let msg_set_beforesend_hook: CosmosMsg = MsgSetBeforeSendHook { - sender: env.contract.address.to_string(), - denom: new_token_denom.clone(), - cosmwasm_address: env.contract.address.to_string(), - } - .into(); - - Ok(Response::new() - .add_attribute("denom", new_token_denom) - .add_submessage(SubMsg::reply_always( - msg_set_beforesend_hook, - BEFORE_SEND_HOOK_REPLY_ID, - ))) - } - BEFORE_SEND_HOOK_REPLY_ID => match msg.result { - SubMsgResult::Ok(_) => { - // Enable features with BeforeSendHook requirement - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; - - Ok(Response::new().add_attribute("extra_features", "enabled")) - } - SubMsgResult::Err(_) => { - // MsgSetBeforeSendHook failed, disable extra features that require it - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &false)?; - - Ok(Response::new().add_attribute("extra_features", "disabled")) - } - }, + Ok(Response::new().add_attribute("denom", new_token_denom)) + } _ => Err(ContractError::UnknownReplyId { id: msg.id }), } } From dc7af3c2467de8385847a51da441c2d325202cc5 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 17:29:49 -0700 Subject: [PATCH 43/59] BeforeSendHook refactor and tests --- .../external/cw-tokenfactory-issuer/README.md | 39 +++++++++++-- .../schema/cw-tokenfactory-issuer.json | 40 ++++++++++--- .../cw-tokenfactory-issuer/src/contract.rs | 18 +++--- .../cw-tokenfactory-issuer/src/execute.rs | 33 ++++++++--- .../cw-tokenfactory-issuer/src/helpers.rs | 8 +-- .../cw-tokenfactory-issuer/src/msg.rs | 23 +++++--- .../cw-tokenfactory-issuer/src/queries.rs | 8 +-- .../cw-tokenfactory-issuer/src/state.rs | 11 +++- .../tests/cases/allowlist.rs | 27 +++++++++ .../tests/cases/beforesend.rs | 25 ++++++++ .../tests/cases/denylist.rs | 31 ++++++++++ .../tests/cases/freeze.rs | 15 ++++- .../tests/cases/instantiate.rs | 48 +++++----------- .../tests/cases/set_before_update_hook.rs | 57 +++++++++++++++---- .../cw-tokenfactory-issuer/tests/test_env.rs | 2 +- 15 files changed, 291 insertions(+), 94 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/README.md b/contracts/external/cw-tokenfactory-issuer/README.md index 31588df06..100273e48 100644 --- a/contracts/external/cw-tokenfactory-issuer/README.md +++ b/contracts/external/cw-tokenfactory-issuer/README.md @@ -3,13 +3,9 @@ Forked from [osmosis-labs/cw-tokenfactory-issuer](https://github.com/osmosis-labs/cw-tokenfactory-issuer). This repo contains a set of contracts that when used in conjunction with the x/tokenfactory module in Osmosis, Juno, and many other chains will enable a centrally issued stablecoin with many features: - - Creating a new Token Factory token or using an existing one - Granting and revoking allowances for the minting and burning of tokens - Updating token metadata -- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to allow transfer to or from -- Denylist to prevent certain addresses from transferring -- Force transfering tokens via the contract owner - Updating the contract owner or Token Factory admin It is intended to work on multiple chains supporting Token Factory, and has been tested on Juno Network and Osmosis. @@ -18,7 +14,40 @@ The contract has an owner (which can be removed or updated via `ExecuteMsg::Upda The contract is also the admin of the newly created Token Factory denom. For minting and burning, users then interact with the contract using its own ExecuteMsgs which trigger the contract's access control logic, and the contract then dispatches tokenfactory sdk.Msgs from its own contract account. -NOTE: this contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the denylisting of specific accounts as well as the freezing of all transfers if necessary. This feature is not enabled on every chain using Token Factory, and so denylisting and freezing features are disabled if `MsgBeforeSendHook` is not supported. DAOs wishing to leverage these features on chains after support is added can call `ExecuteMsg::SetBeforeSendHook {}`. +### Advance Features + +This contract supports a number of advanced features which DAOs or token issuers may wist to leverage: +- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to allow transfer to or from +- Denylist to prevent certain addresses from transferring +- Force transfering tokens via the contract owner + +**By default, these features are disabled**, and must be explictly enabled by the contract owner (for example via a DAO governance prop). + +Moreover, for these features to work, your chain must support the `MsgBeforeSendHook` bank module hook. This is not yet available on every chain using Token Factory, and so denylisting and freezing features are not available if `MsgBeforeSendHook` is not supported. + +On chains where `MsgBeforeSendHook` is supported, DAOs or issuers wishing to leverage these features must set the before send hook with `ExecuteMsg::SetBeforeSendHook {}`. + +This method takes a `cosmwasm_address`, which is the address of a contract implement a `SudoMsg::BlockBeforeSend` entrypoint. Normally this will be the address of the `cw_tokenfactory_issuer` contract itself, but it is possible to specify a custom contract. This contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the denylisting of specific accounts as well as the freezing of all transfers if necessary. + +Example message to set before send hook: +``` json +{ + "set_before_send_hook": { + "cosmwasm_address": "
" + } +} +``` + +DAOs or issuers wishing to leverage these features on chains without support can call `ExecuteMsg::SetBeforeSendHook {}` when support is added. + +If a DAO or issuer wishes to disable and removed before send hook related functionality, they simply need to call `ExecuteMsg::SetBeforeSendHook {}` with an empty string for the `cosmwasm_address` like so: +``` json +{ + "set_before_send_hook": { + "cosmwasm_address": "" + } +} +``` ## Instantiation diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json index 6fe4ba54c..278af0ffe 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -216,7 +216,7 @@ "additionalProperties": false }, { - "description": "Attempt to SetBeforeSendHook on the token attached to this contract. This will fail if the token already has a SetBeforeSendHook or the chain still does not support it.", + "description": "Attempt to SetBeforeSendHook on the token attached to this contract. This will fail if the chain does not support bank module hooks (many Token Factory implementations do not yet support).\n\nThis takes a cosmwasm_address as an argument, which is the address of the contract that will be called before every token transfer. Normally, this will be the issuer contract itself, though it can be a custom contract for greater flexibility.\n\nSetting the address to an empty string will remove the SetBeforeSendHook.\n\nThis method can only be called by the contract owner.", "type": "object", "required": [ "set_before_send_hook" @@ -224,6 +224,14 @@ "properties": { "set_before_send_hook": { "type": "object", + "required": [ + "cosmwasm_address" + ], + "properties": { + "cosmwasm_address": { + "type": "string" + } + }, "additionalProperties": false } }, @@ -679,13 +687,13 @@ "additionalProperties": false }, { - "description": "Returns whether features that require MsgBeforeSendHook are enabled. Most Cosmos chains do not support this feature yet.", + "description": "Returns information about the BeforeSendHook for the token. Note: many Token Factory chains do not yet support this feature.\n\nThe information returned is: - Whether features in this contract that require MsgBeforeSendHook are enabled. - The address of the BeforeSendHook contract if configured.\n\nResponse: BeforeSendHookInfo", "type": "object", "required": [ - "before_send_hook_features_enabled" + "before_send_hook_info" ], "properties": { - "before_send_hook_features_enabled": { + "before_send_hook_info": { "type": "object", "additionalProperties": false } @@ -790,10 +798,28 @@ } } }, - "before_send_hook_features_enabled": { + "before_send_hook_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Boolean", - "type": "boolean" + "title": "BeforeSendHookInfo", + "description": "Whether or not features that require MsgBeforeSendHook are enabled Many Token Factory chains do not yet support MsgBeforeSendHook", + "type": "object", + "required": [ + "advanced_features_enabled" + ], + "properties": { + "advanced_features_enabled": { + "description": "Whether or not features in this contract that require MsgBeforeSendHook are enabled.", + "type": "boolean" + }, + "hook_contract_address": { + "description": "The address of the contract that implements the BeforeSendHook interface. Most often this will be the cw_tokenfactory_issuer contract itself.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false }, "burn_allowance": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index ab7f1887e..28fe59331 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -3,13 +3,11 @@ use std::convert::TryInto; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, SubMsgResult, + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, }; use cosmwasm_std::{CosmosMsg, Reply}; use cw2::{get_contract_version, set_contract_version, ContractVersion}; -use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ - MsgCreateDenom, MsgCreateDenomResponse, MsgSetBeforeSendHook, -}; +use osmosis_std::types::osmosis::tokenfactory::v1beta1::{MsgCreateDenom, MsgCreateDenomResponse}; use token_bindings::TokenFactoryMsg; use crate::error::ContractError; @@ -17,7 +15,7 @@ use crate::execute; use crate::hooks; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; use crate::queries; -use crate::state::{BEFORE_SEND_HOOK_FEATURES_ENABLED, DENOM, IS_FROZEN, OWNER}; +use crate::state::{BeforeSendHookInfo, BEFORE_SEND_HOOK_INFO, DENOM, IS_FROZEN, OWNER}; // Version info for migration const CONTRACT_NAME: &str = "CARGO_PKG_NAME"; @@ -38,7 +36,13 @@ pub fn instantiate( OWNER.save(deps.storage, &info.sender)?; // BeforeSendHook features are disabled by default. - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &false)?; + BEFORE_SEND_HOOK_INFO.save( + deps.storage, + &BeforeSendHookInfo { + advanced_features_enabled: false, + hook_contract_address: None, + }, + )?; IS_FROZEN.save(deps.storage, &false)?; match msg { @@ -132,7 +136,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Allowlist { start_after, limit } => { to_binary(&queries::query_allowlist(deps, start_after, limit)?) } - QueryMsg::BeforeSendHookFeaturesEnabled {} => { + QueryMsg::BeforeSendHookInfo {} => { to_binary(&queries::query_before_send_hook_features(deps)?) } QueryMsg::BurnAllowance { address } => { diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index db8735f65..e3b47b538 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -8,8 +8,8 @@ use token_bindings::TokenFactoryMsg; use crate::error::ContractError; use crate::helpers::{check_before_send_hook_features_enabled, check_is_contract_owner}; use crate::state::{ - ALLOWLIST, BEFORE_SEND_HOOK_FEATURES_ENABLED, BURNER_ALLOWANCES, DENOM, DENYLIST, IS_FROZEN, - MINTER_ALLOWANCES, OWNER, + BeforeSendHookInfo, ALLOWLIST, BEFORE_SEND_HOOK_INFO, BURNER_ALLOWANCES, DENOM, DENYLIST, + IS_FROZEN, MINTER_ALLOWANCES, OWNER, }; /// Mints new tokens. To mint new tokens, the address calling this method must @@ -216,19 +216,34 @@ pub fn set_before_send_hook( // The `cosmwasm_address` can be an empty string if setting the value to nil to // disable the hook. If an empty string, we disable before send hook features. // Otherwise, we validate the `cosmwasm_address` enable before send hook features. - // - // TODO if the address is not the same as the cw_tokenfactory_issuer contract address, - // before send hook features are also disabled. We should have a query that returns more - // contextual information. if cosmwasm_address.is_empty() { // Disable BeforeSendHook features - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; + BEFORE_SEND_HOOK_INFO.save( + deps.storage, + &BeforeSendHookInfo { + advanced_features_enabled: false, + hook_contract_address: None, + }, + )?; } else { // Validate that address is a valid address deps.api.addr_validate(&cosmwasm_address)?; - // Enable BeforeSendHook features - BEFORE_SEND_HOOK_FEATURES_ENABLED.save(deps.storage, &true)?; + // If the `cosmwasm_address` is not the same as the cw_tokenfactory_issuer contract + // BeforeSendHook features are disabled. + let mut advanced_features_enabled = true; + if cosmwasm_address != env.contract.address { + advanced_features_enabled = false; + } + + // Save the BeforeSendHookInfo + BEFORE_SEND_HOOK_INFO.save( + deps.storage, + &BeforeSendHookInfo { + advanced_features_enabled, + hook_contract_address: Some(cosmwasm_address.clone()), + }, + )?; } // Load the Token Factory denom diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index a00d736d5..af2daee89 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -1,6 +1,4 @@ -use crate::state::{ - ALLOWLIST, BEFORE_SEND_HOOK_FEATURES_ENABLED, DENOM, DENYLIST, IS_FROZEN, OWNER, -}; +use crate::state::{ALLOWLIST, BEFORE_SEND_HOOK_INFO, DENOM, DENYLIST, IS_FROZEN, OWNER}; use crate::ContractError; use cosmwasm_std::{Addr, Deps}; @@ -16,8 +14,8 @@ pub fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractE /// Checks wether the BeforeSendHookFeatures gated features are enabled pub fn check_before_send_hook_features_enabled(deps: Deps) -> Result<(), ContractError> { - let enabled = BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage)?; - if !enabled { + let info = BEFORE_SEND_HOOK_INFO.load(deps.storage)?; + if !info.advanced_features_enabled { Err(ContractError::BeforeSendHookFeaturesDisabled {}) } else { Ok(()) diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 40fcc0fc0..102cb2293 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -2,6 +2,8 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Uint128}; pub use osmosis_std::types::cosmos::bank::v1beta1::{DenomUnit, Metadata}; +use crate::state::BeforeSendHookInfo; + /// The message used to create a new instance of this smart contract. #[cw_serde] pub enum InstantiateMsg { @@ -57,12 +59,13 @@ pub enum ExecuteMsg { }, /// Attempt to SetBeforeSendHook on the token attached to this contract. - /// This will fail if the token already has a SetBeforeSendHook or the chain - /// still does not support it. + /// This will fail if the chain does not support bank module hooks (many Token + /// Factory implementations do not yet support). /// /// This takes a cosmwasm_address as an argument, which is the address of the /// contract that will be called before every token transfer. Normally, this - /// will be the issuer contract itself. + /// will be the issuer contract itself, though it can be a custom contract for + /// greater flexibility. /// /// Setting the address to an empty string will remove the SetBeforeSendHook. /// @@ -156,10 +159,16 @@ pub enum QueryMsg { limit: Option, }, - /// Returns whether features that require MsgBeforeSendHook are enabled. - /// Most Cosmos chains do not support this feature yet. - #[returns(bool)] - BeforeSendHookFeaturesEnabled {}, + /// Returns information about the BeforeSendHook for the token. Note: many Token + /// Factory chains do not yet support this feature. + /// + /// The information returned is: + /// - Whether features in this contract that require MsgBeforeSendHook are enabled. + /// - The address of the BeforeSendHook contract if configured. + /// + /// Response: BeforeSendHookInfo + #[returns(BeforeSendHookInfo)] + BeforeSendHookInfo {}, } /// SudoMsg is only exposed for internal Cosmos SDK modules to call. diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index 4fd69487d..eda2f46f5 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -6,8 +6,8 @@ use crate::msg::{ DenylistResponse, IsFrozenResponse, OwnerResponse, StatusInfo, StatusResponse, }; use crate::state::{ - ALLOWLIST, BEFORE_SEND_HOOK_FEATURES_ENABLED, BURNER_ALLOWANCES, DENOM, DENYLIST, IS_FROZEN, - MINTER_ALLOWANCES, OWNER, + BeforeSendHookInfo, ALLOWLIST, BEFORE_SEND_HOOK_INFO, BURNER_ALLOWANCES, DENOM, DENYLIST, + IS_FROZEN, MINTER_ALLOWANCES, OWNER, }; // Default settings for pagination @@ -122,8 +122,8 @@ pub fn query_is_allowed(deps: Deps, address: String) -> StdResult StdResult { - BEFORE_SEND_HOOK_FEATURES_ENABLED.load(deps.storage) +pub fn query_before_send_hook_features(deps: Deps) -> StdResult { + BEFORE_SEND_HOOK_INFO.load(deps.storage) } /// A helper function used in list queries diff --git a/contracts/external/cw-tokenfactory-issuer/src/state.rs b/contracts/external/cw-tokenfactory-issuer/src/state.rs index 1b7199d2c..44abd9072 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/state.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/state.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; @@ -15,7 +16,15 @@ pub const ALLOWLIST: Map<&Addr, bool> = Map::new("allowlist"); /// Whether or not features that require MsgBeforeSendHook are enabled /// Many Token Factory chains do not yet support MsgBeforeSendHook -pub const BEFORE_SEND_HOOK_FEATURES_ENABLED: Item = Item::new("hook_features_enabled"); +#[cw_serde] +pub struct BeforeSendHookInfo { + /// Whether or not features in this contract that require MsgBeforeSendHook are enabled. + pub advanced_features_enabled: bool, + /// The address of the contract that implements the BeforeSendHook interface. + /// Most often this will be the cw_tokenfactory_issuer contract itself. + pub hook_contract_address: Option, +} +pub const BEFORE_SEND_HOOK_INFO: Item = Item::new("hook_features_enabled"); /// Whether or not token transfers are frozen pub const IS_FROZEN: Item = Item::new("is_frozen"); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs index 72e34d604..f5f6143cb 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs @@ -11,6 +11,11 @@ fn allowlist_by_owner_should_pass() { let owner = &env.test_accs[0]; let allowlistee = &env.test_accs[2]; + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + env.cw_tokenfactory_issuer .allow(&allowlistee.address(), true, owner) .unwrap(); @@ -39,8 +44,16 @@ fn allowlist_by_owner_should_pass() { #[test] fn allowlist_by_non_owern_should_fail() { let env = TestEnv::default(); + let owner = &env.test_accs[0]; let non_owner = &env.test_accs[1]; let allowlistee = &env.test_accs[2]; + + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Non-owner cannot add address to allowlist let err = env .cw_tokenfactory_issuer .allow(&allowlistee.address(), true, non_owner) @@ -62,6 +75,13 @@ fn query_allowlist_within_default_limit() { |env| { move |expected_result| { let owner = &env.test_accs[0]; + + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Allowlist the address env.cw_tokenfactory_issuer .allow(&expected_result.address, true, owner) .unwrap(); @@ -88,6 +108,13 @@ fn query_allowlist_over_default_limit() { |env| { move |expected_result| { let owner = &env.test_accs[0]; + + // Owner sets before send hook to enable allowlist feature + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Allowlist the address env.cw_tokenfactory_issuer .allow(&expected_result.address, true, owner) .unwrap(); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs index 023998141..49320bed8 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/beforesend.rs @@ -28,6 +28,11 @@ fn before_send_should_block_on_frozen() { let owner = &env.test_accs[0]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + // Freeze env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); @@ -51,6 +56,11 @@ fn allowlisted_addresses_can_transfer_when_token_frozen() { let allowlistee = &env.test_accs[1]; let other = &env.test_accs[2]; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + // Mint to owner and allowlistee env.cw_tokenfactory_issuer .set_minter(&owner.address(), 100000, owner) @@ -93,6 +103,11 @@ fn non_allowlisted_accounts_can_transfer_to_allowlisted_address_frozen() { let allowlistee = &env.test_accs[1]; let other = &env.test_accs[2]; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + // Mint to other env.cw_tokenfactory_issuer .set_minter(&owner.address(), 100000, owner) @@ -121,6 +136,11 @@ fn before_send_should_block_sending_from_denylist_address() { let denylistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + // Mint to denylistee env.cw_tokenfactory_issuer .set_minter(&owner.address(), 20000, owner) @@ -154,6 +174,11 @@ fn before_send_should_block_sending_to_denylist_address() { let denylistee = &env.test_accs[1]; let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + // Mint to self env.cw_tokenfactory_issuer .set_minter(&owner.address(), 10000, owner) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs index 7f28f54e1..be83bb21d 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs @@ -11,6 +11,11 @@ fn denylist_by_owner_should_pass() { let owner = &env.test_accs[0]; let denylistee = &env.test_accs[2]; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + env.cw_tokenfactory_issuer .deny(&denylistee.address(), true, owner) .unwrap(); @@ -39,8 +44,16 @@ fn denylist_by_owner_should_pass() { #[test] fn denylist_by_non_denylister_should_fail() { let env = TestEnv::default(); + let owner = &env.test_accs[0]; let non_owner = &env.test_accs[1]; let denylistee = &env.test_accs[2]; + + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Non-owner cannot add address to denylist let err = env .cw_tokenfactory_issuer .deny(&denylistee.address(), true, non_owner) @@ -57,6 +70,12 @@ fn set_denylist_to_issuer_itself_fails() { let env = TestEnv::default(); let owner = &env.test_accs[0]; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Owner cannot deny issuer itself let err = env .cw_tokenfactory_issuer .deny(&env.cw_tokenfactory_issuer.contract_addr, true, owner) @@ -79,6 +98,12 @@ fn query_denylist_within_default_limit() { move |expected_result| { let owner = &env.test_accs[0]; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Deny address env.cw_tokenfactory_issuer .deny(&expected_result.address, true, owner) .unwrap(); @@ -106,6 +131,12 @@ fn query_denylist_over_default_limit() { move |expected_result| { let owner = &env.test_accs[0]; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Deny address env.cw_tokenfactory_issuer .deny(&expected_result.address, true, owner) .unwrap(); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs index a25a94a25..35241f8d3 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs @@ -7,6 +7,11 @@ fn freeze_by_owener_should_pass() { let env = TestEnv::default(); let owner = &env.test_accs[0]; + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); // Should be frozen after set true @@ -29,9 +34,17 @@ fn freeze_by_owener_should_pass() { } #[test] -fn freeze_by_non_freezer_should_fail() { +fn freeze_by_non_owner_should_fail() { let env = TestEnv::default(); + let owner = &env.test_accs[0]; let non_owner = &env.test_accs[1]; + + // Owner sets before send hook to enable advanced features + env.cw_tokenfactory_issuer + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) + .unwrap(); + + // Non-owner cannot freeze let err = env .cw_tokenfactory_issuer .freeze(true, non_owner) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs index eff961336..c5cff0094 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs @@ -1,6 +1,8 @@ -use cosmwasm_std::coins; -use cw_tokenfactory_issuer::msg::InstantiateMsg; -use osmosis_test_tube::{Account, OsmosisTestApp, RunnerError}; +use cw_tokenfactory_issuer::{ + msg::{InstantiateMsg, QueryMsg}, + state::BeforeSendHookInfo, +}; +use osmosis_test_tube::{Account, OsmosisTestApp}; use crate::test_env::{TestEnv, TokenfactoryIssuer}; @@ -36,6 +38,7 @@ fn instantiate_with_new_token_should_set_initial_state_correctly() { "denom stored in contract must be `factory//`" ); + // Contract is not frozen let is_frozen = env .cw_tokenfactory_issuer .query_is_frozen() @@ -43,6 +46,13 @@ fn instantiate_with_new_token_should_set_initial_state_correctly() { .is_frozen; assert!(!is_frozen, "newly instantiated contract must not be frozen"); + // Advanced features requiring BeforeSendHook are disabled + let info: BeforeSendHookInfo = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookInfo {}) + .unwrap(); + assert!(!info.advanced_features_enabled); + let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().address; assert_eq!( owner_addr, @@ -51,38 +61,6 @@ fn instantiate_with_new_token_should_set_initial_state_correctly() { ); } -#[test] -fn instantiate_with_new_token_should_set_hook_correctly() { - let subdenom = "uthb".to_string(); - let env = TestEnv::new( - InstantiateMsg::NewToken { - subdenom: subdenom.clone(), - }, - 0, - ) - .unwrap(); - - let owner = &env.test_accs[0]; - - let denom = format!( - "factory/{}/{}", - env.cw_tokenfactory_issuer.contract_addr, subdenom - ); - - env.cw_tokenfactory_issuer.freeze(true, owner).unwrap(); - - // Bank send should fail - let err = env - .send_tokens( - env.test_accs[1].address(), - coins(10000, denom.clone()), - owner, - ) - .unwrap_err(); - - assert_eq!(err, RunnerError::ExecuteError { msg: format!("failed to execute message; message index: 0: failed to call before send hook for denom {denom}: The contract is frozen for denom \"{denom}\". Addresses need to be added to the allowlist to enable transfers to or from an account.: execute wasm contract failed") }); -} - #[test] fn instantiate_with_existing_token_should_set_initial_state_correctly() { let app = OsmosisTestApp::new(); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs index 4feceab68..a3aa982bf 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs @@ -1,6 +1,7 @@ +use cosmwasm_std::coins; use cw_tokenfactory_issuer::msg::QueryMsg; -use cw_tokenfactory_issuer::ContractError; -use osmosis_test_tube::RunnerError; +use cw_tokenfactory_issuer::{state::BeforeSendHookInfo, ContractError}; +use osmosis_test_tube::{Account, RunnerError}; use crate::test_env::{TestEnv, TokenfactoryIssuer}; @@ -13,7 +14,7 @@ fn test_set_before_send_hook() { // Non-owner cannot set before update hook let err = env .cw_tokenfactory_issuer - .set_before_send_hook(non_owner, env.cw_tokenfactory_issuer.contract_addr.clone()) + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), non_owner) .unwrap_err(); assert_eq!( @@ -23,15 +24,15 @@ fn test_set_before_send_hook() { // Owner can set before update hook, but hook is already set env.cw_tokenfactory_issuer - .set_before_send_hook(owner, env.cw_tokenfactory_issuer.contract_addr.clone()) + .set_before_send_hook(env.cw_tokenfactory_issuer.contract_addr.clone(), owner) .unwrap(); // Query before update hook - let enabled: bool = env + let info: BeforeSendHookInfo = env .cw_tokenfactory_issuer - .query(&QueryMsg::BeforeSendHookFeaturesEnabled {}) + .query(&QueryMsg::BeforeSendHookInfo {}) .unwrap(); - assert!(enabled); + assert!(info.advanced_features_enabled); } #[test] @@ -41,15 +42,15 @@ fn test_set_before_send_hook_nil() { // Owner can set before update hook to nil env.cw_tokenfactory_issuer - .set_before_send_hook(owner, "".to_string()) + .set_before_send_hook("".to_string(), owner) .unwrap(); // Query before update hook, should now be disabled - let disabled: bool = env + let info: BeforeSendHookInfo = env .cw_tokenfactory_issuer - .query(&QueryMsg::BeforeSendHookFeaturesEnabled {}) + .query(&QueryMsg::BeforeSendHookInfo {}) .unwrap(); - assert!(disabled); + assert!(!info.advanced_features_enabled); } #[test] @@ -60,7 +61,7 @@ fn test_set_before_send_hook_invalid_address_fails() { // Invalid address fails let err = env .cw_tokenfactory_issuer - .set_before_send_hook(owner, "invalid".to_string()) + .set_before_send_hook("invalid".to_string(), owner) .unwrap_err(); assert_eq!( @@ -68,3 +69,35 @@ fn test_set_before_send_hook_invalid_address_fails() { RunnerError::ExecuteError { msg: "failed to execute message; message index: 0: Generic error: addr_validate errored: decoding bech32 failed: invalid bech32 string length 7: execute wasm contract failed".to_string() } ); } + +#[test] +fn test_set_before_send_hook_to_a_different_contract() { + let env = TestEnv::default(); + let denom = env.cw_tokenfactory_issuer.query_denom().unwrap().denom; + let owner = &env.test_accs[0]; + let hook = &env.test_accs[1]; + + // Owner can set before update hook to nil + env.cw_tokenfactory_issuer + .set_before_send_hook(hook.address(), owner) + .unwrap(); + + // Query before update hook, should now be disabled + let info: BeforeSendHookInfo = env + .cw_tokenfactory_issuer + .query(&QueryMsg::BeforeSendHookInfo {}) + .unwrap(); + // Advanced features for this contract are not enabled + assert!(!info.advanced_features_enabled); + // But the hook contract address is set + assert_eq!(info.hook_contract_address.unwrap(), hook.address()); + + // Bank send should pass + env.send_tokens(hook.address(), coins(10000, "uosmo"), owner) + .unwrap(); + + // Bank send of TF denom should fail as the hook account isn't a contract + // and doesn't implement the required interface. + env.send_tokens(hook.address(), coins(10000, denom), owner) + .unwrap_err(); +} diff --git a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs index 568f321ed..3e3691f1f 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs @@ -269,8 +269,8 @@ impl TokenfactoryIssuer { pub fn set_before_send_hook( &self, - signer: &SigningAccount, cosmwasm_address: String, + signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( &ExecuteMsg::SetBeforeSendHook { cosmwasm_address }, From 508d81c9d0597df06e26a0c3452c599054b56f69 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 5 Sep 2023 17:43:26 -0700 Subject: [PATCH 44/59] Cleanup TODO, verify correct error --- .../src/tests/test_tube/integration_tests.rs | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs index 67fe8d18d..80555688a 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs @@ -213,38 +213,43 @@ fn test_instantiate_invalid_active_threshold_count_fails() { .init_account(&[Coin::new(100000000000, "uosmo")]) .unwrap(); - // TODO verify error is correct - env.instantiate( - &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, - token_info: TokenInfo::New(NewTokenInfo { - subdenom: "cat".to_string(), - metadata: Some(NewDenomMetadata { - description: "Awesome token, get it meow!".to_string(), - additional_denom_units: Some(vec![DenomUnit { - denom: "cat".to_string(), - // Exponent 0 is automatically set - exponent: 0, - aliases: vec![], - }]), - display: "cat".to_string(), - name: "Cat Token".to_string(), - symbol: "CAT".to_string(), + let err = env + .instantiate( + &InstantiateMsg { + token_issuer_code_id: tf_issuer_id, + token_info: TokenInfo::New(NewTokenInfo { + subdenom: "cat".to_string(), + metadata: Some(NewDenomMetadata { + description: "Awesome token, get it meow!".to_string(), + additional_denom_units: Some(vec![DenomUnit { + denom: "cat".to_string(), + // Exponent 0 is automatically set + exponent: 0, + aliases: vec![], + }]), + display: "cat".to_string(), + name: "Cat Token".to_string(), + symbol: "CAT".to_string(), + }), + initial_balances: vec![InitialBalance { + amount: Uint128::new(100), + address: env.accounts[0].address(), + }], + initial_dao_balance: None, }), - initial_balances: vec![InitialBalance { - amount: Uint128::new(100), - address: env.accounts[0].address(), - }], - initial_dao_balance: None, - }), - unstaking_duration: None, - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(1000), - }), - }, - dao, - ) - .unwrap_err(); + unstaking_duration: None, + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(1000), + }), + }, + dao, + ) + .unwrap_err(); + + assert_eq!( + err, + TfDaoVotingContract::execute_submessage_error(ContractError::InvalidAbsoluteCount {}) + ); } #[test] From 8c54bf23d13407a4f0db38c49d7aeb6a0009fa3d Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 8 Sep 2023 18:55:27 -0700 Subject: [PATCH 45/59] Accurate comment --- contracts/external/cw-tokenfactory-issuer/src/contract.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index 28fe59331..a90770aac 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -52,8 +52,7 @@ pub fn instantiate( .add_attribute("owner", info.sender) .add_attribute("subdenom", subdenom.clone()) .add_submessage( - // create new denom, if denom is created successfully, - // set beforesend listener to this contract on reply + // Create new denom, denom info is saved in the reply SubMsg::reply_on_success( >::from(MsgCreateDenom { sender: env.contract.address.to_string(), From fa1a74a96acd4ce2cd7e1c625c9a3a3dade2b9d7 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 8 Sep 2023 19:37:24 -0700 Subject: [PATCH 46/59] Improve active threshold validation reuse --- .../src/contract.rs | 47 ++++++------------- .../src/error.rs | 19 +++----- packages/dao-voting/src/threshold.rs | 32 +++++++++++++ 3 files changed, 54 insertions(+), 44 deletions(-) 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 efb7ead0c..9148336f8 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -2,8 +2,8 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Order, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, + coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, + Order, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_controllers::ClaimsResponse; @@ -18,7 +18,10 @@ use dao_interface::voting::{ }; use dao_voting::{ duration::validate_duration, - threshold::{ActiveThreshold, ActiveThresholdResponse}, + threshold::{ + assert_valid_absolute_count_threshold, assert_valid_percentage_threshold, ActiveThreshold, + ActiveThresholdResponse, + }, }; use crate::error::ContractError; @@ -68,9 +71,7 @@ pub fn instantiate( // We will check Absolute count (if configured) later for both existing // and new tokens. if let ActiveThreshold::Percentage { percent } = active_threshold { - if *percent > Decimal::percent(100) || *percent <= Decimal::percent(0) { - return Err(ContractError::InvalidActivePercentage {}); - } + assert_valid_percentage_threshold(*percent)?; } ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; } @@ -82,7 +83,8 @@ pub fn instantiate( TokenInfo::Existing { denom } => { // Validate active threshold absolute count if configured if let Some(ActiveThreshold::AbsoluteCount { count }) = msg.active_threshold { - assert_valid_absolute_count_threshold(deps.as_ref(), &denom, count)?; + let supply: Coin = deps.querier.query_supply(denom.clone())?; + assert_valid_absolute_count_threshold(count, supply.amount)?; } DENOM.save(deps.storage, &denom)?; @@ -132,21 +134,6 @@ pub fn instantiate( } } -pub fn assert_valid_absolute_count_threshold( - deps: Deps, - token_denom: &str, - count: Uint128, -) -> Result<(), ContractError> { - if count.is_zero() { - return Err(ContractError::ZeroActiveCount {}); - } - let supply: Coin = deps.querier.query_supply(token_denom.to_string())?; - if count > supply.amount { - return Err(ContractError::InvalidAbsoluteCount {}); - } - Ok(()) -} - #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -328,13 +315,12 @@ pub fn execute_update_active_threshold( if let Some(active_threshold) = new_active_threshold { match active_threshold { ActiveThreshold::Percentage { percent } => { - if percent > Decimal::percent(100) || percent.is_zero() { - return Err(ContractError::InvalidActivePercentage {}); - } + assert_valid_percentage_threshold(percent)?; } ActiveThreshold::AbsoluteCount { count } => { let denom = DENOM.load(deps.storage)?; - assert_valid_absolute_count_threshold(deps.as_ref(), &denom, count)?; + let supply: Coin = deps.querier.query_supply(denom.to_string())?; + assert_valid_absolute_count_threshold(count, supply.amount)?; } } ACTIVE_THRESHOLD.save(deps.storage, &active_threshold)?; @@ -601,12 +587,9 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result initial_supply { - return Err(ContractError::InvalidAbsoluteCount {}); - } + // We use initial_supply here because the DAO balance is not + // able to be staked by users. + assert_valid_absolute_count_threshold(count, initial_supply)?; } // Cannot instantiate with no initial token owners because it would 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 9881975c2..27bea213a 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/error.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/error.rs @@ -1,5 +1,6 @@ use cosmwasm_std::StdError; use cw_utils::{ParseReplyError, PaymentError}; +use dao_voting::threshold::ActiveThresholdError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -8,22 +9,19 @@ pub enum ContractError { Std(#[from] StdError), #[error(transparent)] - PaymentError(#[from] PaymentError), - - #[error(transparent)] - ParseReplyError(#[from] ParseReplyError), + ActiveThresholdError(#[from] ActiveThresholdError), #[error(transparent)] HookError(#[from] cw_hooks::HookError), #[error(transparent)] - UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + PaymentError(#[from] PaymentError), - #[error("Absolute count threshold cannot be greater than the total token supply")] - InvalidAbsoluteCount {}, + #[error(transparent)] + ParseReplyError(#[from] ParseReplyError), - #[error("Active threshold percentage must be greater than 0 and less than 1")] - InvalidActivePercentage {}, + #[error(transparent)] + UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), #[error("Initial governance token balances must not be empty")] InitialBalancesError {}, @@ -43,9 +41,6 @@ pub enum ContractError { #[error("Got a submessage reply with unknown id: {id}")] UnknownReplyId { id: u64 }, - #[error("Active threshold count must be greater than zero")] - ZeroActiveCount {}, - #[error("Amount being unstaked must be non-zero")] ZeroUnstake {}, } diff --git a/packages/dao-voting/src/threshold.rs b/packages/dao-voting/src/threshold.rs index f22fb9849..e27e46c7b 100644 --- a/packages/dao-voting/src/threshold.rs +++ b/packages/dao-voting/src/threshold.rs @@ -23,6 +23,38 @@ pub struct ActiveThresholdResponse { pub active_threshold: Option, } +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ActiveThresholdError { + #[error("Absolute count threshold cannot be greater than the total token supply")] + InvalidAbsoluteCount {}, + + #[error("Active threshold percentage must be greater than 0 and less than 1")] + InvalidActivePercentage {}, + + #[error("Active threshold count must be greater than zero")] + ZeroActiveCount {}, +} + +pub fn assert_valid_absolute_count_threshold( + count: Uint128, + supply: Uint128, +) -> Result<(), ActiveThresholdError> { + if count.is_zero() { + return Err(ActiveThresholdError::ZeroActiveCount {}); + } + if count > supply { + return Err(ActiveThresholdError::InvalidAbsoluteCount {}); + } + Ok(()) +} + +pub fn assert_valid_percentage_threshold(percent: Decimal) -> Result<(), ActiveThresholdError> { + if percent.is_zero() || percent > Decimal::one() { + return Err(ActiveThresholdError::InvalidActivePercentage {}); + } + Ok(()) +} + #[derive(Error, Debug, PartialEq, Eq)] pub enum ThresholdError { #[error("Required threshold cannot be zero")] From 71137e860e1330368cd5d5850e56d15435188a6e Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 8 Sep 2023 19:12:11 -0700 Subject: [PATCH 47/59] Don't create issuer for existing tokens --- .../src/testing/do_votes.rs | 1 - .../src/testing/tests.rs | 1 - .../dao-voting-native-staked/src/contract.rs | 1 - .../src/contract.rs | 20 +--------- .../src/tests/multitest/tests.rs | 38 +++++-------------- 5 files changed, 12 insertions(+), 49 deletions(-) diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs index 087324320..c1430c2f9 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs @@ -240,7 +240,6 @@ where match should_execute { ShouldExecute::Yes => { if res.is_err() { - println!("{:?}", res.err()); panic!() } // Check that the vote was recorded correctly. diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs index d9b98ce04..9ffcd709b 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs @@ -815,7 +815,6 @@ fn test_active_threshold_percent_rounds_up() { .wrap() .query_wasm_smart(voting_addr.clone(), &QueryMsg::IsActive {}) .unwrap(); - println!("{:?}", is_active); assert!(!is_active.active); // Stake 1 more token as creator, should now be active. diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs index 446720f06..4e019d3e3 100644 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ b/contracts/voting/dao-voting-native-staked/src/contract.rs @@ -53,7 +53,6 @@ pub fn instantiate( // Validate denom by checking supply let supply: Coin = deps.querier.query_supply(msg.denom.to_string())?; - println!("supply {:?}", supply); if Uint128::is_zero(&supply.amount) { return Err(ContractError::InvalidDenom {}); } 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 9148336f8..3d2847e78 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/contract.rs @@ -89,26 +89,10 @@ pub fn instantiate( DENOM.save(deps.storage, &denom)?; - // Instantiate cw-token-factory-issuer contract - // DAO (sender) is set as admin - let issuer_instantiate_msg = SubMsg::reply_on_success( - WasmMsg::Instantiate { - admin: Some(info.sender.to_string()), - code_id: msg.token_issuer_code_id, - msg: to_binary(&IssuerInstantiateMsg::ExistingToken { - denom: denom.clone(), - })?, - funds: info.funds, - label: "cw-tokenfactory-issuer".to_string(), - }, - INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, - ); - Ok(Response::new() .add_attribute("action", "instantiate") .add_attribute("token", "existing_token") - .add_attribute("denom", denom) - .add_submessage(issuer_instantiate_msg)) + .add_attribute("denom", denom)) } TokenInfo::New(token) => { // Tnstantiate cw-token-factory-issuer contract @@ -557,7 +541,7 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { - // Not much to do here. + // TODO this should never be called? Throw error? Unreachable? Ok( Response::new() .add_attribute("cw-tokenfactory-issuer-address", issuer_addr), diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs index f0091e70f..9ddc5824f 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs +++ b/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs @@ -1,7 +1,7 @@ use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; use crate::msg::{ - ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, - StakerBalanceResponse, TokenInfo, + DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, + QueryMsg, StakerBalanceResponse, TokenInfo, }; use crate::state::Config; use cosmwasm_std::testing::{mock_dependencies, mock_env}; @@ -246,11 +246,16 @@ fn test_instantiate_existing() { }, ); - let token_contract: Addr = app + let denom: DenomResponse = app .wrap() - .query_wasm_smart(addr, &QueryMsg::TokenContract {}) + .query_wasm_smart(addr, &QueryMsg::Denom {}) .unwrap(); - assert_eq!(token_contract, Addr::unchecked("contract3")); + assert_eq!( + denom, + DenomResponse { + denom: DENOM.to_string() + } + ); } #[test] @@ -726,29 +731,6 @@ fn test_query_info() { ); } -#[test] -fn test_query_token_contract() { - let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - token_issuer_code_id: issuer_id, - token_info: TokenInfo::Existing { - denom: DENOM.to_string(), - }, - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::TokenContract {}; - let res: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(res, Addr::unchecked("contract1")); -} - #[test] fn test_query_claims() { let mut app = mock_app(); From b6f9c04da511f354bcf95795d35fde03640fa1cc Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 8 Sep 2023 20:42:52 -0700 Subject: [PATCH 48/59] Reorg so we can have only one native token voting contract --- Cargo.lock | 397 +++-- Cargo.toml | 7 +- .../proposal/dao-proposal-multiple/Cargo.toml | 2 +- .../src/testing/instantiate.rs | 4 +- .../proposal/dao-proposal-single/Cargo.toml | 2 +- .../src/testing/contracts.rs | 6 +- .../src/testing/instantiate.rs | 8 +- .../dao-voting-native-staked/Cargo.toml | 38 - .../voting/dao-voting-native-staked/README.md | 13 - .../schema/dao-voting-native-staked.json | 993 ------------ .../dao-voting-native-staked/src/contract.rs | 509 ------- .../dao-voting-native-staked/src/error.rs | 45 - .../dao-voting-native-staked/src/msg.rs | 84 - .../dao-voting-native-staked/src/state.rs | 46 - .../dao-voting-native-staked/src/tests.rs | 1355 ----------------- .../.cargo/config | 4 - .../examples/schema.rs | 11 - .../src/lib.rs | 11 - .../.cargo/config | 0 .../Cargo.toml | 2 +- .../README.md | 24 +- .../examples/schema.rs | 2 +- .../dao-voting-token-factory-staked.json | 4 +- .../src/contract.rs | 2 +- .../src/error.rs | 0 .../src/lib.rs | 0 .../src/msg.rs | 1 - .../src/state.rs | 0 .../src/tests/mod.rs | 0 .../src/tests/multitest/mod.rs | 0 .../src/tests/multitest/tests.rs | 5 +- .../src/tests/multitest/tf_module_mock.rs | 0 .../src/tests/test_tube/integration_tests.rs | 0 .../src/tests/test_tube/mod.rs | 0 .../src/tests/test_tube/test_env.rs | 4 +- packages/dao-testing/Cargo.toml | 3 +- packages/dao-testing/src/contracts.rs | 6 +- scripts/publish.sh | 2 +- 38 files changed, 292 insertions(+), 3298 deletions(-) delete mode 100644 contracts/voting/dao-voting-native-staked/Cargo.toml delete mode 100644 contracts/voting/dao-voting-native-staked/README.md delete mode 100644 contracts/voting/dao-voting-native-staked/schema/dao-voting-native-staked.json delete mode 100644 contracts/voting/dao-voting-native-staked/src/contract.rs delete mode 100644 contracts/voting/dao-voting-native-staked/src/error.rs delete mode 100644 contracts/voting/dao-voting-native-staked/src/msg.rs delete mode 100644 contracts/voting/dao-voting-native-staked/src/state.rs delete mode 100644 contracts/voting/dao-voting-native-staked/src/tests.rs delete mode 100644 contracts/voting/dao-voting-token-factory-staked/.cargo/config delete mode 100644 contracts/voting/dao-voting-token-factory-staked/examples/schema.rs delete mode 100644 contracts/voting/dao-voting-token-factory-staked/src/lib.rs rename contracts/voting/{dao-voting-native-staked => dao-voting-token-staked}/.cargo/config (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/Cargo.toml (98%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/README.md (66%) rename contracts/voting/{dao-voting-native-staked => dao-voting-token-staked}/examples/schema.rs (68%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/schema/dao-voting-token-factory-staked.json (99%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/contract.rs (99%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/error.rs (100%) rename contracts/voting/{dao-voting-native-staked => dao-voting-token-staked}/src/lib.rs (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/msg.rs (97%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/state.rs (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/tests/mod.rs (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/tests/multitest/mod.rs (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/tests/multitest/tests.rs (99%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/tests/multitest/tf_module_mock.rs (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/tests/test_tube/integration_tests.rs (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/tests/test_tube/mod.rs (100%) rename contracts/voting/{dao-voting-token-factory-staked => dao-voting-token-staked}/src/tests/test_tube/test_env.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index b8807dbfa..3e5b50203 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -79,7 +79,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -165,6 +165,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -214,7 +220,7 @@ checksum = "b30ed1d6f8437a487a266c8293aeb95b61a23261273e3e02912cdb8b68bf798b" dependencies = [ "bs58", "hmac", - "k256", + "k256 0.11.6", "once_cell", "pbkdf2", "rand_core 0.6.4", @@ -256,9 +262,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" +checksum = "128a44527fc0d6abf05f9eda748b9027536e12dff93f5acc8449f51583309350" [[package]] name = "bootstrap-env" @@ -306,9 +312,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] @@ -339,9 +345,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "num-traits", ] @@ -497,10 +503,10 @@ checksum = "3903590099dcf1ea580d9353034c9ba1dbf55d1389a5bd2ade98535c3445d1f9" dependencies = [ "bip32", "cosmos-sdk-proto 0.14.0", - "ecdsa", + "ecdsa 0.14.8", "eyre", "getrandom", - "k256", + "k256 0.11.6", "rand_core 0.6.4", "serde", "serde_json", @@ -518,10 +524,10 @@ checksum = "6fa07096219b1817432b8f1e47c22e928c64bbfd231fc08f0a98f0e7ddd602b7" dependencies = [ "bip32", "cosmos-sdk-proto 0.15.0", - "ecdsa", + "ecdsa 0.14.8", "eyre", "getrandom", - "k256", + "k256 0.11.6", "rand_core 0.6.4", "serde", "serde_json", @@ -533,31 +539,31 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871ce1d5a4b00ed1741f84b377eec19fadd81a904a227bc1e268d76539d26f5e" +checksum = "1ca101fbf2f76723711a30ea3771ef312ec3ec254ad021b237871ed802f9f175" dependencies = [ "digest 0.10.7", "ed25519-zebra", - "k256", + "k256 0.13.1", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce8b44b45a7c8c6d6f770cd0a51458c2445c7c15b6115e1d215fa35c77b305c" +checksum = "c73d2dd292f60e42849d2b07c03d809cf31e128a4299a805abd6d24553bcaaf5" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99222fa0401ee36389550d8a065700380877a2299c3043d24c38d705708c9d9d" +checksum = "6ce34a08020433989af5cc470104f6bd22134320fe0221bd8aeb919fd5ec92d5" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -568,9 +574,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b74eaf9e585ef8e5e3486b240b13ee593cb0f658b5879696937d8c22243d4fb" +checksum = "96694ec781a7dd6dea1f968a2529ade009c21ad999c88b5f53d6cc495b3b96f7" dependencies = [ "proc-macro2", "quote", @@ -579,11 +585,11 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da78abcf059181e8cb01e95e5003cf64fe95dde6c72b3fe37e5cabc75cdba32a" +checksum = "2a44d3f9c25b2f864737c6605a98f2e4675d53fd8bbc7cf4d7c02475661a793d" dependencies = [ - "base64 0.13.1", + "base64 0.21.3", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -599,9 +605,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b52be0d56b78f502f3acb75e40908a0d04d9f265b6beb0f86b36ec2ece048748" +checksum = "ab544dfcad7c9e971933d522d99ec75cc8ddfa338854bb992b092e11bcd7e818" dependencies = [ "cosmwasm-std", "serde", @@ -628,6 +634,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -849,7 +867,7 @@ dependencies = [ "cw-utils 1.0.1", "derivative", "itertools", - "k256", + "k256 0.11.6", "prost 0.9.0", "schemars", "serde", @@ -1027,7 +1045,7 @@ dependencies = [ "cw-multi-test", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "osmosis-std 0.16.2", + "osmosis-std", "osmosis-test-tube", "prost 0.11.9", "schemars", @@ -1884,7 +1902,7 @@ dependencies = [ "dao-voting-cw20-staked", "dao-voting-cw4", "dao-voting-cw721-staked", - "dao-voting-native-staked", + "dao-voting-token-staked", "rand", "thiserror", ] @@ -1925,7 +1943,7 @@ dependencies = [ "dao-voting-cw20-staked", "dao-voting-cw4", "dao-voting-cw721-staked", - "dao-voting-native-staked", + "dao-voting-token-staked", "thiserror", ] @@ -1978,9 +1996,8 @@ dependencies = [ "dao-voting-cw4", "dao-voting-cw721-roles", "dao-voting-cw721-staked", - "dao-voting-native-staked", - "dao-voting-token-factory-staked", - "osmosis-std 0.16.2", + "dao-voting-token-staked", + "osmosis-std", "osmosis-test-tube", "rand", "serde", @@ -2126,30 +2143,7 @@ dependencies = [ ] [[package]] -name = "dao-voting-native-staked" -version = "2.2.0" -dependencies = [ - "anyhow", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-controllers 1.1.0", - "cw-hooks", - "cw-multi-test", - "cw-paginate-storage 2.2.0", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", - "cw2 1.1.0", - "dao-dao-macros", - "dao-hooks", - "dao-interface", - "dao-proposal-hook-counter", - "dao-voting 2.2.0", - "thiserror", -] - -[[package]] -name = "dao-voting-token-factory-staked" +name = "dao-voting-token-staked" version = "2.2.0" dependencies = [ "anyhow", @@ -2170,7 +2164,7 @@ dependencies = [ "dao-proposal-hook-counter", "dao-testing", "dao-voting 2.2.0", - "osmosis-std 0.17.0-rc0", + "osmosis-std", "osmosis-test-tube", "serde", "thiserror", @@ -2187,6 +2181,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -2214,6 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -2236,10 +2241,24 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der 0.7.8", + "digest 0.10.7", + "elliptic-curve 0.13.5", + "rfc6979 0.4.0", + "signature 2.1.0", + "spki 0.7.2", ] [[package]] @@ -2248,7 +2267,7 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", ] [[package]] @@ -2290,16 +2309,35 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", - "der", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.3", "digest 0.10.7", - "ff", + "ff 0.13.0", "generic-array", - "group", - "pkcs8", + "group 0.13.0", + "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -2386,6 +2424,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "flex-error" version = "0.4.4" @@ -2473,7 +2521,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -2514,6 +2562,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2547,7 +2596,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] @@ -2640,6 +2700,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -2888,12 +2957,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", "sha2 0.10.7", "sha3", ] +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa 0.16.8", + "elliptic-curve 0.13.5", + "once_cell", + "sha2 0.10.7", + "signature 2.1.0", +] + [[package]] name = "keccak" version = "0.1.4" @@ -3044,9 +3127,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -3087,28 +3170,13 @@ checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "osmosis-std" -version = "0.16.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=autobuild-v17.0.0-rc0#36ef03c6d61b860c6bd25cac56bc52144c4e6b9c" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive 0.16.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=autobuild-v17.0.0-rc0)", - "prost 0.11.9", - "prost-types", - "schemars", - "serde", - "serde-cw-value", -] - -[[package]] -name = "osmosis-std" -version = "0.17.0-rc0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b022b748710ecdf1adc6a124c3bef29f17ef05e7fa1260a08889d1d53f9cc5" +checksum = "088997c4da871db9edb9e090a4e164d1cb12498781491ccb8030246287400300" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", + "osmosis-std-derive", "prost 0.11.9", "prost-types", "schemars", @@ -3129,29 +3197,17 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "osmosis-std-derive" -version = "0.16.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=autobuild-v17.0.0-rc0#36ef03c6d61b860c6bd25cac56bc52144c4e6b9c" -dependencies = [ - "itertools", - "proc-macro2", - "prost-types", - "quote", - "syn 1.0.109", -] - [[package]] name = "osmosis-test-tube" -version = "17.0.0-rc0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977a2b4f088dd704a47e96b5914e28465cfcdb1cb1145a1f9b45c219a9b145c5" +checksum = "d176b76c0142f7a047d4c4f12a553b10de557aa60304077ca88532249924da0f" dependencies = [ "base64 0.13.1", "bindgen", "cosmrs 0.9.0", "cosmwasm-std", - "osmosis-std 0.17.0-rc0", + "osmosis-std", "prost 0.11.9", "serde", "serde_json", @@ -3250,7 +3306,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -3281,7 +3337,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -3302,8 +3358,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.2", ] [[package]] @@ -3468,11 +3534,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hmac", "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -3649,10 +3725,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", - "der", + "base16ct 0.1.1", + "der 0.6.1", "generic-array", - "pkcs8", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.8", + "generic-array", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -3730,7 +3820,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -3763,7 +3853,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -3891,9 +3981,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signature" @@ -3905,6 +3995,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "slab" version = "0.4.9" @@ -3947,7 +4047,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.8", ] [[package]] @@ -4042,9 +4152,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -4069,7 +4179,7 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", + "k256 0.11.6", "num-traits", "once_cell", "prost 0.11.9", @@ -4080,7 +4190,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.9.9", - "signature", + "signature 1.6.4", "subtle", "subtle-encoding", "tendermint-proto 0.23.9", @@ -4100,7 +4210,7 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", + "k256 0.11.6", "num-traits", "once_cell", "prost 0.11.9", @@ -4111,7 +4221,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.9.9", - "signature", + "signature 1.6.4", "subtle", "subtle-encoding", "tendermint-proto 0.26.0", @@ -4300,14 +4410,14 @@ dependencies = [ [[package]] name = "test-tube" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b1f7cafdf7738331999fb1465d2d3032f08ac61940e1ef4601dbbef21d6a5e" +checksum = "04de0d85f2438f0b64a5c135a1524564f2b89263cfbce011542446b6681d006f" dependencies = [ "base64 0.13.1", "cosmrs 0.9.0", "cosmwasm-std", - "osmosis-std 0.17.0-rc0", + "osmosis-std", "prost 0.11.9", "serde", "serde_json", @@ -4337,7 +4447,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -4419,7 +4529,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -4579,7 +4689,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -4689,9 +4799,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -4733,7 +4843,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", "wasm-bindgen-shared", ] @@ -4755,7 +4865,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4797,13 +4907,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -4941,5 +5052,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] diff --git a/Cargo.toml b/Cargo.toml index be9eab9a9..0d55ba587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,8 +54,8 @@ cw721 = "0.18" cw721-base = "0.18" env_logger = "0.10" once_cell = "1.18" -osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", branch = "autobuild-v17.0.0-rc0" } -osmosis-test-tube = "17.0.0-rc0" +osmosis-std = "0.19.0" +osmosis-test-tube = "19.0.0" proc-macro2 = "1.0" prost = "0.11" quote = "1.0" @@ -112,8 +112,7 @@ dao-voting-cw20-staked = { path = "./contracts/voting/dao-voting-cw20-staked", v dao-voting-cw4 = { path = "./contracts/voting/dao-voting-cw4", version = "2.2.0" } dao-voting-cw721-roles = { path = "./contracts/voting/dao-voting-cw721-roles", version = "*" } dao-voting-cw721-staked = { path = "./contracts/voting/dao-voting-cw721-staked", version = "2.2.0" } -dao-voting-native-staked = { path = "./contracts/voting/dao-voting-native-staked", version = "2.2.0" } -dao-voting-token-factory-staked = { path = "./contracts/voting/dao-voting-token-factory-staked", version = "2.2.0" } +dao-voting-token-staked = { path = "./contracts/voting/dao-voting-token-staked", version = "2.2.0" } # v1 dependencies. used for state migrations. cw-core-v1 = { package = "cw-core", version = "0.1.0" } diff --git a/contracts/proposal/dao-proposal-multiple/Cargo.toml b/contracts/proposal/dao-proposal-multiple/Cargo.toml index 1d090e620..a7016b1ee 100644 --- a/contracts/proposal/dao-proposal-multiple/Cargo.toml +++ b/contracts/proposal/dao-proposal-multiple/Cargo.toml @@ -47,7 +47,7 @@ cw-multi-test = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-cw20-balance = { workspace = true } dao-voting-cw20-staked = { workspace = true } -dao-voting-native-staked = { workspace = true } +dao-voting-token-staked = { workspace = true } dao-voting-cw721-staked = { workspace = true } cw-denom = { workspace = true } dao-testing = { workspace = true } diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index 45a76e9d2..fd3ea9aa5 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -265,7 +265,7 @@ pub fn _instantiate_with_native_staked_balances_governance( automatically_add_cw721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { code_id: native_stake_id, - msg: to_binary(&dao_voting_native_staked::msg::InstantiateMsg { + msg: to_binary(&dao_voting_token_staked::msg::InstantiateMsg { denom: "ujuno".to_string(), unstaking_duration: None, active_threshold: None, @@ -316,7 +316,7 @@ pub fn _instantiate_with_native_staked_balances_governance( app.execute_contract( Addr::unchecked(&address), native_staking_addr.clone(), - &dao_voting_native_staked::msg::ExecuteMsg::Stake {}, + &dao_voting_token_staked::msg::ExecuteMsg::Stake {}, &[Coin { amount, denom: "ujuno".to_string(), diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index 4ef03f069..eb23de7f3 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -45,7 +45,7 @@ dao-dao-core = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-cw20-balance = { workspace = true } dao-voting-cw20-staked = { workspace = true } -dao-voting-native-staked = { workspace = true } +dao-voting-token-staked = { workspace = true } dao-voting-cw721-staked = { workspace = true } dao-pre-propose-single = { workspace = true } cw-denom = { workspace = true } diff --git a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs index f27bd7e35..345cf5a3b 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs @@ -82,9 +82,9 @@ pub(crate) fn cw20_staked_balances_voting_contract() -> Box> pub(crate) fn native_staked_balances_voting_contract() -> Box> { let contract = ContractWrapper::new( - dao_voting_native_staked::contract::execute, - dao_voting_native_staked::contract::instantiate, - dao_voting_native_staked::contract::query, + dao_voting_token_staked::contract::execute, + dao_voting_token_staked::contract::instantiate, + dao_voting_token_staked::contract::query, ); Box::new(contract) } diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 8f1756edf..eb8830b7a 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -263,8 +263,10 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( automatically_add_cw721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { code_id: native_stake_id, - msg: to_binary(&dao_voting_native_staked::msg::InstantiateMsg { - denom: "ujuno".to_string(), + msg: to_binary(&dao_voting_token_staked::msg::InstantiateMsg { + token_info: dao_voting_token_staked::msg::TokenInfo::Existing { + denom: "ujuno".to_string(), + }, unstaking_duration: None, active_threshold: None, }) @@ -313,7 +315,7 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( app.execute_contract( Addr::unchecked(&address), native_staking_addr.clone(), - &dao_voting_native_staked::msg::ExecuteMsg::Stake {}, + &dao_voting_token_staked::msg::ExecuteMsg::Stake {}, &[Coin { amount, denom: "ujuno".to_string(), diff --git a/contracts/voting/dao-voting-native-staked/Cargo.toml b/contracts/voting/dao-voting-native-staked/Cargo.toml deleted file mode 100644 index 25eac3906..000000000 --- a/contracts/voting/dao-voting-native-staked/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "dao-voting-native-staked" -authors = ["Callum Anderson ", "Jake Hartnell "] -description = "A DAO DAO voting module based on staked native tokens. If your chain uses Token Factory, consider using dao-voting-token-factory-staked for additional functionality including creating new tokens." -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } -cosmwasm-schema = { workspace = true } -cosmwasm-storage = { workspace = true } -cw2 = { workspace = true } -cw-controllers = { workspace = true } -cw-hooks = { workspace = true } -cw-paginate-storage = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -dao-dao-macros = { workspace = true } -dao-hooks = { workspace = true } -dao-interface = { workspace = true } -dao-voting = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -dao-proposal-hook-counter = { workspace = true } diff --git a/contracts/voting/dao-voting-native-staked/README.md b/contracts/voting/dao-voting-native-staked/README.md deleted file mode 100644 index d91c34d39..000000000 --- a/contracts/voting/dao-voting-native-staked/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# CW Native Staked Balance Voting - -[![dao-voting-native-staked on crates.io](https://img.shields.io/crates/v/dao-voting-native-staked.svg?logo=rust)](https://crates.io/crates/dao-voting-native-staked) -[![docs.rs](https://img.shields.io/docsrs/dao-voting-native-staked?logo=docsdotrs)](https://docs.rs/dao-voting-native-staked/latest/dao_voting_native_staked/) - -Simple native token voting contract which assumes the native denom -provided is not used for staking for securing the network e.g. IBC -denoms or secondary tokens (ION). Staked balances may be queried at an -arbitrary height. This contract implements the interface needed to be a DAO -DAO [voting -module](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design#the-voting-module). - -If your chain uses Token Factory, consider using `dao-voting-token-factory-staked` for additional functionality including creating new tokens. diff --git a/contracts/voting/dao-voting-native-staked/schema/dao-voting-native-staked.json b/contracts/voting/dao-voting-native-staked/schema/dao-voting-native-staked.json deleted file mode 100644 index ae2140695..000000000 --- a/contracts/voting/dao-voting-native-staked/schema/dao-voting-native-staked.json +++ /dev/null @@ -1,993 +0,0 @@ -{ - "contract_name": "dao-voting-native-staked", - "contract_version": "2.2.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "denom" - ], - "properties": { - "active_threshold": { - "description": "The number or percentage of tokens that must be staked for the DAO to be active", - "anyOf": [ - { - "$ref": "#/definitions/ActiveThreshold" - }, - { - "type": "null" - } - ] - }, - "denom": { - "description": "Token denom e.g. ujuno, or some ibc denom", - "type": "string" - }, - "unstaking_duration": { - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "ActiveThreshold": { - "description": "The threshold of tokens that must be staked in order for this voting module to be active. If this is not reached, this module will response to `is_active` queries with false and proposal modules which respect active thresholds will not allow the creation of proposals.", - "oneOf": [ - { - "description": "The absolute number of tokens that must be staked for the module to be active.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The percentage of tokens that must be staked for the module to be active. Computed as `staked / total_supply`.", - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "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": [ - { - "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" - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Stakes tokens with the contract to get voting power in the DAO", - "type": "object", - "required": [ - "stake" - ], - "properties": { - "stake": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Unstakes tokens so that they begin unbonding", - "type": "object", - "required": [ - "unstake" - ], - "properties": { - "unstake": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates the contract configuration", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "duration": { - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Claims unstaked tokens that have completed the unbonding period", - "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets the active threshold to a new value. Only the instantiator of this contract (a DAO most likely) may call this method.", - "type": "object", - "required": [ - "update_active_threshold" - ], - "properties": { - "update_active_threshold": { - "type": "object", - "properties": { - "new_threshold": { - "anyOf": [ - { - "$ref": "#/definitions/ActiveThreshold" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Adds a hook that fires on staking / unstaking", - "type": "object", - "required": [ - "add_hook" - ], - "properties": { - "add_hook": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Removes a hook that fires on staking / unstaking", - "type": "object", - "required": [ - "remove_hook" - ], - "properties": { - "remove_hook": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "ActiveThreshold": { - "description": "The threshold of tokens that must be staked in order for this voting module to be active. If this is not reached, this module will response to `is_active` queries with false and proposal modules which respect active thresholds will not allow the creation of proposals.", - "oneOf": [ - { - "description": "The absolute number of tokens that must be staked for the module to be active.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The percentage of tokens that must be staked for the module to be active. Computed as `staked / total_supply`.", - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "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": [ - { - "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" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "get_config" - ], - "properties": { - "get_config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claims" - ], - "properties": { - "claims": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_denom" - ], - "properties": { - "get_denom": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "list_stakers" - ], - "properties": { - "list_stakers": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "active_threshold" - ], - "properties": { - "active_threshold": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_hooks" - ], - "properties": { - "get_hooks": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the voting power for an address at a given height.", - "type": "object", - "required": [ - "voting_power_at_height" - ], - "properties": { - "voting_power_at_height": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - }, - "height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the total voting power at a given block heigh.", - "type": "object", - "required": [ - "total_power_at_height" - ], - "properties": { - "total_power_at_height": { - "type": "object", - "properties": { - "height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the address of the DAO this module belongs to.", - "type": "object", - "required": [ - "dao" - ], - "properties": { - "dao": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns contract version info.", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "is_active" - ], - "properties": { - "is_active": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "type": "object", - "additionalProperties": false - }, - "sudo": null, - "responses": { - "active_threshold": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ActiveThresholdResponse", - "type": "object", - "properties": { - "active_threshold": { - "anyOf": [ - { - "$ref": "#/definitions/ActiveThreshold" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "ActiveThreshold": { - "description": "The threshold of tokens that must be staked in order for this voting module to be active. If this is not reached, this module will response to `is_active` queries with false and proposal modules which respect active thresholds will not allow the creation of proposals.", - "oneOf": [ - { - "description": "The absolute number of tokens that must be staked for the module to be active.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The percentage of tokens that must be staked for the module to be active. Computed as `staked / total_supply`.", - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "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" - } - } - }, - "claims": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ClaimsResponse", - "type": "object", - "required": [ - "claims" - ], - "properties": { - "claims": { - "type": "array", - "items": { - "$ref": "#/definitions/Claim" - } - } - }, - "additionalProperties": false, - "definitions": { - "Claim": { - "type": "object", - "required": [ - "amount", - "release_at" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "release_at": { - "$ref": "#/definitions/Expiration" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "dao": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "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" - }, - "get_config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - }, - "unstaking_duration": { - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "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 - } - ] - } - } - }, - "get_denom": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "DenomResponse", - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - }, - "get_hooks": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "GetHooksResponse", - "type": "object", - "required": [ - "hooks" - ], - "properties": { - "hooks": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InfoResponse", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "$ref": "#/definitions/ContractVersion" - } - }, - "additionalProperties": false, - "definitions": { - "ContractVersion": { - "type": "object", - "required": [ - "contract", - "version" - ], - "properties": { - "contract": { - "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", - "type": "string" - }, - "version": { - "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", - "type": "string" - } - }, - "additionalProperties": false - } - } - }, - "is_active": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Boolean", - "type": "boolean" - }, - "list_stakers": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ListStakersResponse", - "type": "object", - "required": [ - "stakers" - ], - "properties": { - "stakers": { - "type": "array", - "items": { - "$ref": "#/definitions/StakerBalanceResponse" - } - } - }, - "additionalProperties": false, - "definitions": { - "StakerBalanceResponse": { - "type": "object", - "required": [ - "address", - "balance" - ], - "properties": { - "address": { - "type": "string" - }, - "balance": { - "$ref": "#/definitions/Uint128" - } - }, - "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" - } - } - }, - "total_power_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TotalPowerAtHeightResponse", - "type": "object", - "required": [ - "height", - "power" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "power": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "voting_power_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VotingPowerAtHeightResponse", - "type": "object", - "required": [ - "height", - "power" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "power": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - } - } -} diff --git a/contracts/voting/dao-voting-native-staked/src/contract.rs b/contracts/voting/dao-voting-native-staked/src/contract.rs deleted file mode 100644 index 4e019d3e3..000000000 --- a/contracts/voting/dao-voting-native-staked/src/contract.rs +++ /dev/null @@ -1,509 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Response, StdResult, Uint128, Uint256, -}; -use cw2::{get_contract_version, set_contract_version, ContractVersion}; -use cw_controllers::ClaimsResponse; -use cw_utils::{must_pay, Duration}; -use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; -use dao_interface::voting::{ - IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, -}; -use dao_voting::{ - duration::validate_duration, - threshold::{ActiveThreshold, ActiveThresholdResponse}, -}; - -use crate::error::ContractError; -use crate::msg::{ - DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, - QueryMsg, StakerBalanceResponse, -}; -use crate::state::{ - Config, ACTIVE_THRESHOLD, CLAIMS, CONFIG, DAO, HOOKS, MAX_CLAIMS, STAKED_BALANCES, STAKED_TOTAL, -}; - -pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-native-staked"; -pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -// We multiply by this when calculating needed power for being active -// when using active threshold with percent -const PRECISION_FACTOR: u128 = 10u128.pow(9); - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - validate_duration(msg.unstaking_duration)?; - - let config = Config { - denom: msg.denom.clone(), - unstaking_duration: msg.unstaking_duration, - }; - - CONFIG.save(deps.storage, &config)?; - DAO.save(deps.storage, &info.sender)?; - - // Validate denom by checking supply - let supply: Coin = deps.querier.query_supply(msg.denom.to_string())?; - if Uint128::is_zero(&supply.amount) { - return Err(ContractError::InvalidDenom {}); - } - - // Validate active threshold - if let Some(active_threshold) = msg.active_threshold.as_ref() { - match active_threshold { - ActiveThreshold::AbsoluteCount { count } => { - assert_valid_absolute_count_threshold(*count, supply.amount)?; - } - ActiveThreshold::Percentage { percent } => { - if *percent > Decimal::percent(100) || *percent <= Decimal::percent(0) { - return Err(ContractError::InvalidActivePercentage {}); - } - } - } - - ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; - } - - Ok(Response::new().add_attribute("action", "instantiate")) -} - -pub fn assert_valid_absolute_count_threshold( - count: Uint128, - supply: Uint128, -) -> Result<(), ContractError> { - if count.is_zero() { - return Err(ContractError::ZeroActiveCount {}); - } - if count > supply { - return Err(ContractError::InvalidAbsoluteCount {}); - } - Ok(()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Stake {} => execute_stake(deps, env, info), - ExecuteMsg::Unstake { amount } => execute_unstake(deps, env, info, amount), - ExecuteMsg::UpdateConfig { duration } => execute_update_config(deps, info, duration), - ExecuteMsg::Claim {} => execute_claim(deps, env, info), - ExecuteMsg::UpdateActiveThreshold { new_threshold } => { - execute_update_active_threshold(deps, env, info, new_threshold) - } - ExecuteMsg::AddHook { addr } => execute_add_hook(deps, env, info, addr), - ExecuteMsg::RemoveHook { addr } => execute_remove_hook(deps, env, info, addr), - } -} - -pub fn execute_stake( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let config = CONFIG.load(deps.storage)?; - let amount = must_pay(&info, &config.denom)?; - - STAKED_BALANCES.update( - deps.storage, - &info.sender, - env.block.height, - |balance| -> StdResult { Ok(balance.unwrap_or_default().checked_add(amount)?) }, - )?; - STAKED_TOTAL.update( - deps.storage, - env.block.height, - |total| -> StdResult { Ok(total.unwrap_or_default().checked_add(amount)?) }, - )?; - - // Add stake hook messages - let hook_msgs = stake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; - - Ok(Response::new() - .add_submessages(hook_msgs) - .add_attribute("action", "stake") - .add_attribute("amount", amount.to_string()) - .add_attribute("from", info.sender)) -} - -pub fn execute_unstake( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, -) -> Result { - if amount.is_zero() { - return Err(ContractError::ZeroUnstake {}); - } - - STAKED_BALANCES.update( - deps.storage, - &info.sender, - env.block.height, - |balance| -> Result { - balance - .unwrap_or_default() - .checked_sub(amount) - .map_err(|_e| ContractError::InvalidUnstakeAmount {}) - }, - )?; - STAKED_TOTAL.update( - deps.storage, - env.block.height, - |total| -> Result { - total - .unwrap_or_default() - .checked_sub(amount) - .map_err(|_e| ContractError::InvalidUnstakeAmount {}) - }, - )?; - - // Add unstake hook messages - let hook_msgs = unstake_hook_msgs(HOOKS, deps.storage, info.sender.clone(), amount)?; - - let config = CONFIG.load(deps.storage)?; - match config.unstaking_duration { - None => { - let msg = CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(amount.u128(), config.denom), - }); - Ok(Response::new() - .add_message(msg) - .add_submessages(hook_msgs) - .add_attribute("action", "unstake") - .add_attribute("from", info.sender) - .add_attribute("amount", amount) - .add_attribute("claim_duration", "None")) - } - Some(duration) => { - let outstanding_claims = CLAIMS.query_claims(deps.as_ref(), &info.sender)?.claims; - if outstanding_claims.len() >= MAX_CLAIMS as usize { - return Err(ContractError::TooManyClaims {}); - } - - CLAIMS.create_claim( - deps.storage, - &info.sender, - amount, - duration.after(&env.block), - )?; - Ok(Response::new() - .add_submessages(hook_msgs) - .add_attribute("action", "unstake") - .add_attribute("from", info.sender) - .add_attribute("amount", amount) - .add_attribute("claim_duration", format!("{duration}"))) - } - } -} - -pub fn execute_update_config( - deps: DepsMut, - info: MessageInfo, - duration: Option, -) -> Result { - let mut config: Config = CONFIG.load(deps.storage)?; - - // Only the DAO can update the config - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - validate_duration(duration)?; - - config.unstaking_duration = duration; - - CONFIG.save(deps.storage, &config)?; - Ok(Response::new().add_attribute("action", "update_config")) -} - -pub fn execute_claim( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let release = CLAIMS.claim_tokens(deps.storage, &info.sender, &env.block, None)?; - if release.is_zero() { - return Err(ContractError::NothingToClaim {}); - } - - let config = CONFIG.load(deps.storage)?; - let msg = CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(release.u128(), config.denom), - }); - - Ok(Response::new() - .add_message(msg) - .add_attribute("action", "claim") - .add_attribute("from", info.sender) - .add_attribute("amount", release)) -} - -pub fn execute_update_active_threshold( - deps: DepsMut, - _env: Env, - info: MessageInfo, - new_active_threshold: Option, -) -> Result { - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - if let Some(active_threshold) = new_active_threshold { - match active_threshold { - ActiveThreshold::Percentage { percent } => { - if percent > Decimal::percent(100) || percent.is_zero() { - return Err(ContractError::InvalidActivePercentage {}); - } - } - ActiveThreshold::AbsoluteCount { count } => { - let denom = CONFIG.load(deps.storage)?.denom; - let supply: Coin = deps.querier.query_supply(denom)?; - assert_valid_absolute_count_threshold(count, supply.amount)?; - } - } - ACTIVE_THRESHOLD.save(deps.storage, &active_threshold)?; - } else { - ACTIVE_THRESHOLD.remove(deps.storage); - } - - Ok(Response::new().add_attribute("action", "update_active_threshold")) -} - -pub fn execute_add_hook( - deps: DepsMut, - _env: Env, - info: MessageInfo, - addr: String, -) -> Result { - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - let hook = deps.api.addr_validate(&addr)?; - HOOKS.add_hook(deps.storage, hook)?; - Ok(Response::new() - .add_attribute("action", "add_hook") - .add_attribute("hook", addr)) -} - -pub fn execute_remove_hook( - deps: DepsMut, - _env: Env, - info: MessageInfo, - addr: String, -) -> Result { - let dao = DAO.load(deps.storage)?; - if info.sender != dao { - return Err(ContractError::Unauthorized {}); - } - - let hook = deps.api.addr_validate(&addr)?; - HOOKS.remove_hook(deps.storage, hook)?; - Ok(Response::new() - .add_attribute("action", "remove_hook") - .add_attribute("hook", addr)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::VotingPowerAtHeight { address, height } => { - to_binary(&query_voting_power_at_height(deps, env, address, height)?) - } - QueryMsg::TotalPowerAtHeight { height } => { - to_binary(&query_total_power_at_height(deps, env, height)?) - } - QueryMsg::Info {} => query_info(deps), - QueryMsg::Dao {} => query_dao(deps), - QueryMsg::Claims { address } => to_binary(&query_claims(deps, address)?), - QueryMsg::GetConfig {} => to_binary(&CONFIG.load(deps.storage)?), - QueryMsg::ListStakers { start_after, limit } => { - query_list_stakers(deps, start_after, limit) - } - QueryMsg::GetDenom {} => query_denom(deps), - QueryMsg::IsActive {} => query_is_active(deps), - QueryMsg::ActiveThreshold {} => query_active_threshold(deps), - QueryMsg::GetHooks {} => to_binary(&query_hooks(deps)?), - } -} - -pub fn query_voting_power_at_height( - deps: Deps, - env: Env, - address: String, - height: Option, -) -> StdResult { - let height = height.unwrap_or(env.block.height); - let address = deps.api.addr_validate(&address)?; - let power = STAKED_BALANCES - .may_load_at_height(deps.storage, &address, height)? - .unwrap_or_default(); - Ok(VotingPowerAtHeightResponse { power, height }) -} - -pub fn query_total_power_at_height( - deps: Deps, - env: Env, - height: Option, -) -> StdResult { - let height = height.unwrap_or(env.block.height); - let power = STAKED_TOTAL - .may_load_at_height(deps.storage, height)? - .unwrap_or_default(); - Ok(TotalPowerAtHeightResponse { power, height }) -} - -pub fn query_info(deps: Deps) -> StdResult { - let info = cw2::get_contract_version(deps.storage)?; - to_binary(&dao_interface::voting::InfoResponse { info }) -} - -pub fn query_dao(deps: Deps) -> StdResult { - let dao = DAO.load(deps.storage)?; - to_binary(&dao) -} - -pub fn query_denom(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - to_binary(&DenomResponse { - denom: config.denom, - }) -} - -pub fn query_claims(deps: Deps, address: String) -> StdResult { - CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?) -} - -pub fn query_list_stakers( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - let start_at = start_after - .map(|addr| deps.api.addr_validate(&addr)) - .transpose()?; - - let stakers = cw_paginate_storage::paginate_snapshot_map( - deps, - &STAKED_BALANCES, - start_at.as_ref(), - limit, - cosmwasm_std::Order::Ascending, - )?; - - let stakers = stakers - .into_iter() - .map(|(address, balance)| StakerBalanceResponse { - address: address.into_string(), - balance, - }) - .collect(); - - to_binary(&ListStakersResponse { stakers }) -} - -pub fn query_is_active(deps: Deps) -> StdResult { - let threshold = ACTIVE_THRESHOLD.may_load(deps.storage)?; - if let Some(threshold) = threshold { - let denom = CONFIG.load(deps.storage)?.denom; - let actual_power = STAKED_TOTAL.may_load(deps.storage)?.unwrap_or_default(); - match threshold { - ActiveThreshold::AbsoluteCount { count } => to_binary(&IsActiveResponse { - active: actual_power >= count, - }), - ActiveThreshold::Percentage { percent } => { - // percent is bounded between [0, 100]. decimal - // represents percents in u128 terms as p * - // 10^15. this bounds percent between [0, 10^17]. - // - // total_potential_power is bounded between [0, 2^128] - // as it tracks the balances of a cw20 token which has - // a max supply of 2^128. - // - // with our precision factor being 10^9: - // - // total_power <= 2^128 * 10^9 <= 2^256 - // - // so we're good to put that in a u256. - // - // multiply_ratio promotes to a u512 under the hood, - // so it won't overflow, multiplying by a percent less - // than 100 is gonna make something the same size or - // smaller, applied + 10^9 <= 2^128 * 10^9 + 10^9 <= - // 2^256, so the top of the round won't overflow, and - // rounding is rounding down, so the whole thing can - // be safely unwrapped at the end of the day thank you - // for coming to my ted talk. - let total_potential_power: cosmwasm_std::SupplyResponse = - deps.querier - .query(&cosmwasm_std::QueryRequest::Bank(BankQuery::Supply { - denom, - }))?; - let total_power = total_potential_power - .amount - .amount - .full_mul(PRECISION_FACTOR); - // under the hood decimals are `atomics / 10^decimal_places`. - // cosmwasm doesn't give us a Decimal * Uint256 - // implementation so we take the decimal apart and - // multiply by the fraction. - let applied = total_power.multiply_ratio( - percent.atomics(), - Uint256::from(10u64).pow(percent.decimal_places()), - ); - let rounded = (applied + Uint256::from(PRECISION_FACTOR) - Uint256::from(1u128)) - / Uint256::from(PRECISION_FACTOR); - let count: Uint128 = rounded.try_into().unwrap(); - to_binary(&IsActiveResponse { - active: actual_power >= count, - }) - } - } - } else { - to_binary(&IsActiveResponse { active: true }) - } -} - -pub fn query_active_threshold(deps: Deps) -> StdResult { - to_binary(&ActiveThresholdResponse { - active_threshold: ACTIVE_THRESHOLD.may_load(deps.storage)?, - }) -} - -pub fn query_hooks(deps: Deps) -> StdResult { - Ok(GetHooksResponse { - hooks: HOOKS.query_hooks(deps)?.hooks, - }) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - let storage_version: ContractVersion = get_contract_version(deps.storage)?; - - // Only migrate if newer - if storage_version.version.as_str() < CONTRACT_VERSION { - // Set contract to version to latest - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - } - - Ok(Response::new().add_attribute("action", "migrate")) -} diff --git a/contracts/voting/dao-voting-native-staked/src/error.rs b/contracts/voting/dao-voting-native-staked/src/error.rs deleted file mode 100644 index 8601af4b2..000000000 --- a/contracts/voting/dao-voting-native-staked/src/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -use cosmwasm_std::StdError; -use cw_utils::PaymentError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - PaymentError(#[from] PaymentError), - - #[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("Nothing to claim")] - NothingToClaim {}, - - #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] - TooManyClaims {}, - - #[error("Absolute count threshold cannot be greater than the total token supply")] - InvalidAbsoluteCount {}, - - #[error("Active threshold percentage must be greater than 0 and less than 1")] - InvalidActivePercentage {}, - - #[error("Can only unstake less than or equal to the amount you have staked")] - InvalidUnstakeAmount {}, - - #[error("Active threshold count must be greater than zero")] - ZeroActiveCount {}, - - #[error("Amount being unstaked must be non-zero")] - ZeroUnstake {}, -} diff --git a/contracts/voting/dao-voting-native-staked/src/msg.rs b/contracts/voting/dao-voting-native-staked/src/msg.rs deleted file mode 100644 index a8b562524..000000000 --- a/contracts/voting/dao-voting-native-staked/src/msg.rs +++ /dev/null @@ -1,84 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; -use cw_utils::Duration; -use dao_dao_macros::{active_query, voting_module_query}; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; - -#[cw_serde] -pub struct InstantiateMsg { - /// Token denom e.g. ujuno, or some ibc denom - pub denom: String, - // How long until the tokens become liquid again - pub unstaking_duration: Option, - /// The number or percentage of tokens that must be staked - /// for the DAO to be active - pub active_threshold: Option, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Stakes tokens with the contract to get voting power in the DAO - Stake {}, - /// Unstakes tokens so that they begin unbonding - Unstake { amount: Uint128 }, - /// Updates the contract configuration - UpdateConfig { duration: Option }, - /// Claims unstaked tokens that have completed the unbonding period - Claim {}, - /// Sets the active threshold to a new value. Only the - /// instantiator of this contract (a DAO most likely) may call this - /// method. - UpdateActiveThreshold { - new_threshold: Option, - }, - /// Adds a hook that fires on staking / unstaking - AddHook { addr: String }, - /// Removes a hook that fires on staking / unstaking - RemoveHook { addr: String }, -} - -#[voting_module_query] -#[active_query] -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(crate::state::Config)] - GetConfig {}, - #[returns(cw_controllers::ClaimsResponse)] - Claims { address: String }, - #[returns(DenomResponse)] - GetDenom {}, - #[returns(ListStakersResponse)] - ListStakers { - start_after: Option, - limit: Option, - }, - #[returns(ActiveThresholdResponse)] - ActiveThreshold {}, - #[returns(GetHooksResponse)] - GetHooks {}, -} - -#[cw_serde] -pub struct MigrateMsg {} - -#[cw_serde] -pub struct ListStakersResponse { - pub stakers: Vec, -} - -#[cw_serde] -pub struct StakerBalanceResponse { - pub address: String, - pub balance: Uint128, -} - -#[cw_serde] -pub struct DenomResponse { - pub denom: String, -} - -#[cw_serde] -pub struct GetHooksResponse { - pub hooks: Vec, -} diff --git a/contracts/voting/dao-voting-native-staked/src/state.rs b/contracts/voting/dao-voting-native-staked/src/state.rs deleted file mode 100644 index 3e46d0281..000000000 --- a/contracts/voting/dao-voting-native-staked/src/state.rs +++ /dev/null @@ -1,46 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; -use cw_controllers::Claims; -use cw_hooks::Hooks; -use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; -use cw_utils::Duration; -use dao_voting::threshold::ActiveThreshold; - -#[cw_serde] -pub struct Config { - pub denom: String, - pub unstaking_duration: Option, -} - -/// The configuration of this voting contract -pub const CONFIG: Item = Item::new("config"); - -/// The address of the DAO that instantiated this contract -pub const DAO: Item = Item::new("dao"); - -/// Keeps track of staked balances by address over time -pub const STAKED_BALANCES: SnapshotMap<&Addr, Uint128> = SnapshotMap::new( - "staked_balances", - "staked_balance__checkpoints", - "staked_balance__changelog", - Strategy::EveryBlock, -); - -/// Keeps track of staked total over time -pub const STAKED_TOTAL: SnapshotItem = SnapshotItem::new( - "total_staked", - "total_staked__checkpoints", - "total_staked__changelog", - Strategy::EveryBlock, -); - -/// The maximum number of claims that may be outstanding. -pub const MAX_CLAIMS: u64 = 100; - -pub const CLAIMS: Claims = Claims::new("claims"); - -/// The minimum amount of staked tokens for the DAO to be active -pub const ACTIVE_THRESHOLD: Item = Item::new("active_threshold"); - -/// Hooks to contracts that will receive staking and unstaking messages -pub const HOOKS: Hooks = Hooks::new("hooks"); diff --git a/contracts/voting/dao-voting-native-staked/src/tests.rs b/contracts/voting/dao-voting-native-staked/src/tests.rs deleted file mode 100644 index b88ec2806..000000000 --- a/contracts/voting/dao-voting-native-staked/src/tests.rs +++ /dev/null @@ -1,1355 +0,0 @@ -use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; -use cw_controllers::ClaimsResponse; -use cw_multi_test::{ - custom_app, next_block, App, AppResponse, Contract, ContractWrapper, Executor, -}; -use cw_utils::Duration; -use dao_interface::voting::{ - InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, -}; -use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; - -use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; -use crate::error::ContractError; -use crate::msg::{ - DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, - QueryMsg, StakerBalanceResponse, -}; -use crate::state::Config; - -const DAO_ADDR: &str = "dao"; -const ADDR1: &str = "addr1"; -const ADDR2: &str = "addr2"; -const DENOM: &str = "ujuno"; -const INVALID_DENOM: &str = "uinvalid"; -const ODD_DENOM: &str = "uodd"; - -fn staking_contract() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ); - Box::new(contract) -} - -fn hook_counter_contract() -> Box> { - let contract = ContractWrapper::new_with_empty( - dao_proposal_hook_counter::contract::execute, - dao_proposal_hook_counter::contract::instantiate, - dao_proposal_hook_counter::contract::query, - ); - Box::new(contract) -} - -fn mock_app() -> App { - custom_app(|r, _a, s| { - r.bank - .init_balance( - s, - &Addr::unchecked(DAO_ADDR), - vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - ) - .unwrap(); - r.bank - .init_balance( - s, - &Addr::unchecked(ADDR1), - vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: ODD_DENOM.to_string(), - amount: Uint128::new(5), - }, - ], - ) - .unwrap(); - r.bank - .init_balance( - s, - &Addr::unchecked(ADDR2), - vec![ - Coin { - denom: DENOM.to_string(), - amount: Uint128::new(10000), - }, - Coin { - denom: INVALID_DENOM.to_string(), - amount: Uint128::new(10000), - }, - ], - ) - .unwrap(); - }) -} - -fn instantiate_staking(app: &mut App, staking_id: u64, msg: InstantiateMsg) -> Addr { - app.instantiate_contract( - staking_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "Staking", - None, - ) - .unwrap() -} - -fn stake_tokens( - app: &mut App, - staking_addr: Addr, - sender: &str, - amount: u128, - denom: &str, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Stake {}, - &coins(amount, denom), - ) -} - -fn unstake_tokens( - app: &mut App, - staking_addr: Addr, - sender: &str, - amount: u128, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Unstake { - amount: Uint128::new(amount), - }, - &[], - ) -} - -fn claim(app: &mut App, staking_addr: Addr, sender: &str) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::Claim {}, - &[], - ) -} - -fn update_config( - app: &mut App, - staking_addr: Addr, - sender: &str, - duration: Option, -) -> anyhow::Result { - app.execute_contract( - Addr::unchecked(sender), - staking_addr, - &ExecuteMsg::UpdateConfig { duration }, - &[], - ) -} - -fn get_voting_power_at_height( - app: &mut App, - staking_addr: Addr, - address: String, - height: Option, -) -> VotingPowerAtHeightResponse { - app.wrap() - .query_wasm_smart( - staking_addr, - &QueryMsg::VotingPowerAtHeight { address, height }, - ) - .unwrap() -} - -fn get_total_power_at_height( - app: &mut App, - staking_addr: Addr, - height: Option, -) -> TotalPowerAtHeightResponse { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::TotalPowerAtHeight { height }) - .unwrap() -} - -fn get_config(app: &mut App, staking_addr: Addr) -> Config { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::GetConfig {}) - .unwrap() -} - -fn get_claims(app: &mut App, staking_addr: Addr, address: String) -> ClaimsResponse { - app.wrap() - .query_wasm_smart(staking_addr, &QueryMsg::Claims { address }) - .unwrap() -} - -fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { - app.wrap().query_balance(address, denom).unwrap().amount -} - -#[test] -fn test_instantiate() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - // Populated fields - let _addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Non populated fields - let _addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: None, - active_threshold: None, - }, - ); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_instantiate_invalid_unstaking_duration_height() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - - // Populated fields with height - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(0)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(1), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Denom does not exist on chain")] -fn test_instantiate_invalid_denom() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - - // Populated fields with height - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - // This denom has zero supply - denom: "uinvalid2".to_string(), - unstaking_duration: None, - active_threshold: None, - }, - ); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_instantiate_invalid_unstaking_duration_time() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - - // Populated fields with height - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Time(0)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(1), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Must send reserve token 'ujuno'")] -fn test_stake_invalid_denom() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Try and stake an invalid denom - stake_tokens(&mut app, addr, ADDR1, 100, INVALID_DENOM).unwrap(); -} - -#[test] -fn test_stake_valid_denom() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Try and stake an valid denom - stake_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); -} - -#[test] -#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] -fn test_unstake_none_staked() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - unstake_tokens(&mut app, addr, ADDR1, 100).unwrap(); -} - -#[test] -#[should_panic(expected = "Amount being unstaked must be non-zero")] -fn test_unstake_zero_tokens() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - unstake_tokens(&mut app, addr, ADDR1, 0).unwrap(); -} - -#[test] -#[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] -fn test_unstake_invalid_balance() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Try and unstake too many - unstake_tokens(&mut app, addr, ADDR1, 200).unwrap(); -} - -#[test] -fn test_unstake() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - - // Query claims - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 1); - app.update_block(next_block); - - // Unstake the rest - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - - // Query claims - let claims = get_claims(&mut app, addr, ADDR1.to_string()); - assert_eq!(claims.claims.len(), 2); -} - -#[test] -fn test_unstake_no_unstaking_duration() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: None, - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some tokens - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - - app.update_block(next_block); - - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 - assert_eq!(balance, Uint128::new(9975)); - - // Unstake the rest - unstake_tokens(&mut app, addr, ADDR1, 25).unwrap(); - - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 - assert_eq!(balance, Uint128::new(10000)) -} - -#[test] -#[should_panic(expected = "Nothing to claim")] -fn test_claim_no_claims() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - claim(&mut app, addr, ADDR1).unwrap(); -} - -#[test] -#[should_panic(expected = "Nothing to claim")] -fn test_claim_claim_not_reached() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake them to create the claims - unstake_tokens(&mut app, addr.clone(), ADDR1, 100).unwrap(); - app.update_block(next_block); - - // We have a claim but it isnt reached yet so this will still fail - claim(&mut app, addr, ADDR1).unwrap(); -} - -#[test] -fn test_claim() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some to create the claims - unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); - app.update_block(|b| { - b.height += 5; - b.time = b.time.plus_seconds(25); - }); - - // Claim - claim(&mut app, addr.clone(), ADDR1).unwrap(); - - // Query balance - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 - assert_eq!(balance, Uint128::new(9975)); - - // Unstake the rest - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(|b| { - b.height += 10; - b.time = b.time.plus_seconds(50); - }); - - // Claim - claim(&mut app, addr, ADDR1).unwrap(); - - // Query balance - let balance = get_balance(&mut app, ADDR1, DENOM); - // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 - assert_eq!(balance, Uint128::new(10000)); -} - -#[test] -#[should_panic(expected = "Unauthorized")] -fn test_update_config_invalid_sender() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // From ADDR2, so not owner or manager - update_config(&mut app, addr, ADDR1, Some(Duration::Height(10))).unwrap(); -} - -#[test] -fn test_update_config_as_dao() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Swap owner and manager, change duration - update_config(&mut app, addr.clone(), DAO_ADDR, Some(Duration::Height(10))).unwrap(); - - let config = get_config(&mut app, addr); - assert_eq!( - Config { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(10)), - }, - config - ); -} - -#[test] -#[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] -fn test_update_config_invalid_duration() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Change duration and manager as manager cannot change owner - update_config(&mut app, addr, DAO_ADDR, Some(Duration::Height(0))).unwrap(); -} - -#[test] -fn test_query_dao() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::Dao {}; - let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(dao, Addr::unchecked(DAO_ADDR)); -} - -#[test] -fn test_query_denom() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::GetDenom {}; - let denom: DenomResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(denom.denom, DENOM.to_string()); -} - -#[test] -fn test_query_info() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let msg = QueryMsg::Info {}; - let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!(resp.info.contract, "crates.io:dao-voting-native-staked"); -} - -#[test] -fn test_query_claims() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 0); - - // Stake some tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Unstake some tokens - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(next_block); - - let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); - assert_eq!(claims.claims.len(), 1); - - unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); - app.update_block(next_block); - - let claims = get_claims(&mut app, addr, ADDR1.to_string()); - assert_eq!(claims.claims.len(), 2); -} - -#[test] -fn test_query_get_config() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let config = get_config(&mut app, addr); - assert_eq!( - config, - Config { - unstaking_duration: Some(Duration::Height(5)), - denom: DENOM.to_string(), - } - ) -} - -#[test] -fn test_voting_power_queries() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Total power is 0 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert!(resp.power.is_zero()); - - // ADDR1 has no power, none staked - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert!(resp.power.is_zero()); - - // ADDR1 stakes - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 100 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); - assert!(resp.power.is_zero()); - - // ADDR2 stakes - stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); - app.update_block(next_block); - let prev_height = app.block_info().height - 1; - - // Query the previous height, total 100, ADDR1 100, ADDR2 0 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 100 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); - assert!(resp.power.is_zero()); - - // For current height, total 150, ADDR1 100, ADDR2 50 - // Total power is 150 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(150)); - - // ADDR1 has 100 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 now has 50 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); - - // ADDR1 unstakes half - unstake_tokens(&mut app, addr.clone(), ADDR1, 50).unwrap(); - app.update_block(next_block); - let prev_height = app.block_info().height - 1; - - // Query the previous height, total 150, ADDR1 100, ADDR2 50 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(150)); - - // ADDR1 has 100 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR2 still has 0 power - let resp = - get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); - assert_eq!(resp.power, Uint128::new(50)); - - // For current height, total 100, ADDR1 50, ADDR2 50 - // Total power is 100 - let resp = get_total_power_at_height(&mut app, addr.clone(), None); - assert_eq!(resp.power, Uint128::new(100)); - - // ADDR1 has 50 power - let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); - - // ADDR2 now has 50 power - let resp = get_voting_power_at_height(&mut app, addr, ADDR2.to_string(), None); - assert_eq!(resp.power, Uint128::new(50)); -} - -#[test] -fn test_query_list_stakers() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // ADDR1 stakes - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - - // ADDR2 stakes - stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); - - // check entire result set - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr.clone(), - &QueryMsg::ListStakers { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let test_res = ListStakersResponse { - stakers: vec![ - StakerBalanceResponse { - address: ADDR1.to_string(), - balance: Uint128::new(100), - }, - StakerBalanceResponse { - address: ADDR2.to_string(), - balance: Uint128::new(50), - }, - ], - }; - - assert_eq!(stakers, test_res); - - // skipped 1, check result - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr.clone(), - &QueryMsg::ListStakers { - start_after: Some(ADDR1.to_string()), - limit: None, - }, - ) - .unwrap(); - - let test_res = ListStakersResponse { - stakers: vec![StakerBalanceResponse { - address: ADDR2.to_string(), - balance: Uint128::new(50), - }], - }; - - assert_eq!(stakers, test_res); - - // skipped 2, check result. should be nothing - let stakers: ListStakersResponse = app - .wrap() - .query_wasm_smart( - addr, - &QueryMsg::ListStakers { - start_after: Some(ADDR2.to_string()), - limit: None, - }, - ) - .unwrap(); - - assert_eq!(stakers, ListStakersResponse { stakers: vec![] }); -} - -#[test] -#[should_panic(expected = "Active threshold count must be greater than zero")] -fn test_instantiate_zero_active_threshold_count() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::zero(), - }), - }, - ); -} - -#[test] -fn test_active_threshold_absolute_count() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 100 tokens - stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - app.update_block(next_block); - - // Active as enough staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_percent() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(20), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 6000 tokens, now active - stake_tokens(&mut app, addr.clone(), ADDR1, 6000, DENOM).unwrap(); - app.update_block(next_block); - - // Active as enough staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_percent_rounds_up() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: ODD_DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(50), - }), - }, - ); - - // Not active as none staked - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 2 tokens, should not be active. - stake_tokens(&mut app, addr.clone(), ADDR1, 2, ODD_DENOM).unwrap(); - app.update_block(next_block); - - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::IsActive {}) - .unwrap(); - assert!(!is_active.active); - - // Stake 1 more token, should now be active. - stake_tokens(&mut app, addr.clone(), ADDR1, 1, ODD_DENOM).unwrap(); - app.update_block(next_block); - - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_active_threshold_none() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Active as no threshold - let is_active: IsActiveResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::IsActive {}) - .unwrap(); - assert!(is_active.active); -} - -#[test] -fn test_update_active_threshold() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!(resp.active_threshold, None); - - let msg = ExecuteMsg::UpdateActiveThreshold { - new_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - }; - - // Expect failure as sender is not the DAO - app.execute_contract(Addr::unchecked(ADDR1), addr.clone(), &msg, &[]) - .unwrap_err(); - - // Expect success as sender is the DAO - app.execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) - .unwrap(); - - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!( - resp.active_threshold, - Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100) - }) - ); - - // Can't set threshold to invalid value - let msg = ExecuteMsg::UpdateActiveThreshold { - new_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(120), - }), - }; - let err: ContractError = app - .execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::InvalidActivePercentage {}); - - // Remove threshold - let msg = ExecuteMsg::UpdateActiveThreshold { - new_threshold: None, - }; - app.execute_contract(Addr::unchecked(DAO_ADDR), addr.clone(), &msg, &[]) - .unwrap(); - let resp: ActiveThresholdResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::ActiveThreshold {}) - .unwrap(); - assert_eq!(resp.active_threshold, None); -} - -#[test] -#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] -fn test_active_threshold_percentage_gt_100() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(120), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] -fn test_active_threshold_percentage_lte_0() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::Percentage { - percent: Decimal::percent(0), - }), - }, - ); -} - -#[test] -#[should_panic(expected = "Absolute count threshold cannot be greater than the total token supply")] -fn test_active_threshold_absolute_count_invalid() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(3000000000000000000), - }), - }, - ); -} - -#[test] -fn test_add_remove_hooks() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // No hooks exist. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, Vec::::new()); - - // Non-owner can't add hook - let err: ContractError = app - .execute_contract( - Addr::unchecked(ADDR2), - addr.clone(), - &ExecuteMsg::AddHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::Unauthorized {}); - - // Add a hook. - app.execute_contract( - Addr::unchecked(DAO_ADDR), - addr.clone(), - &ExecuteMsg::AddHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap(); - - // One hook exists. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr.clone(), &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, vec!["hook".to_string()]); - - // Non-owner can't remove hook - let err: ContractError = app - .execute_contract( - Addr::unchecked(ADDR2), - addr.clone(), - &ExecuteMsg::RemoveHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::Unauthorized {}); - - // Remove hook. - app.execute_contract( - Addr::unchecked(DAO_ADDR), - addr.clone(), - &ExecuteMsg::RemoveHook { - addr: "hook".to_string(), - }, - &[], - ) - .unwrap(); - - // No hook exists. - let resp: GetHooksResponse = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::GetHooks {}) - .unwrap(); - assert_eq!(resp.hooks, Vec::::new()); -} - -#[test] -fn test_staking_hooks() { - let mut app = mock_app(); - let staking_id = app.store_code(staking_contract()); - let hook_id = app.store_code(hook_counter_contract()); - - let hook = app - .instantiate_contract( - hook_id, - Addr::unchecked(DAO_ADDR), - &dao_proposal_hook_counter::msg::InstantiateMsg { - should_error: false, - }, - &[], - "hook counter".to_string(), - None, - ) - .unwrap(); - - let addr = instantiate_staking( - &mut app, - staking_id, - InstantiateMsg { - denom: DENOM.to_string(), - unstaking_duration: Some(Duration::Height(5)), - active_threshold: None, - }, - ); - - // Add a staking hook. - app.execute_contract( - Addr::unchecked(DAO_ADDR), - addr.clone(), - &ExecuteMsg::AddHook { - addr: hook.to_string(), - }, - &[], - ) - .unwrap(); - - // Stake some tokens - let res = stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); - - // Make sure hook is included in response - assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); - - app.update_block(next_block); - - // Unstake some - let res = unstake_tokens(&mut app, addr, ADDR1, 75).unwrap(); - - // Make sure hook is included in response - assert_eq!("stake_hook", res.events.last().unwrap().attributes[1].value); -} - -#[test] -fn test_migrate_update_version() { - let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "1.0.0").unwrap(); - migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); - let version = cw2::get_contract_version(&deps.storage).unwrap(); - assert_eq!(version.version, CONTRACT_VERSION); - assert_eq!(version.contract, CONTRACT_NAME); -} diff --git a/contracts/voting/dao-voting-token-factory-staked/.cargo/config b/contracts/voting/dao-voting-token-factory-staked/.cargo/config deleted file mode 100644 index 336b618a1..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/voting/dao-voting-token-factory-staked/examples/schema.rs b/contracts/voting/dao-voting-token-factory-staked/examples/schema.rs deleted file mode 100644 index 2aa85dbff..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/examples/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::write_api; -use dao_voting_token_factory_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - migrate: MigrateMsg, - } -} diff --git a/contracts/voting/dao-voting-token-factory-staked/src/lib.rs b/contracts/voting/dao-voting-token-factory-staked/src/lib.rs deleted file mode 100644 index d1800adbc..000000000 --- a/contracts/voting/dao-voting-token-factory-staked/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] - -pub mod contract; -mod error; -pub mod msg; -pub mod state; - -#[cfg(test)] -mod tests; - -pub use crate::error::ContractError; diff --git a/contracts/voting/dao-voting-native-staked/.cargo/config b/contracts/voting/dao-voting-token-staked/.cargo/config similarity index 100% rename from contracts/voting/dao-voting-native-staked/.cargo/config rename to contracts/voting/dao-voting-token-staked/.cargo/config diff --git a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml b/contracts/voting/dao-voting-token-staked/Cargo.toml similarity index 98% rename from contracts/voting/dao-voting-token-factory-staked/Cargo.toml rename to contracts/voting/dao-voting-token-staked/Cargo.toml index 2ad870d50..9e5413352 100644 --- a/contracts/voting/dao-voting-token-factory-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-staked/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dao-voting-token-factory-staked" +name = "dao-voting-token-staked" authors = ["Callum Anderson ", "Noah Saso ", "Jake Hartnell "] description = "A DAO DAO voting module based on staked token factory or native tokens. Only works with chains that support Token Factory." edition = { workspace = true } diff --git a/contracts/voting/dao-voting-token-factory-staked/README.md b/contracts/voting/dao-voting-token-staked/README.md similarity index 66% rename from contracts/voting/dao-voting-token-factory-staked/README.md rename to contracts/voting/dao-voting-token-staked/README.md index ad427dc0b..ec25b5d3d 100644 --- a/contracts/voting/dao-voting-token-factory-staked/README.md +++ b/contracts/voting/dao-voting-token-staked/README.md @@ -1,14 +1,12 @@ -# `dao_voting_token_factory_staked` +# `dao_voting_token_staked` Simple native or Token Factory based token voting / staking contract which assumes the native denom provided is not used for staking for securing the network e.g. IBC denoms or secondary tokens (ION). Staked balances may be queried at an arbitrary height. This contract implements the interface needed to be a DAO DAO [voting module](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design#the-voting-module). -`dao_voting_token_factory_staked` leverages the `cw_tokenfactory_issuer` contract for tokenfactory functionality. When instantiated, `dao_voting_token_factory_staked` creates a new `cw_tokenfactory_issuer` contract to manage the new Token, with the DAO as admin and owner (these can be renounced or updated by vote of the DAO). - -NOTE: This contract requires having the Token Factory module on your chain, which allows the creation of new native tokens. If your chain does not have this module, use `dao-voting-native-staked` instead. +### Token Factory support +`dao_voting_token_staked` leverages the `cw_tokenfactory_issuer` contract for tokenfactory functionality. When instantiated, `dao_voting_token_staked` creates a new `cw_tokenfactory_issuer` contract to manage the new Token, with the DAO as admin and owner (these can be renounced or updated by vote of the DAO). ## Instantiation -When instantiating a new `dao_voting_token_factory_staked` contract there are two required fields: -- `token_issuer_code_id`: must be set to a valid Code ID for the `cw_tokenfactory_issuer` contract. +When instantiating a new `dao_voting_token_staked` contract there are two required fields: - `token_info`: you have the option to leverage an `existing` token or creating a `new` one. There are a few optional fields: @@ -16,6 +14,7 @@ There are a few optional fields: - `active_theshold`: the amount of tokens that must be staked for the DAO to be active. This may be either an `absolute_count` or a `percentage`. ### Create a New Token +- `token_issuer_code_id`: must be set to a valid Code ID for the `cw_tokenfactory_issuer` contract. Creating a token has a few additional optional fields: - `metadata`: information about the token. See [Cosmos SDK Coin metadata documentation](https://docs.cosmos.network/main/architecture/adr-024-coin-metadata) for more info on coin metadata. - `initial_dao_balance`: the initial balance created for the DAO. @@ -23,9 +22,9 @@ Creating a token has a few additional optional fields: Example insantiation mesggage: ``` json { - "token_issuer_code_id": , "token_info": { "new": { + "token_issuer_code_id": , "subdenom": "meow", "metadata": { "description": "Meow!", @@ -60,17 +59,14 @@ Example insantiation mesggage: } ``` -### Use Existing +### Use Existing Native Token Example insantiation mesggage: ``` json { - "token_issuer_code_id": , "token_info": { - "new": { - "subdenom": "factory/{address}/{denom}", - } + "new": { + "subdenom": "uion", + } } ``` - -When leveraging an existing token, cetain `cw_tokenfactory_issuer` features will not work until admin of the Token is transferred over to the `cw_tokenfactory_issuer` contract. diff --git a/contracts/voting/dao-voting-native-staked/examples/schema.rs b/contracts/voting/dao-voting-token-staked/examples/schema.rs similarity index 68% rename from contracts/voting/dao-voting-native-staked/examples/schema.rs rename to contracts/voting/dao-voting-token-staked/examples/schema.rs index f7ad58f28..0bb3d7c39 100644 --- a/contracts/voting/dao-voting-native-staked/examples/schema.rs +++ b/contracts/voting/dao-voting-token-staked/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use dao_voting_native_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use dao_voting_token_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/voting/dao-voting-token-factory-staked/schema/dao-voting-token-factory-staked.json b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-factory-staked.json similarity index 99% rename from contracts/voting/dao-voting-token-factory-staked/schema/dao-voting-token-factory-staked.json rename to contracts/voting/dao-voting-token-staked/schema/dao-voting-token-factory-staked.json index 624c8be17..0622e2358 100644 --- a/contracts/voting/dao-voting-token-factory-staked/schema/dao-voting-token-factory-staked.json +++ b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-factory-staked.json @@ -1,5 +1,5 @@ { - "contract_name": "dao-voting-token-factory-staked", + "contract_name": "dao-voting-token-staked", "contract_version": "2.2.0", "idl_version": "1.0.0", "instantiate": { @@ -265,7 +265,7 @@ "TokenInfo": { "oneOf": [ { - "description": "Uses an existing Token Factory token and creates a new issuer contract. Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, must be done manually. Note, for chain controlled denoms or IBC tokens use dao-voting-native-staked.", + "description": "Uses an existing Token Factory token and creates a new issuer contract. Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, must be done manually. Note, for chain controlled denoms or IBC tokens use dao-voting-token-staked.", "type": "object", "required": [ "existing" diff --git a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs similarity index 99% rename from contracts/voting/dao-voting-token-factory-staked/src/contract.rs rename to contracts/voting/dao-voting-token-staked/src/contract.rs index 3d2847e78..bf57053e7 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -34,7 +34,7 @@ use crate::state::{ STAKED_TOTAL, TOKEN_INSTANTIATION_INFO, TOKEN_ISSUER_CONTRACT, }; -pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-token-factory-staked"; +pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-voting-token-staked"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); // Settings for query pagination diff --git a/contracts/voting/dao-voting-token-factory-staked/src/error.rs b/contracts/voting/dao-voting-token-staked/src/error.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/error.rs rename to contracts/voting/dao-voting-token-staked/src/error.rs diff --git a/contracts/voting/dao-voting-native-staked/src/lib.rs b/contracts/voting/dao-voting-token-staked/src/lib.rs similarity index 100% rename from contracts/voting/dao-voting-native-staked/src/lib.rs rename to contracts/voting/dao-voting-token-staked/src/lib.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs similarity index 97% rename from contracts/voting/dao-voting-token-factory-staked/src/msg.rs rename to contracts/voting/dao-voting-token-staked/src/msg.rs index 122b18148..c81a95d22 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -45,7 +45,6 @@ pub enum TokenInfo { /// Uses an existing Token Factory token and creates a new issuer contract. /// Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, /// must be done manually. - /// Note, for chain controlled denoms or IBC tokens use dao-voting-native-staked. Existing { /// Token factory denom denom: String, diff --git a/contracts/voting/dao-voting-token-factory-staked/src/state.rs b/contracts/voting/dao-voting-token-staked/src/state.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/state.rs rename to contracts/voting/dao-voting-token-staked/src/state.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/mod.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/mod.rs rename to contracts/voting/dao-voting-token-staked/src/tests/mod.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/mod.rs rename to contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs similarity index 99% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs rename to contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs index 9ddc5824f..d03f3d437 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs @@ -725,10 +725,7 @@ fn test_query_info() { let msg = QueryMsg::Info {}; let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); - assert_eq!( - resp.info.contract, - "crates.io:dao-voting-token-factory-staked" - ); + assert_eq!(resp.info.contract, "crates.io:dao-voting-token-staked"); } #[test] diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tf_module_mock.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/multitest/tf_module_mock.rs rename to contracts/voting/dao-voting-token-staked/src/tests/multitest/tf_module_mock.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/integration_tests.rs rename to contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/mod.rs similarity index 100% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/mod.rs rename to contracts/voting/dao-voting-token-staked/src/tests/test_tube/mod.rs diff --git a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/test_env.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs similarity index 98% rename from contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/test_env.rs rename to contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs index 26b706de5..493f6a511 100644 --- a/contracts/voting/dao-voting-token-factory-staked/src/tests/test_tube/test_env.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs @@ -319,7 +319,7 @@ impl<'a> TfDaoVotingContract<'a> { .join("..") .join("..") .join("artifacts") - .join("dao_voting_token_factory_staked.wasm"), + .join("dao_voting_token_staked.wasm"), ); match byte_code { Ok(byte_code) => byte_code, @@ -330,7 +330,7 @@ impl<'a> TfDaoVotingContract<'a> { .join("..") .join("..") .join("artifacts") - .join("dao_voting_token_factory_staked-aarch64.wasm"), + .join("dao_voting_token_staked-aarch64.wasm"), ) .unwrap(), } diff --git a/packages/dao-testing/Cargo.toml b/packages/dao-testing/Cargo.toml index a153e57e0..0416bdc84 100644 --- a/packages/dao-testing/Cargo.toml +++ b/packages/dao-testing/Cargo.toml @@ -55,7 +55,6 @@ dao-voting-cw20-staked = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-cw721-staked = { workspace = true } dao-voting-cw721-roles = { workspace = true } -dao-voting-native-staked = { workspace = true } -dao-voting-token-factory-staked = { workspace = true } +dao-voting-token-staked = { workspace = true } voting-v1 = { workspace = true } stake-cw20-v03 = { workspace = true } diff --git a/packages/dao-testing/src/contracts.rs b/packages/dao-testing/src/contracts.rs index e82c2b9c1..b2fb42b27 100644 --- a/packages/dao-testing/src/contracts.rs +++ b/packages/dao-testing/src/contracts.rs @@ -110,9 +110,9 @@ pub fn cw20_balances_voting_contract() -> Box> { pub fn native_staked_balances_voting_contract() -> Box> { let contract = ContractWrapper::new( - dao_voting_native_staked::contract::execute, - dao_voting_native_staked::contract::instantiate, - dao_voting_native_staked::contract::query, + dao_voting_token_staked::contract::execute, + dao_voting_token_staked::contract::instantiate, + dao_voting_token_staked::contract::query, ); Box::new(contract) } diff --git a/scripts/publish.sh b/scripts/publish.sh index e8f688008..5d70f7888 100644 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -163,7 +163,7 @@ cd contracts/voting/dao-voting-cw721-staked cargo hack publish --no-dev-deps --allow-dirty cd "$START_DIR" -cd contracts/voting/dao-voting-native-staked +cd contracts/voting/dao-voting-token-staked cargo hack publish --no-dev-deps --allow-dirty cd "$START_DIR" From c46597e885fe4001f84ee54a054f1f68b1da7a1c Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 8 Sep 2023 20:55:49 -0700 Subject: [PATCH 49/59] No need to have an issuer for existing tokens --- Cargo.lock | 1 - .../src/testing/instantiate.rs | 4 +- .../voting/dao-voting-token-staked/Cargo.toml | 1 - .../dao-voting-token-staked/src/contract.rs | 3 +- .../voting/dao-voting-token-staked/src/msg.rs | 4 +- .../src/tests/multitest/mod.rs | 1 - .../src/tests/multitest/tests.rs | 129 ++++++------------ .../src/tests/multitest/tf_module_mock.rs | 106 -------------- 8 files changed, 48 insertions(+), 201 deletions(-) delete mode 100644 contracts/voting/dao-voting-token-staked/src/tests/multitest/tf_module_mock.rs diff --git a/Cargo.lock b/Cargo.lock index 3e5b50203..457252c88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2168,7 +2168,6 @@ dependencies = [ "osmosis-test-tube", "serde", "thiserror", - "token-bindings", ] [[package]] diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index fd3ea9aa5..ce6d06222 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -266,7 +266,9 @@ pub fn _instantiate_with_native_staked_balances_governance( voting_module_instantiate_info: ModuleInstantiateInfo { code_id: native_stake_id, msg: to_binary(&dao_voting_token_staked::msg::InstantiateMsg { - denom: "ujuno".to_string(), + token_info: dao_voting_token_staked::msg::TokenInfo::Existing { + denom: "ujuno".to_string(), + }, unstaking_duration: None, active_threshold: None, }) diff --git a/contracts/voting/dao-voting-token-staked/Cargo.toml b/contracts/voting/dao-voting-token-staked/Cargo.toml index 9e5413352..553fc2db1 100644 --- a/contracts/voting/dao-voting-token-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-staked/Cargo.toml @@ -37,7 +37,6 @@ dao-interface = { workspace = true } dao-voting = { workspace = true } cw-paginate-storage = { workspace = true } cw-tokenfactory-issuer = { workspace = true, features = ["library"] } -token-bindings = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/voting/dao-voting-token-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs index bf57053e7..31268f54c 100644 --- a/contracts/voting/dao-voting-token-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -76,6 +76,7 @@ pub fn instantiate( ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; } + // TODO only needed for new tokens // Save new token info for use in reply TOKEN_INSTANTIATION_INFO.save(deps.storage, &msg.token_info)?; @@ -100,7 +101,7 @@ pub fn instantiate( let issuer_instantiate_msg = SubMsg::reply_on_success( WasmMsg::Instantiate { admin: Some(info.sender.to_string()), - code_id: msg.token_issuer_code_id, + code_id: token.token_issuer_code_id, msg: to_binary(&IssuerInstantiateMsg::NewToken { subdenom: token.subdenom, })?, diff --git a/contracts/voting/dao-voting-token-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs index c81a95d22..e08123680 100644 --- a/contracts/voting/dao-voting-token-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -28,6 +28,8 @@ pub struct NewDenomMetadata { #[cw_serde] pub struct NewTokenInfo { + /// The code id of the cw-tokenfactory-issuer contract + pub token_issuer_code_id: u64, /// The subdenom of the token to create, will also be used as an alias /// for the denom. The Token Factory denom will have the format of /// factory/{contract_address}/{subdenom} @@ -56,8 +58,6 @@ pub enum TokenInfo { #[cw_serde] pub struct InstantiateMsg { - /// The code id of the cw-tokenfactory-issuer contract - pub token_issuer_code_id: u64, /// New or existing native token to use for voting power. pub token_info: TokenInfo, /// How long until the tokens become liquid again diff --git a/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs index d6b447afb..14f00389d 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/mod.rs @@ -1,2 +1 @@ mod tests; -mod tf_module_mock; diff --git a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs index d03f3d437..ab56d26f1 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs @@ -5,19 +5,16 @@ use crate::msg::{ }; use crate::state::Config; use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cw_controllers::ClaimsResponse; use cw_multi_test::{ - next_block, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, + next_block, App, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, }; use cw_utils::Duration; use dao_interface::voting::{ InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -use token_bindings::TokenFactoryMsg; - -use super::tf_module_mock::TokenFactoryApp as App; const DAO_ADDR: &str = "dao"; const ADDR1: &str = "addr1"; @@ -26,18 +23,8 @@ const DENOM: &str = "ujuno"; const INVALID_DENOM: &str = "uinvalid"; const ODD_DENOM: &str = "uodd"; -fn issuer_contract() -> Box> { +fn hook_counter_contract() -> Box> { let contract = ContractWrapper::new( - cw_tokenfactory_issuer::contract::execute, - cw_tokenfactory_issuer::contract::instantiate, - cw_tokenfactory_issuer::contract::query, - ) - .with_reply(cw_tokenfactory_issuer::contract::reply); - Box::new(contract) -} - -fn hook_counter_contract() -> Box> { - let contract = ContractWrapper::new_with_empty( dao_proposal_hook_counter::contract::execute, dao_proposal_hook_counter::contract::instantiate, dao_proposal_hook_counter::contract::query, @@ -45,19 +32,19 @@ fn hook_counter_contract() -> Box> { Box::new(contract) } -fn staking_contract() -> Box> { - let contract = ContractWrapper::new_with_empty( +fn staking_contract() -> Box> { + let contract = ContractWrapper::new( crate::contract::execute, crate::contract::instantiate, crate::contract::query, ) - .with_reply_empty(crate::contract::reply) - .with_migrate_empty(crate::contract::migrate); + .with_reply(crate::contract::reply) + .with_migrate(crate::contract::migrate); Box::new(contract) } fn mock_app() -> App { - let mut app = App::new(); + let mut app = App::default(); app.sudo(SudoMsg::Bank(BankSudo::Mint { to_address: DAO_ADDR.to_string(), amount: vec![ @@ -216,14 +203,13 @@ fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { #[test] fn test_instantiate_existing() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); // Populated fields instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -237,7 +223,6 @@ fn test_instantiate_existing() { &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -262,7 +247,7 @@ fn test_instantiate_existing() { #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] fn test_instantiate_invalid_unstaking_duration_height() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); // Populated fields @@ -270,7 +255,6 @@ fn test_instantiate_invalid_unstaking_duration_height() { &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -286,7 +270,7 @@ fn test_instantiate_invalid_unstaking_duration_height() { #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] fn test_instantiate_invalid_unstaking_duration_time() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); // Populated fields with height @@ -294,7 +278,6 @@ fn test_instantiate_invalid_unstaking_duration_time() { &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -310,13 +293,12 @@ fn test_instantiate_invalid_unstaking_duration_time() { #[should_panic(expected = "Must send reserve token 'ujuno'")] fn test_stake_invalid_denom() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -332,13 +314,12 @@ fn test_stake_invalid_denom() { #[test] fn test_stake_valid_denom() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -356,13 +337,12 @@ fn test_stake_valid_denom() { #[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] fn test_unstake_none_staked() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -378,13 +358,12 @@ fn test_unstake_none_staked() { #[should_panic(expected = "Amount being unstaked must be non-zero")] fn test_unstake_zero_tokens() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -400,13 +379,12 @@ fn test_unstake_zero_tokens() { #[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] fn test_unstake_invalid_balance() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -426,13 +404,12 @@ fn test_unstake_invalid_balance() { #[test] fn test_unstake() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -464,13 +441,12 @@ fn test_unstake() { #[test] fn test_unstake_no_unstaking_duration() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -504,13 +480,12 @@ fn test_unstake_no_unstaking_duration() { #[should_panic(expected = "Nothing to claim")] fn test_claim_no_claims() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -526,13 +501,12 @@ fn test_claim_no_claims() { #[should_panic(expected = "Nothing to claim")] fn test_claim_claim_not_reached() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -556,13 +530,12 @@ fn test_claim_claim_not_reached() { #[test] fn test_claim() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -610,13 +583,12 @@ fn test_claim() { #[should_panic(expected = "Unauthorized")] fn test_update_config_invalid_sender() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -632,13 +604,12 @@ fn test_update_config_invalid_sender() { #[test] fn test_update_config_as_owner() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -663,13 +634,12 @@ fn test_update_config_as_owner() { #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] fn test_update_config_invalid_duration() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -685,13 +655,12 @@ fn test_update_config_invalid_duration() { #[test] fn test_query_dao() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -708,13 +677,12 @@ fn test_query_dao() { #[test] fn test_query_info() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -731,13 +699,12 @@ fn test_query_info() { #[test] fn test_query_claims() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -770,13 +737,12 @@ fn test_query_claims() { #[test] fn test_query_get_config() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -797,13 +763,12 @@ fn test_query_get_config() { #[test] fn test_voting_power_queries() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -906,13 +871,12 @@ fn test_voting_power_queries() { #[test] fn test_query_list_stakers() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -994,13 +958,12 @@ fn test_query_list_stakers() { #[should_panic(expected = "Active threshold count must be greater than zero")] fn test_instantiate_zero_active_threshold_count() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1015,14 +978,13 @@ fn test_instantiate_zero_active_threshold_count() { #[test] fn test_active_threshold_absolute_count() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1055,13 +1017,12 @@ fn test_active_threshold_absolute_count() { #[test] fn test_active_threshold_percent() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1094,13 +1055,12 @@ fn test_active_threshold_percent() { #[test] fn test_active_threshold_percent_rounds_up() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: ODD_DENOM.to_string(), }, @@ -1142,13 +1102,12 @@ fn test_active_threshold_percent_rounds_up() { #[test] fn test_active_threshold_none() { let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1168,13 +1127,12 @@ fn test_active_threshold_none() { #[test] fn test_update_active_threshold() { let mut app = mock_app(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1219,13 +1177,12 @@ fn test_update_active_threshold() { #[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] fn test_active_threshold_percentage_gt_100() { let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1241,13 +1198,12 @@ fn test_active_threshold_percentage_gt_100() { #[should_panic(expected = "Active threshold percentage must be greater than 0 and less than 1")] fn test_active_threshold_percentage_lte_0() { let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1263,13 +1219,12 @@ fn test_active_threshold_percentage_lte_0() { #[should_panic(expected = "Absolute count threshold cannot be greater than the total token supply")] fn test_active_threshold_absolute_count_invalid() { let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1284,14 +1239,13 @@ fn test_active_threshold_absolute_count_invalid() { #[test] fn test_add_remove_hooks() { let mut app = App::default(); - let issuer_id = app.store_code(issuer_contract()); + let staking_id = app.store_code(staking_contract()); let addr = instantiate_staking( &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, @@ -1347,8 +1301,8 @@ fn test_add_remove_hooks() { #[test] fn test_staking_hooks() { let mut app = mock_app(); + let staking_id = app.store_code(staking_contract()); - let issuer_id = app.store_code(issuer_contract()); let hook_id = app.store_code(hook_counter_contract()); let hook = app @@ -1368,7 +1322,6 @@ fn test_staking_hooks() { &mut app, staking_id, InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::Existing { denom: DENOM.to_string(), }, diff --git a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tf_module_mock.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tf_module_mock.rs deleted file mode 100644 index 21070bbcc..000000000 --- a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tf_module_mock.rs +++ /dev/null @@ -1,106 +0,0 @@ -use anyhow::{bail, Result as AnyResult}; -use cosmwasm_schema::schemars::JsonSchema; -use cosmwasm_std::testing::{MockApi, MockStorage}; -use cosmwasm_std::{ - Addr, Api, Binary, BlockInfo, CustomQuery, Empty, Querier, QuerierResult, Storage, -}; -use cw_multi_test::{ - App, AppResponse, BankKeeper, BasicAppBuilder, CosmosRouter, Module, WasmKeeper, -}; -use serde::de::DeserializeOwned; -use std::fmt::Debug; -use std::ops::{Deref, DerefMut}; -use token_bindings::TokenFactoryMsg; - -// A Mock cw-multi-test module for token factory -pub struct TokenFactoryModule {} - -impl Module for TokenFactoryModule { - type ExecT = TokenFactoryMsg; - type QueryT = Empty; - type SudoT = Empty; - - // Builds a mock rust implementation of the expected Token Factory functionality for testing - fn execute( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - _sender: Addr, - _msg: Self::ExecT, - ) -> AnyResult - where - ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static, - { - bail!("execute not implemented for TokenFactoryModule") - } - - fn sudo( - &self, - _api: &dyn Api, - _storage: &mut dyn Storage, - _router: &dyn CosmosRouter, - _block: &BlockInfo, - _msg: Self::SudoT, - ) -> AnyResult - where - ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, - QueryC: CustomQuery + DeserializeOwned + 'static, - { - bail!("sudo not implemented for TokenFactoryModule") - } - - fn query( - &self, - _api: &dyn Api, - _storage: &dyn Storage, - _querier: &dyn Querier, - _block: &BlockInfo, - _request: Self::QueryT, - ) -> anyhow::Result { - bail!("query not implemented for TokenFactoryModule") - } -} - -pub type TokenFactoryAppWrapped = - App>; - -pub struct TokenFactoryApp(TokenFactoryAppWrapped); - -impl Deref for TokenFactoryApp { - type Target = TokenFactoryAppWrapped; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for TokenFactoryApp { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Querier for TokenFactoryApp { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - self.0.raw_query(bin_request) - } -} - -impl Default for TokenFactoryApp { - fn default() -> Self { - Self::new() - } -} - -impl TokenFactoryApp { - pub fn new() -> Self { - Self( - BasicAppBuilder::::new_custom() - .with_custom(TokenFactoryModule {}) - .build(|_router, _, _storage| {}), - ) - } -} From cc764d0b6d7d0fd7b399c0d5d8760fe9b40b98b8 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Fri, 8 Sep 2023 20:58:50 -0700 Subject: [PATCH 50/59] Update Schema --- .../dao-dao-core/schema/dao-dao-core.json | 6 ++--- .../dao-migrator/schema/dao-migrator.json | 24 +++++++++---------- .../dao-pre-propose-approval-single.json | 6 ++--- .../schema/dao-pre-propose-multiple.json | 6 ++--- .../schema/dao-pre-propose-single.json | 6 ++--- .../schema/dao-proposal-condorcet.json | 12 +++++----- .../schema/dao-proposal-multiple.json | 24 +++++++++---------- .../schema/dao-proposal-single.json | 24 +++++++++---------- ...aked.json => dao-voting-token-staked.json} | 20 ++++++++-------- 9 files changed, 64 insertions(+), 64 deletions(-) rename contracts/voting/dao-voting-token-staked/schema/{dao-voting-token-factory-staked.json => dao-voting-token-staked.json} (99%) diff --git a/contracts/dao-dao-core/schema/dao-dao-core.json b/contracts/dao-dao-core/schema/dao-dao-core.json index fec05f159..c2e9cf422 100644 --- a/contracts/dao-dao-core/schema/dao-dao-core.json +++ b/contracts/dao-dao-core/schema/dao-dao-core.json @@ -1029,7 +1029,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1147,7 +1147,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1398,7 +1398,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/external/dao-migrator/schema/dao-migrator.json b/contracts/external/dao-migrator/schema/dao-migrator.json index 3e47c1302..db060770c 100644 --- a/contracts/external/dao-migrator/schema/dao-migrator.json +++ b/contracts/external/dao-migrator/schema/dao-migrator.json @@ -1045,7 +1045,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1163,7 +1163,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1600,7 +1600,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -2899,7 +2899,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -3017,7 +3017,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -3490,7 +3490,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -4080,7 +4080,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -4198,7 +4198,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -4651,7 +4651,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -5206,7 +5206,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -5324,7 +5324,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -5797,7 +5797,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json index c11084723..e8675b00a 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json +++ b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json @@ -816,7 +816,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -934,7 +934,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1273,7 +1273,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json index d0fb4ab83..e2972c646 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json +++ b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json @@ -734,7 +734,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -852,7 +852,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1228,7 +1228,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json index 9cf71147a..815c31c6b 100644 --- a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json +++ b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json @@ -734,7 +734,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -852,7 +852,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1192,7 +1192,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json b/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json index bf32a13d6..88f46d48b 100644 --- a/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json +++ b/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json @@ -586,7 +586,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -704,7 +704,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -956,7 +956,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -1727,7 +1727,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1845,7 +1845,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -2250,7 +2250,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json index 3783a86e8..8d67f6e3c 100644 --- a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json +++ b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json @@ -1019,7 +1019,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1137,7 +1137,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1521,7 +1521,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -2785,7 +2785,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -2903,7 +2903,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -3350,7 +3350,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -3966,7 +3966,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -4084,7 +4084,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -4512,7 +4512,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -5104,7 +5104,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -5222,7 +5222,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -5669,7 +5669,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json index 87f802625..44da656fc 100644 --- a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json +++ b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json @@ -1045,7 +1045,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -1163,7 +1163,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -1600,7 +1600,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -2899,7 +2899,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -3017,7 +3017,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -3490,7 +3490,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -4080,7 +4080,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -4198,7 +4198,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -4651,7 +4651,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { @@ -5206,7 +5206,7 @@ ] }, "channel_id": { - "description": "exisiting channel to send the tokens over", + "description": "existing channel to send the tokens over", "type": "string" }, "timeout": { @@ -5324,7 +5324,7 @@ "minimum": 0.0 }, "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -5797,7 +5797,7 @@ } }, "label": { - "description": "A human-readbale label for the contract", + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", "type": "string" }, "msg": { diff --git a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-factory-staked.json b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json similarity index 99% rename from contracts/voting/dao-voting-token-staked/schema/dao-voting-token-factory-staked.json rename to contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json index 0622e2358..b102a0e25 100644 --- a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-factory-staked.json +++ b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json @@ -7,8 +7,7 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "token_info", - "token_issuer_code_id" + "token_info" ], "properties": { "active_threshold": { @@ -30,12 +29,6 @@ } ] }, - "token_issuer_code_id": { - "description": "The code id of the cw-tokenfactory-issuer contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "unstaking_duration": { "description": "How long until the tokens become liquid again", "anyOf": [ @@ -223,7 +216,8 @@ "type": "object", "required": [ "initial_balances", - "subdenom" + "subdenom", + "token_issuer_code_id" ], "properties": { "initial_balances": { @@ -258,6 +252,12 @@ "subdenom": { "description": "The subdenom of the token to create, will also be used as an alias for the denom. The Token Factory denom will have the format of factory/{contract_address}/{subdenom}", "type": "string" + }, + "token_issuer_code_id": { + "description": "The code id of the cw-tokenfactory-issuer contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false @@ -265,7 +265,7 @@ "TokenInfo": { "oneOf": [ { - "description": "Uses an existing Token Factory token and creates a new issuer contract. Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, must be done manually. Note, for chain controlled denoms or IBC tokens use dao-voting-token-staked.", + "description": "Uses an existing Token Factory token and creates a new issuer contract. Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, must be done manually.", "type": "object", "required": [ "existing" From 87aaf6d08e8a348e3e227da4832a8d3a8574599c Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 13:39:14 -0700 Subject: [PATCH 51/59] Fix up integration tests --- .../src/tests/test_tube/integration_tests.rs | 16 ++++++++------ .../src/tests/test_tube/test_env.rs | 22 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs index 80555688a..e6216844a 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Coin, Uint128}; use cw_tokenfactory_issuer::msg::DenomUnit; -use dao_voting::threshold::ActiveThreshold; +use dao_voting::threshold::{ActiveThreshold, ActiveThresholdError}; use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_test_tube::{Account, OsmosisTestApp}; @@ -81,8 +81,8 @@ fn test_instantiate_no_dao_balance() { let vp_contract = env .instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "ucat".to_string(), metadata: Some(NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -144,8 +144,8 @@ fn test_instantiate_no_metadata() { env.instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "ucat".to_string(), metadata: None, initial_balances: vec![InitialBalance { @@ -174,8 +174,8 @@ fn test_instantiate_invalid_metadata_fails() { env.instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "cat".to_string(), metadata: Some(NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -216,8 +216,8 @@ fn test_instantiate_invalid_active_threshold_count_fails() { let err = env .instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "cat".to_string(), metadata: Some(NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -248,7 +248,9 @@ fn test_instantiate_invalid_active_threshold_count_fails() { assert_eq!( err, - TfDaoVotingContract::execute_submessage_error(ContractError::InvalidAbsoluteCount {}) + TfDaoVotingContract::execute_submessage_error(ContractError::ActiveThresholdError( + ActiveThresholdError::InvalidAbsoluteCount {} + )) ); } @@ -264,8 +266,8 @@ fn test_instantiate_no_initial_balances_fails() { let err = env .instantiate( &InstantiateMsg { - token_issuer_code_id: tf_issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: tf_issuer_id, subdenom: "ucat".to_string(), metadata: Some(NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), diff --git a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs index 493f6a511..a65173cde 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs @@ -118,8 +118,8 @@ impl TestEnvBuilder { let vp_contract = TfDaoVotingContract::deploy( app, &InstantiateMsg { - token_issuer_code_id: issuer_id, token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: issuer_id, subdenom: DENOM.to_string(), metadata: Some(crate::msg::NewDenomMetadata { description: "Awesome token, get it meow!".to_string(), @@ -172,14 +172,18 @@ impl TestEnvBuilder { let issuer_addr = TfDaoVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); - let tf_issuer = TokenfactoryIssuer::new_with_values( - app, - self.instantiate_msg - .expect("instantiate msg not set") - .token_issuer_code_id, - issuer_addr, - ) - .unwrap(); + let issuer_id: u64; + if let TokenInfo::New(token) = self + .instantiate_msg + .expect("instantiate msg not set") + .token_info + { + issuer_id = token.token_issuer_code_id; + } else { + panic!("TokenInfo is not New"); + } + + let tf_issuer = TokenfactoryIssuer::new_with_values(app, issuer_id, issuer_addr).unwrap(); TestEnv { app, From 21e91eeb4fa254639539d06c9ba86fe3f45b885e Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 14:29:55 -0700 Subject: [PATCH 52/59] Remove owner from dao-voting-cw721-staked The concept of ownership doesn't really make sense for voting contracts. IMO the owner should always be the DAO. --- .../src/tests/dao_voting_cw721_staked_test.rs | 14 +-- contracts/external/cw721-roles/src/tests.rs | 1 - .../src/testing/instantiate.rs | 1 - .../src/testing/instantiate.rs | 1 - .../schema/dao-voting-cw721-staked.json | 68 ----------- .../dao-voting-cw721-staked/src/contract.rs | 63 +++------- .../voting/dao-voting-cw721-staked/src/msg.rs | 5 - .../dao-voting-cw721-staked/src/state.rs | 1 - .../src/testing/adversarial.rs | 10 +- .../src/testing/execute.rs | 6 +- .../src/testing/hooks.rs | 7 +- .../src/testing/mod.rs | 5 +- .../src/testing/tests.rs | 110 ++---------------- 13 files changed, 42 insertions(+), 250 deletions(-) diff --git a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs index e08d81fa9..dd13c47fb 100644 --- a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs +++ b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs @@ -1,7 +1,6 @@ use cosm_orc::orchestrator::{ExecReq, SigningKey}; use cosmwasm_std::{Binary, Empty, Uint128}; use cw_utils::Duration; -use dao_interface::state::Admin; use test_context::test_context; use dao_voting_cw721_staked as module; @@ -38,7 +37,6 @@ pub fn instantiate_cw721_base(chain: &mut Chain, key: &SigningKey, minter: &str) fn setup_test( chain: &mut Chain, - owner: Option, unstaking_duration: Option, key: &SigningKey, minter: &str, @@ -50,7 +48,6 @@ fn setup_test( CONTRACT_NAME, "instantiate_dao_voting_cw721_staked", &module::msg::InstantiateMsg { - owner, nft_contract: module::msg::NftContract::Existing { address: cw721.clone(), }, @@ -169,7 +166,7 @@ fn cw721_stake_tokens(chain: &mut Chain) { let user_addr = chain.users["user1"].account.address.clone(); let user_key = chain.users["user1"].key.clone(); - let CommonTest { module, .. } = setup_test(chain, None, None, &user_key, &user_addr); + let CommonTest { module, .. } = setup_test(chain, None, &user_key, &user_addr); mint_and_stake_nft(chain, &user_key, &user_addr, &module, "a"); @@ -202,13 +199,8 @@ fn cw721_stake_max_claims_works(chain: &mut Chain) { let user_addr = chain.users["user1"].account.address.clone(); let user_key = chain.users["user1"].key.clone(); - let CommonTest { module, .. } = setup_test( - chain, - None, - Some(Duration::Height(1)), - &user_key, - &user_addr, - ); + let CommonTest { module, .. } = + setup_test(chain, Some(Duration::Height(1)), &user_key, &user_addr); // Create `MAX_CLAIMS` claims. diff --git a/contracts/external/cw721-roles/src/tests.rs b/contracts/external/cw721-roles/src/tests.rs index 270a1bc0e..0783af57a 100644 --- a/contracts/external/cw721-roles/src/tests.rs +++ b/contracts/external/cw721-roles/src/tests.rs @@ -260,7 +260,6 @@ fn test_send_permissions() { dao_voting_cw721_staked_id, Addr::unchecked(DAO), &Cw721StakedInstantiateMsg { - owner: None, nft_contract: NftContract::Existing { address: cw721_addr.to_string(), }, diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index ce6d06222..2d767ae5d 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -148,7 +148,6 @@ pub fn _instantiate_with_staked_cw721_governance( voting_module_instantiate_info: ModuleInstantiateInfo { code_id: cw721_stake_id, msg: to_binary(&dao_voting_cw721_staked::msg::InstantiateMsg { - owner: Some(Admin::CoreModule {}), unstaking_duration: None, nft_contract: dao_voting_cw721_staked::msg::NftContract::Existing { address: nft_address.to_string(), diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index eb8830b7a..aafcb5abe 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -146,7 +146,6 @@ pub(crate) fn instantiate_with_staked_cw721_governance( voting_module_instantiate_info: ModuleInstantiateInfo { code_id: cw721_stake_id, msg: to_binary(&dao_voting_cw721_staked::msg::InstantiateMsg { - owner: Some(Admin::CoreModule {}), unstaking_duration: None, nft_contract: dao_voting_cw721_staked::msg::NftContract::Existing { address: nft_address.to_string(), diff --git a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json index c1e16bcf3..aabc38089 100644 --- a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json +++ b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json @@ -29,17 +29,6 @@ } ] }, - "owner": { - "description": "TODO use cw_ownable May change unstaking duration and add hooks.", - "anyOf": [ - { - "$ref": "#/definitions/Admin" - }, - { - "type": "null" - } - ] - }, "unstaking_duration": { "description": "Amount of time between unstaking and tokens being avaliable. To unstake with no delay, leave as `None`.", "anyOf": [ @@ -103,47 +92,6 @@ } ] }, - "Admin": { - "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", - "oneOf": [ - { - "description": "Set the admin to a specified address.", - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets the admin as the core module address.", - "type": "object", - "required": [ - "core_module" - ], - "properties": { - "core_module": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, "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" @@ -332,12 +280,6 @@ "type": "null" } ] - }, - "owner": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false @@ -820,16 +762,6 @@ "nft_address": { "$ref": "#/definitions/Addr" }, - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, "unstaking_duration": { "anyOf": [ { diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 0de26769a..82e6ac050 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -10,7 +10,6 @@ use cw721::{Cw721QueryMsg, Cw721ReceiveMsg, NumTokensResponse}; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; use dao_hooks::nft_stake::{stake_nft_hook_msgs, unstake_nft_hook_msgs}; -use dao_interface::state::Admin; use dao_interface::voting::IsActiveResponse; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; @@ -87,15 +86,6 @@ pub fn instantiate( DAO.save(deps.storage, &info.sender)?; - let owner = msg - .owner - .as_ref() - .map(|owner| match owner { - Admin::Address { addr } => deps.api.addr_validate(addr), - Admin::CoreModule {} => Ok(info.sender.clone()), - }) - .transpose()?; - if let Some(active_threshold) = msg.active_threshold.as_ref() { match active_threshold { ActiveThreshold::Percentage { percent } => { @@ -128,7 +118,6 @@ pub fn instantiate( match msg.nft_contract { NftContract::Existing { address } => { let config = Config { - owner: owner.clone(), nft_address: deps.api.addr_validate(&address)?, unstaking_duration: msg.unstaking_duration, }; @@ -136,12 +125,6 @@ pub fn instantiate( Ok(Response::default() .add_attribute("method", "instantiate") - .add_attribute( - "owner", - owner - .map(|a| a.into_string()) - .unwrap_or_else(|| "None".to_string()), - ) .add_attribute("nft_contract", address)) } NftContract::New { @@ -169,7 +152,6 @@ pub fn instantiate( // Save config with empty nft_address let config = Config { - owner: owner.clone(), nft_address: Addr::unchecked(""), unstaking_duration: msg.unstaking_duration, }; @@ -192,12 +174,6 @@ pub fn instantiate( Ok(Response::default() .add_attribute("method", "instantiate") - .add_attribute( - "owner", - owner - .map(|a| a.into_string()) - .unwrap_or_else(|| "None".to_string()), - ) .add_submessage(instantiate_msg)) } } @@ -214,9 +190,7 @@ pub fn execute( ExecuteMsg::ReceiveNft(msg) => execute_stake(deps, env, info, msg), ExecuteMsg::Unstake { token_ids } => execute_unstake(deps, env, info, token_ids), ExecuteMsg::ClaimNfts {} => execute_claim_nfts(deps, env, info), - ExecuteMsg::UpdateConfig { owner, duration } => { - execute_update_config(info, deps, owner, duration) - } + ExecuteMsg::UpdateConfig { duration } => execute_update_config(info, deps, duration), ExecuteMsg::AddHook { addr } => execute_add_hook(deps, info, addr), ExecuteMsg::RemoveHook { addr } => execute_remove_hook(deps, info, addr), ExecuteMsg::UpdateActiveThreshold { new_threshold } => { @@ -386,32 +360,21 @@ pub fn execute_claim_nfts( pub fn execute_update_config( info: MessageInfo, deps: DepsMut, - new_owner: Option, duration: Option, ) -> Result { let mut config: Config = CONFIG.load(deps.storage)?; + let dao = DAO.load(deps.storage)?; - if config.owner.map_or(true, |owner| owner != info.sender) { - return Err(ContractError::NotOwner {}); + // Only the DAO can update the config. + if info.sender != dao { + return Err(ContractError::Unauthorized {}); } - let new_owner = new_owner - .map(|new_owner| deps.api.addr_validate(&new_owner)) - .transpose()?; - - config.owner = new_owner; config.unstaking_duration = duration; CONFIG.save(deps.storage, &config)?; Ok(Response::default() .add_attribute("action", "update_config") - .add_attribute( - "owner", - config - .owner - .map(|a| a.to_string()) - .unwrap_or_else(|| "none".to_string()), - ) .add_attribute( "unstaking_duration", config @@ -426,9 +389,11 @@ pub fn execute_add_hook( info: MessageInfo, addr: String, ) -> Result { - let config: Config = CONFIG.load(deps.storage)?; - if config.owner.map_or(true, |owner| owner != info.sender) { - return Err(ContractError::NotOwner {}); + let dao = DAO.load(deps.storage)?; + + // Only the DAO can add a hook + if info.sender != dao { + return Err(ContractError::Unauthorized {}); } let hook = deps.api.addr_validate(&addr)?; @@ -444,9 +409,11 @@ pub fn execute_remove_hook( info: MessageInfo, addr: String, ) -> Result { - let config: Config = CONFIG.load(deps.storage)?; - if config.owner.map_or(true, |owner| owner != info.sender) { - return Err(ContractError::NotOwner {}); + let dao = DAO.load(deps.storage)?; + + // Only the DAO can remove a hook + if info.sender != dao { + return Err(ContractError::Unauthorized {}); } let hook = deps.api.addr_validate(&addr)?; diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 643247817..f47d82533 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -3,7 +3,6 @@ use cosmwasm_std::Binary; use cw721::Cw721ReceiveMsg; use cw_utils::Duration; use dao_dao_macros::{active_query, voting_module_query}; -use dao_interface::state::Admin; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; #[cw_serde] @@ -28,9 +27,6 @@ pub enum NftContract { #[cw_serde] pub struct InstantiateMsg { - /// TODO use cw_ownable - /// May change unstaking duration and add hooks. - pub owner: Option, /// Address of the cw721 NFT contract that may be staked. pub nft_contract: NftContract, /// Amount of time between unstaking and tokens being @@ -55,7 +51,6 @@ pub enum ExecuteMsg { }, ClaimNfts {}, UpdateConfig { - owner: Option, duration: Option, }, AddHook { diff --git a/contracts/voting/dao-voting-cw721-staked/src/state.rs b/contracts/voting/dao-voting-cw721-staked/src/state.rs index a3cded8ba..0d8e8b62f 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/state.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/state.rs @@ -10,7 +10,6 @@ use crate::ContractError; #[cw_serde] pub struct Config { - pub owner: Option, pub nft_address: Addr, pub unstaking_duration: Option, } diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs index f8479c1c3..e5c587564 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs @@ -27,7 +27,7 @@ fn test_circular_stake() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -72,7 +72,7 @@ fn test_immediate_unstake() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -94,7 +94,7 @@ fn test_immediate_unstake() -> anyhow::Result<()> { fn test_stake_wrong_nft() -> anyhow::Result<()> { let CommonTest { mut app, module, .. - } = setup_test(None, None); + } = setup_test(None); let other_nft = instantiate_cw721_base(&mut app, CREATOR_ADDR, CREATOR_ADDR); let res = mint_and_stake_nft(&mut app, &other_nft, &module, CREATOR_ADDR, "1"); @@ -115,7 +115,7 @@ fn test_query_the_future() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; @@ -154,7 +154,7 @@ fn test_bypass_max_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); let mut to_stake = vec![]; for i in 1..(MAX_CLAIMS + 10) { let i_str = &i.to_string(); diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs index de31d35e8..dd5b60877 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs @@ -96,16 +96,12 @@ pub fn update_config( app: &mut App, module: &Addr, sender: &str, - owner: Option<&str>, duration: Option, ) -> AnyResult { app.execute_contract( addr!(sender), module.clone(), - &ExecuteMsg::UpdateConfig { - owner: owner.map(str::to_string), - duration, - }, + &ExecuteMsg::UpdateConfig { duration }, &[], ) } diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs index e97faf5fb..4bcac6fbc 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/hooks.rs @@ -6,7 +6,7 @@ use dao_hooks::nft_stake::{stake_nft_hook_msgs, unstake_nft_hook_msgs}; use crate::{ contract::execute, - state::{Config, CONFIG, HOOKS}, + state::{Config, CONFIG, DAO, HOOKS}, }; #[test] @@ -31,12 +31,15 @@ fn test_hooks() { .unwrap(); assert_eq!(messages.len(), 0); + // Save a DAO address for the execute messages we're testing. + DAO.save(deps.as_mut().storage, &Addr::unchecked("ekez")) + .unwrap(); + // Save a config for the execute messages we're testing. CONFIG .save( deps.as_mut().storage, &Config { - owner: Some(Addr::unchecked("ekez")), nft_address: Addr::unchecked("ekez-token"), unstaking_duration: None, }, diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs index 7fbae3de5..ea43bc797 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/mod.rs @@ -8,8 +8,6 @@ mod tests; use cosmwasm_std::Addr; use cw_multi_test::{App, Executor}; use cw_utils::Duration; - -use dao_interface::state::Admin; use dao_testing::contracts::voting_cw721_staked_contract; use crate::msg::{InstantiateMsg, NftContract}; @@ -25,7 +23,7 @@ pub(crate) struct CommonTest { nft: Addr, } -pub(crate) fn setup_test(owner: Option, unstaking_duration: Option) -> CommonTest { +pub(crate) fn setup_test(unstaking_duration: Option) -> CommonTest { let mut app = App::default(); let module_id = app.store_code(voting_cw721_staked_contract()); @@ -35,7 +33,6 @@ pub(crate) fn setup_test(owner: Option, unstaking_duration: Option anyhow::Result<()> { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -90,7 +87,7 @@ fn test_stake_tokens() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); let total_power = query_total_power(&app, &module, None)?; let voting_power = query_voting_power(&app, &module, CREATOR_ADDR, None)?; @@ -127,7 +124,7 @@ fn test_unstake_tokens_no_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, None); + } = setup_test(None); let friend = "friend"; @@ -185,7 +182,7 @@ fn test_update_config() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(Some(Admin::CoreModule {}), Some(Duration::Height(3))); + } = setup_test(Some(Duration::Height(3))); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -203,14 +200,8 @@ fn test_update_config() -> anyhow::Result<()> { } ); - // Make friend the new owner. - update_config( - &mut app, - &module, - CREATOR_ADDR, - Some("friend"), - Some(Duration::Time(1)), - )?; + // Update duration + update_config(&mut app, &module, CREATOR_ADDR, Some(Duration::Time(1)))?; // Existing claims should remain unchanged. let claims = query_claims(&app, &module, CREATOR_ADDR)?; @@ -258,39 +249,6 @@ fn test_update_config() -> anyhow::Result<()> { let claims = query_claims(&app, &module, CREATOR_ADDR)?; assert_eq!(claims, NftClaimsResponse { nft_claims: vec![] }); - // Creator can no longer do config updates. - let res = update_config( - &mut app, - &module, - CREATOR_ADDR, - Some("friend"), - Some(Duration::Time(1)), - ); - is_error!(res => "Only the owner of this contract my execute this message"); - - // Friend can still do config updates, and even remove themselves - // as the owner. - update_config(&mut app, &module, "friend", None, None)?; - let config = query_config(&app, &module)?; - assert_eq!( - config, - Config { - owner: None, - nft_address: nft, - unstaking_duration: None - } - ); - - // Friend has removed themselves. - let res = update_config( - &mut app, - &module, - "friend", - Some("friend"), - Some(Duration::Time(1)), - ); - is_error!(res => "Only the owner of this contract my execute this message"); - Ok(()) } @@ -303,7 +261,7 @@ fn test_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(Some(Admin::CoreModule {}), Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -346,7 +304,7 @@ fn test_max_claims() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(None, Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); for i in 0..MAX_CLAIMS { let i_str = &i.to_string(); @@ -368,7 +326,7 @@ fn test_list_staked_nfts() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test(Some(Admin::CoreModule {}), Some(Duration::Height(1))); + } = setup_test(Some(Duration::Height(1))); mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; @@ -413,7 +371,7 @@ fn test_list_staked_nfts() -> anyhow::Result<()> { #[test] fn test_info_query_works() -> anyhow::Result<()> { - let CommonTest { app, module, .. } = setup_test(None, None); + let CommonTest { app, module, .. } = setup_test(None); let info = query_info(&app, &module)?; assert_eq!(info.info.version, env!("CARGO_PKG_VERSION").to_string()); Ok(()) @@ -426,12 +384,7 @@ fn test_add_remove_hooks() -> anyhow::Result<()> { mut app, module, nft, - } = setup_test( - Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), - None, - ); + } = setup_test(None); add_hook(&mut app, &module, CREATOR_ADDR, "meow")?; remove_hook(&mut app, &module, CREATOR_ADDR, "meow")?; @@ -456,7 +409,7 @@ fn test_add_remove_hooks() -> anyhow::Result<()> { is_error!(res => "Given address not registered as a hook"); let res = add_hook(&mut app, &module, "ekez", "evil"); - is_error!(res => "Only the owner of this contract my execute this message"); + is_error!(res => "Unauthorized"); Ok(()) } @@ -472,9 +425,6 @@ fn test_instantiate_zero_active_threshold_count() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -515,9 +465,6 @@ fn test_instantiate_invalid_active_threshold_count_new_nft() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -558,9 +505,6 @@ fn test_instantiate_invalid_active_threshold_count_existing_nft() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::Existing { address: cw721_addr.to_string(), }, @@ -587,9 +531,6 @@ fn test_active_threshold_absolute_count() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -670,9 +611,6 @@ fn test_active_threshold_percent() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -734,9 +672,6 @@ fn test_active_threshold_percent_rounds_up() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -839,9 +774,6 @@ fn test_update_active_threshold() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -916,9 +848,6 @@ fn test_active_threshold_percentage_gt_100() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -959,9 +888,6 @@ fn test_active_threshold_percentage_lte_0() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -1002,9 +928,6 @@ fn test_invalid_instantiate_msg() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -1044,9 +967,6 @@ fn test_no_initial_nfts_fails() { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: cw721_id, label: "Test NFT".to_string(), @@ -1107,9 +1027,6 @@ fn test_instantiate_with_new_sg721_collection() -> anyhow::Result<()> { module_id, Addr::unchecked(CREATOR_ADDR), &InstantiateMsg { - owner: Some(Admin::Address { - addr: CREATOR_ADDR.to_string(), - }), nft_contract: NftContract::New { code_id: sg721_id, label: "Test NFT".to_string(), @@ -1180,9 +1097,6 @@ fn test_instantiate_with_new_sg721_collection_abs_count_validation() { module_id, Addr::unchecked("contract0"), &InstantiateMsg { - owner: Some(Admin::Address { - addr: "contract0".to_string(), - }), nft_contract: NftContract::New { code_id: sg721_id, label: "Test NFT".to_string(), From 2d40452530283982d7079aed6d48f04e782e9d88 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 15:41:47 -0700 Subject: [PATCH 53/59] Improve TF docs --- .../external/cw-tokenfactory-issuer/README.md | 73 ++++++++++--------- .../voting/dao-voting-token-staked/README.md | 18 ++++- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/README.md b/contracts/external/cw-tokenfactory-issuer/README.md index 100273e48..e54174cba 100644 --- a/contracts/external/cw-tokenfactory-issuer/README.md +++ b/contracts/external/cw-tokenfactory-issuer/README.md @@ -7,6 +7,7 @@ This repo contains a set of contracts that when used in conjunction with the x/t - Granting and revoking allowances for the minting and burning of tokens - Updating token metadata - Updating the contract owner or Token Factory admin +- And more! (see [Advanced Features](#advanced-features)) It is intended to work on multiple chains supporting Token Factory, and has been tested on Juno Network and Osmosis. @@ -14,44 +15,9 @@ The contract has an owner (which can be removed or updated via `ExecuteMsg::Upda The contract is also the admin of the newly created Token Factory denom. For minting and burning, users then interact with the contract using its own ExecuteMsgs which trigger the contract's access control logic, and the contract then dispatches tokenfactory sdk.Msgs from its own contract account. -### Advance Features - -This contract supports a number of advanced features which DAOs or token issuers may wist to leverage: -- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to allow transfer to or from -- Denylist to prevent certain addresses from transferring -- Force transfering tokens via the contract owner - -**By default, these features are disabled**, and must be explictly enabled by the contract owner (for example via a DAO governance prop). - -Moreover, for these features to work, your chain must support the `MsgBeforeSendHook` bank module hook. This is not yet available on every chain using Token Factory, and so denylisting and freezing features are not available if `MsgBeforeSendHook` is not supported. - -On chains where `MsgBeforeSendHook` is supported, DAOs or issuers wishing to leverage these features must set the before send hook with `ExecuteMsg::SetBeforeSendHook {}`. - -This method takes a `cosmwasm_address`, which is the address of a contract implement a `SudoMsg::BlockBeforeSend` entrypoint. Normally this will be the address of the `cw_tokenfactory_issuer` contract itself, but it is possible to specify a custom contract. This contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the denylisting of specific accounts as well as the freezing of all transfers if necessary. - -Example message to set before send hook: -``` json -{ - "set_before_send_hook": { - "cosmwasm_address": "
" - } -} -``` - -DAOs or issuers wishing to leverage these features on chains without support can call `ExecuteMsg::SetBeforeSendHook {}` when support is added. - -If a DAO or issuer wishes to disable and removed before send hook related functionality, they simply need to call `ExecuteMsg::SetBeforeSendHook {}` with an empty string for the `cosmwasm_address` like so: -``` json -{ - "set_before_send_hook": { - "cosmwasm_address": "" - } -} -``` - ## Instantiation -When instantiating `cw-tokenfactory-issuer`, you can either create a `new_token` or an `existing_token`. +When instantiating `cw-tokenfactory-issuer`, you can either create a `new` or an `existing`. ### Creating a new Token Factory token @@ -99,3 +65,38 @@ For example, on Juno this could be: ``` The Token Factory standard requires a Token Factory admin per token, by setting to a null address the Token is rendered immutable and the `cw-tokenfactory-issuer` will be unable to make future updates. This is secure as the cryptography that underlies the chain enforces that even with the largest super computers in the world it would take an astonomically large amount of time to compute the private key for this address. + +### Advanced Features + +This contract supports a number of advanced features which DAOs or token issuers may wist to leverage: +- Freezing and unfreezing transfers, with an allowlist to allow specified addresses to allow transfer to or from +- Denylist to prevent certain addresses from transferring +- Force transfering tokens via the contract owner + +**By default, these features are disabled**, and must be explictly enabled by the contract owner (for example via a DAO governance prop). + +Moreover, for these features to work, your chain must support the `MsgBeforeSendHook` bank module hook. This is not yet available on every chain using Token Factory, and so denylisting and freezing features are not available if `MsgBeforeSendHook` is not supported. + +On chains where `MsgBeforeSendHook` is supported, DAOs or issuers wishing to leverage these features must set the before send hook with `ExecuteMsg::SetBeforeSendHook {}`. + +This method takes a `cosmwasm_address`, which is the address of a contract implement a `SudoMsg::BlockBeforeSend` entrypoint. Normally this will be the address of the `cw_tokenfactory_issuer` contract itself, but it is possible to specify a custom contract. This contract contains a `SudoMsg::BlockBeforeSend` hook that allows for the denylisting of specific accounts as well as the freezing of all transfers if necessary. + +Example message to set before send hook: +``` json +{ + "set_before_send_hook": { + "cosmwasm_address": "
" + } +} +``` + +DAOs or issuers wishing to leverage these features on chains without support can call `ExecuteMsg::SetBeforeSendHook {}` when support is added. + +If a DAO or issuer wishes to disable and removed before send hook related functionality, they simply need to call `ExecuteMsg::SetBeforeSendHook {}` with an empty string for the `cosmwasm_address` like so: +``` json +{ + "set_before_send_hook": { + "cosmwasm_address": "" + } +} +``` diff --git a/contracts/voting/dao-voting-token-staked/README.md b/contracts/voting/dao-voting-token-staked/README.md index ec25b5d3d..c9275c390 100644 --- a/contracts/voting/dao-voting-token-staked/README.md +++ b/contracts/voting/dao-voting-token-staked/README.md @@ -5,9 +5,11 @@ Simple native or Token Factory based token voting / staking contract which assum ### Token Factory support `dao_voting_token_staked` leverages the `cw_tokenfactory_issuer` contract for tokenfactory functionality. When instantiated, `dao_voting_token_staked` creates a new `cw_tokenfactory_issuer` contract to manage the new Token, with the DAO as admin and owner (these can be renounced or updated by vote of the DAO). +The `cw_tokenfactory_issuer` contract supports many features, see the [cw_tokenfactory_issuer contract README](../../external/cw-tokenfactory-issuer/README.md) for more information. + ## Instantiation When instantiating a new `dao_voting_token_staked` contract there are two required fields: -- `token_info`: you have the option to leverage an `existing` token or creating a `new` one. +- `token_info`: you have the option to leverage an `existing` native token or creating a `new` one using the Token Factory module. There are a few optional fields: - `unstaking_duration`: can be set to `height` or `time` (in seconds), this is the amount of time that must elapse before a user can claim fully unstaked tokens. If not set, they are instantly claimable. @@ -15,9 +17,11 @@ There are a few optional fields: ### Create a New Token - `token_issuer_code_id`: must be set to a valid Code ID for the `cw_tokenfactory_issuer` contract. +- `initial_balances`: the initial distribution of the new token, there must be at least 1 account with a balance so as the DAO is not locked. + Creating a token has a few additional optional fields: - `metadata`: information about the token. See [Cosmos SDK Coin metadata documentation](https://docs.cosmos.network/main/architecture/adr-024-coin-metadata) for more info on coin metadata. -- `initial_dao_balance`: the initial balance created for the DAO. +- `initial_dao_balance`: the initial balance created for the DAO treasury. Example insantiation mesggage: ``` json @@ -60,13 +64,19 @@ Example insantiation mesggage: ``` ### Use Existing Native Token +`dao-voting-token-staked` can also be used with existing native tokens. They could be in the form of a native denom like `ion`, an IBC token, or a Token Factory token. + Example insantiation mesggage: ``` json { "token_info": { - "new": { - "subdenom": "uion", + "existing": { + "denom": "uion", } + } } ``` + +NOTE: if using an existing Token Factory token, double check the Token Factory admin and consider changing the Token Factory to be the DAO after the DAO is created. + From 4778813234f4dbe26fce48bf1f57491b4644a184 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 18:00:49 -0700 Subject: [PATCH 54/59] Implement two-step ownership transfer for cw_tokenfactory_issue --- Cargo.lock | 3 + .../cw-tokenfactory-issuer/Cargo.toml | 3 +- .../external/cw-tokenfactory-issuer/README.md | 6 +- .../schema/cw-tokenfactory-issuer.json | 238 ++++++++++++++++-- .../cw-tokenfactory-issuer/src/contract.rs | 10 +- .../cw-tokenfactory-issuer/src/error.rs | 3 + .../cw-tokenfactory-issuer/src/execute.rs | 40 ++- .../cw-tokenfactory-issuer/src/helpers.rs | 14 +- .../cw-tokenfactory-issuer/src/msg.rs | 10 +- .../cw-tokenfactory-issuer/src/queries.rs | 13 +- .../cw-tokenfactory-issuer/src/state.rs | 3 - .../tests/cases/allowlist.rs | 4 +- .../tests/cases/burn.rs | 4 +- .../tests/cases/contract_owner.rs | 23 +- .../tests/cases/denom_metadata.rs | 4 +- .../tests/cases/denylist.rs | 6 +- .../tests/cases/force_transfer.rs | 4 +- .../tests/cases/freeze.rs | 4 +- .../tests/cases/instantiate.rs | 9 +- .../tests/cases/mint.rs | 4 +- .../tests/cases/set_before_update_hook.rs | 4 +- .../tests/cases/tokenfactory_admin.rs | 4 +- .../cw-tokenfactory-issuer/tests/test_env.rs | 23 +- .../voting/dao-voting-cw721-staked/README.md | 2 +- .../voting/dao-voting-token-staked/Cargo.toml | 2 + .../dao-voting-token-staked/src/contract.rs | 29 ++- .../src/tests/test_tube/integration_tests.rs | 27 +- .../src/tests/test_tube/test_env.rs | 182 +++++++++++--- .../dao-testing/src/test_tube/dao_dao_core.rs | 127 ++++++++++ .../src/test_tube/dao_proposal_single.rs | 129 ++++++++++ packages/dao-testing/src/test_tube/mod.rs | 6 + 31 files changed, 783 insertions(+), 157 deletions(-) create mode 100644 packages/dao-testing/src/test_tube/dao_dao_core.rs create mode 100644 packages/dao-testing/src/test_tube/dao_proposal_single.rs diff --git a/Cargo.lock b/Cargo.lock index 457252c88..b81b4604b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,6 +1043,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", + "cw-ownable", "cw-storage-plus 1.1.0", "cw2 1.1.0", "osmosis-std", @@ -2153,6 +2154,7 @@ dependencies = [ "cw-controllers 1.1.0", "cw-hooks", "cw-multi-test", + "cw-ownable", "cw-paginate-storage 2.2.0", "cw-storage-plus 1.1.0", "cw-tokenfactory-issuer", @@ -2162,6 +2164,7 @@ dependencies = [ "dao-hooks", "dao-interface", "dao-proposal-hook-counter", + "dao-proposal-single", "dao-testing", "dao-voting 2.2.0", "osmosis-std", diff --git a/contracts/external/cw-tokenfactory-issuer/Cargo.toml b/contracts/external/cw-tokenfactory-issuer/Cargo.toml index 10161d182..66fc81ada 100644 --- a/contracts/external/cw-tokenfactory-issuer/Cargo.toml +++ b/contracts/external/cw-tokenfactory-issuer/Cargo.toml @@ -37,8 +37,9 @@ test-tube = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-storage = { workspace = true } -cw-storage-plus = { workspace = true } cw2 = { workspace = true } +cw-ownable = { workspace = true } +cw-storage-plus = { workspace = true } osmosis-std = { workspace = true } prost = { workspace = true } schemars = { workspace = true } diff --git a/contracts/external/cw-tokenfactory-issuer/README.md b/contracts/external/cw-tokenfactory-issuer/README.md index e54174cba..1cfdcf352 100644 --- a/contracts/external/cw-tokenfactory-issuer/README.md +++ b/contracts/external/cw-tokenfactory-issuer/README.md @@ -11,9 +11,11 @@ This repo contains a set of contracts that when used in conjunction with the x/t It is intended to work on multiple chains supporting Token Factory, and has been tested on Juno Network and Osmosis. -The contract has an owner (which can be removed or updated via `ExecuteMsg::UpdateContractOwner {}`), but it can delegate capabilities to other acccounts. For example, the owner of a contract can delegate minting allowance of 1000 tokens to a new address. +The contract has an owner (which can be removed or updated via `ExecuteMsg::UpdateOwnership {}`), but it can delegate capabilities to other acccounts. For example, the owner of a contract can delegate minting allowance of 1000 tokens to a new address. -The contract is also the admin of the newly created Token Factory denom. For minting and burning, users then interact with the contract using its own ExecuteMsgs which trigger the contract's access control logic, and the contract then dispatches tokenfactory sdk.Msgs from its own contract account. +Ownership functionality for this contract is implemented using the `cw-ownable` library. + +The `cw_tokenfactory_issuer` contract is also the admin of newly created Token Factory denoms. For minting and burning, users then interact with the contract using its own ExecuteMsgs which trigger the contract's access control logic, and the contract then dispatches tokenfactory sdk.Msgs from its own contract account. ## Instantiation diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json index 278af0ffe..ca5ea62fb 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -334,29 +334,71 @@ "additionalProperties": false }, { - "description": "Updates the owner of this contract who is allowed to call privileged methods. NOTE: this is separate from the Token Factory token admin, for this contract to work at all, it needs to the be the Token Factory token admin.\n\nNormally, the contract owner will be a DAO.", + "description": "Updates the owner of this contract who is allowed to call privileged methods. NOTE: this is separate from the Token Factory token admin, for this contract to work at all, it needs to the be the Token Factory token admin.\n\nNormally, the contract owner will be a DAO.\n\nThe `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", "type": "object", "required": [ - "update_contract_owner" + "update_ownership" ], "properties": { - "update_contract_owner": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", "type": "object", "required": [ - "new_owner" + "transfer_ownership" ], "properties": { - "new_owner": { - "type": "string" + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] } - }, - "additionalProperties": false - } - ], - "definitions": { + ] + }, "DenomUnit": { "description": "DenomUnit represents a struct that describes a given denomination unit of the basic token.", "type": "object", @@ -385,6 +427,53 @@ } } }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Metadata": { "description": "Metadata represents a struct that describes a basic token.", "type": "object", @@ -425,9 +514,21 @@ } } }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" } } }, @@ -465,13 +566,12 @@ "additionalProperties": false }, { - "description": "Returns the owner of the contract. Response: OwnerResponse", "type": "object", "required": [ - "owner" + "ownership" ], "properties": { - "owner": { + "ownership": { "type": "object", "additionalProperties": false } @@ -1042,20 +1142,112 @@ } } }, - "owner": { + "ownership": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerResponse", - "description": "Returns the current owner of this issuer contract who is allowed to call priviledged methods.", + "title": "Ownership_for_Addr", + "description": "The contract's ownership info", "type": "object", - "required": [ - "address" - ], "properties": { - "address": { - "type": "string" + "owner": { + "description": "The contract's current owner. `None` if the ownership has been renounced.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "pending_expiry": { + "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pending_owner": { + "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, - "additionalProperties": false + "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" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } } } } diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index a90770aac..a4d6fee73 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -15,7 +15,7 @@ use crate::execute; use crate::hooks; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; use crate::queries; -use crate::state::{BeforeSendHookInfo, BEFORE_SEND_HOOK_INFO, DENOM, IS_FROZEN, OWNER}; +use crate::state::{BeforeSendHookInfo, BEFORE_SEND_HOOK_INFO, DENOM, IS_FROZEN}; // Version info for migration const CONTRACT_NAME: &str = "CARGO_PKG_NAME"; @@ -33,7 +33,7 @@ pub fn instantiate( set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; // Owner is the sender of the initial InstantiateMsg - OWNER.save(deps.storage, &info.sender)?; + cw_ownable::initialize_owner(deps.storage, deps.api, Some(info.sender.as_str()))?; // BeforeSendHook features are disabled by default. BEFORE_SEND_HOOK_INFO.save( @@ -102,8 +102,8 @@ pub fn execute( ExecuteMsg::UpdateTokenFactoryAdmin { new_admin } => { execute::update_tokenfactory_admin(deps, info, new_admin) } - ExecuteMsg::UpdateContractOwner { new_owner } => { - execute::update_contract_owner(deps, info, new_owner) + ExecuteMsg::UpdateOwnership(action) => { + execute::update_contract_owner(deps, env, info, action) } ExecuteMsg::SetMinterAllowance { address, allowance } => { execute::set_minter(deps, info, address, allowance) @@ -151,7 +151,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::IsAllowed { address } => to_binary(&queries::query_is_allowed(deps, address)?), QueryMsg::IsDenied { address } => to_binary(&queries::query_is_denied(deps, address)?), QueryMsg::IsFrozen {} => to_binary(&queries::query_is_frozen(deps)?), - QueryMsg::Owner {} => to_binary(&queries::query_owner(deps)?), + QueryMsg::Ownership {} => to_binary(&queries::query_owner(deps)?), QueryMsg::MintAllowance { address } => { to_binary(&queries::query_mint_allowance(deps, address)?) } diff --git a/contracts/external/cw-tokenfactory-issuer/src/error.rs b/contracts/external/cw-tokenfactory-issuer/src/error.rs index 92a6ce562..69dc28154 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/error.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/error.rs @@ -6,6 +6,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error(transparent)] + Ownership(#[from] cw_ownable::OwnershipError), + #[error("BeforeSendHook not set. Features requiring it are disabled.")] BeforeSendHookFeaturesDisabled {}, diff --git a/contracts/external/cw-tokenfactory-issuer/src/execute.rs b/contracts/external/cw-tokenfactory-issuer/src/execute.rs index e3b47b538..c8ff45f4a 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/execute.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/execute.rs @@ -6,10 +6,10 @@ use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ use token_bindings::TokenFactoryMsg; use crate::error::ContractError; -use crate::helpers::{check_before_send_hook_features_enabled, check_is_contract_owner}; +use crate::helpers::check_before_send_hook_features_enabled; use crate::state::{ BeforeSendHookInfo, ALLOWLIST, BEFORE_SEND_HOOK_INFO, BURNER_ALLOWANCES, DENOM, DENYLIST, - IS_FROZEN, MINTER_ALLOWANCES, OWNER, + IS_FROZEN, MINTER_ALLOWANCES, }; /// Mints new tokens. To mint new tokens, the address calling this method must @@ -130,21 +130,13 @@ pub fn burn( /// this method. pub fn update_contract_owner( deps: DepsMut, + env: Env, info: MessageInfo, - new_owner: String, + action: cw_ownable::Action, ) -> Result, ContractError> { - // Only allow current contract owner to change owner - check_is_contract_owner(deps.as_ref(), info.sender)?; - - // Validate that new owner is a valid address - let new_owner_addr = deps.api.addr_validate(&new_owner)?; - - // Update the contract owner in the contract config - OWNER.save(deps.storage, &new_owner_addr)?; - - Ok(Response::new() - .add_attribute("action", "update_contract_owner") - .add_attribute("new_owner", new_owner)) + // cw-ownable performs all validation and ownership checks for us + let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; + Ok(Response::default().add_attributes(ownership.into_attributes())) } /// Updates the Token Factory token admin. To set no admin, specify the `new_admin` @@ -158,7 +150,7 @@ pub fn update_tokenfactory_admin( new_admin: String, ) -> Result, ContractError> { // Only allow current contract owner to change tokenfactory admin - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; // Validate that the new admin is a valid address let new_admin_addr = deps.api.addr_validate(&new_admin)?; @@ -185,7 +177,7 @@ pub fn set_denom_metadata( metadata: Metadata, ) -> Result, ContractError> { // Only allow current contract owner to set denom metadata - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; Ok(Response::new() .add_attribute("action", "set_denom_metadata") @@ -211,7 +203,7 @@ pub fn set_before_send_hook( cosmwasm_address: String, ) -> Result, ContractError> { // Only allow current contract owner - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; // The `cosmwasm_address` can be an empty string if setting the value to nil to // disable the hook. If an empty string, we disable before send hook features. @@ -275,7 +267,7 @@ pub fn set_burner( allowance: Uint128, ) -> Result, ContractError> { // Only allow current contract owner to set burner allowance - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; // Validate that burner is a valid address let address = deps.api.addr_validate(&address)?; @@ -304,7 +296,7 @@ pub fn set_minter( allowance: Uint128, ) -> Result, ContractError> { // Only allow current contract owner to set minter allowance - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; // Validate that minter is a valid address let address = deps.api.addr_validate(&address)?; @@ -342,7 +334,7 @@ pub fn freeze( check_before_send_hook_features_enabled(deps.as_ref())?; // Only allow current contract owner to call this method - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; // Update config frozen status // NOTE: Does not check if new status is same as old status @@ -368,7 +360,7 @@ pub fn deny( check_before_send_hook_features_enabled(deps.as_ref())?; // Only allow current contract owner to call this method - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; let address = deps.api.addr_validate(&address)?; @@ -408,7 +400,7 @@ pub fn allow( check_before_send_hook_features_enabled(deps.as_ref())?; // Only allow current contract owner to call this method - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; let address = deps.api.addr_validate(&address)?; @@ -441,7 +433,7 @@ pub fn force_transfer( to_address: String, ) -> Result, ContractError> { // Only allow current contract owner to change owner - check_is_contract_owner(deps.as_ref(), info.sender)?; + cw_ownable::assert_owner(deps.storage, &info.sender)?; // Load TF denom for this contract let denom = DENOM.load(deps.storage)?; diff --git a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs index af2daee89..2328035a4 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/helpers.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/helpers.rs @@ -1,16 +1,6 @@ -use crate::state::{ALLOWLIST, BEFORE_SEND_HOOK_INFO, DENOM, DENYLIST, IS_FROZEN, OWNER}; +use crate::state::{ALLOWLIST, BEFORE_SEND_HOOK_INFO, DENOM, DENYLIST, IS_FROZEN}; use crate::ContractError; -use cosmwasm_std::{Addr, Deps}; - -/// Checks wether the sender is the owner of the contract -pub fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractError> { - let owner = OWNER.load(deps.storage)?; - if owner != sender { - Err(ContractError::Unauthorized {}) - } else { - Ok(()) - } -} +use cosmwasm_std::Deps; /// Checks wether the BeforeSendHookFeatures gated features are enabled pub fn check_before_send_hook_features_enabled(deps: Deps) -> Result<(), ContractError> { diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 102cb2293..3c874e584 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -92,7 +92,10 @@ pub enum ExecuteMsg { /// at all, it needs to the be the Token Factory token admin. /// /// Normally, the contract owner will be a DAO. - UpdateContractOwner { new_owner: String }, + /// + /// The `action` to be provided can be either to propose transferring ownership to an + /// account, accept a pending ownership transfer, or renounce the ownership permanently. + UpdateOwnership(cw_ownable::Action), } /// Used for smart contract migration. @@ -111,9 +114,8 @@ pub enum QueryMsg { #[returns(DenomResponse)] Denom {}, - /// Returns the owner of the contract. Response: OwnerResponse - #[returns(OwnerResponse)] - Owner {}, + #[returns(::cw_ownable::Ownership<::cosmwasm_std::Addr>)] + Ownership {}, /// Returns the burn allowance of the specified address. Response: AllowanceResponse #[returns(AllowanceResponse)] diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index eda2f46f5..a6b7f3b5d 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -3,11 +3,11 @@ use cw_storage_plus::{Bound, Map}; use crate::msg::{ AllowanceInfo, AllowanceResponse, AllowancesResponse, AllowlistResponse, DenomResponse, - DenylistResponse, IsFrozenResponse, OwnerResponse, StatusInfo, StatusResponse, + DenylistResponse, IsFrozenResponse, StatusInfo, StatusResponse, }; use crate::state::{ BeforeSendHookInfo, ALLOWLIST, BEFORE_SEND_HOOK_INFO, BURNER_ALLOWANCES, DENOM, DENYLIST, - IS_FROZEN, MINTER_ALLOWANCES, OWNER, + IS_FROZEN, MINTER_ALLOWANCES, }; // Default settings for pagination @@ -26,12 +26,9 @@ pub fn query_is_frozen(deps: Deps) -> StdResult { Ok(IsFrozenResponse { is_frozen }) } -/// Returns the owner of the contract. Response: OwnerResponse -pub fn query_owner(deps: Deps) -> StdResult { - let owner = OWNER.load(deps.storage)?; - Ok(OwnerResponse { - address: owner.into_string(), - }) +/// Returns the owner of the contract. Response: Ownership +pub fn query_owner(deps: Deps) -> StdResult> { + cw_ownable::get_ownership(deps.storage) } /// Returns the mint allowance of the specified user. Response: AllowanceResponse diff --git a/contracts/external/cw-tokenfactory-issuer/src/state.rs b/contracts/external/cw-tokenfactory-issuer/src/state.rs index 44abd9072..3c694c8c4 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/state.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/state.rs @@ -2,9 +2,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; -/// Holds the owner of this contract -pub const OWNER: Item = Item::new("owner"); - /// Holds the Token Factory denom managed by this contract pub const DENOM: Item = Item::new("denom"); diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs index f5f6143cb..9d1da91d8 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/allowlist.rs @@ -61,7 +61,9 @@ fn allowlist_by_non_owern_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs index bd6dfc6df..6c63e1e38 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/burn.rs @@ -42,7 +42,9 @@ fn set_burner_performed_by_non_contract_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs index 5c276be98..4066e0f57 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs @@ -1,3 +1,4 @@ +use cosmwasm_std::Addr; use cw_tokenfactory_issuer::ContractError; use osmosis_test_tube::Account; @@ -10,25 +11,27 @@ fn change_owner_by_owner_should_work() { let new_owner = &env.test_accs[1]; assert_eq!( - prev_owner.address(), - env.cw_tokenfactory_issuer.query_owner().unwrap().address + Some(Addr::unchecked(prev_owner.address())), + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, ); env.cw_tokenfactory_issuer - .update_contract_owner(&new_owner.address(), prev_owner) + .update_contract_owner(new_owner, prev_owner) .unwrap(); assert_eq!( - new_owner.address(), - env.cw_tokenfactory_issuer.query_owner().unwrap().address + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, + Some(Addr::unchecked(new_owner.address())), ); // Previous owner should not be able to execute owner action assert_eq!( env.cw_tokenfactory_issuer - .update_contract_owner(&prev_owner.address(), prev_owner) + .update_contract_owner(prev_owner, prev_owner) .unwrap_err(), - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } @@ -39,11 +42,13 @@ fn change_owner_by_non_owner_should_fail() { let err = env .cw_tokenfactory_issuer - .update_contract_owner(&new_owner.address(), new_owner) + .update_contract_owner(new_owner, new_owner) .unwrap_err(); assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs index 35968133f..cfcced250 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denom_metadata.rs @@ -72,7 +72,9 @@ fn set_denom_metadata_by_contract_non_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ) } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs index be83bb21d..b15de2f22 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/denylist.rs @@ -42,7 +42,7 @@ fn denylist_by_owner_should_pass() { } #[test] -fn denylist_by_non_denylister_should_fail() { +fn denylist_by_non_owner_should_fail() { let env = TestEnv::default(); let owner = &env.test_accs[0]; let non_owner = &env.test_accs[1]; @@ -61,7 +61,9 @@ fn denylist_by_non_denylister_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs index db8c4c5a4..a02bafce9 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/force_transfer.rs @@ -37,7 +37,9 @@ fn test_force_transfer() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); // Owner can force transfer tokens diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs index 35241f8d3..8a9963c73 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/freeze.rs @@ -52,6 +52,8 @@ fn freeze_by_non_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs index c5cff0094..32d176f1e 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/instantiate.rs @@ -1,3 +1,4 @@ +use cosmwasm_std::Addr; use cw_tokenfactory_issuer::{ msg::{InstantiateMsg, QueryMsg}, state::BeforeSendHookInfo, @@ -53,10 +54,10 @@ fn instantiate_with_new_token_should_set_initial_state_correctly() { .unwrap(); assert!(!info.advanced_features_enabled); - let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().address; + let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().owner; assert_eq!( owner_addr, - owner.address(), + Some(Addr::unchecked(owner.address())), "owner must be contract instantiate tx signer" ); } @@ -96,10 +97,10 @@ fn instantiate_with_existing_token_should_set_initial_state_correctly() { .is_frozen; assert!(!is_frozen, "newly instantiated contract must not be frozen"); - let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().address; + let owner_addr = env.cw_tokenfactory_issuer.query_owner().unwrap().owner; assert_eq!( owner_addr, - owner.address(), + Some(Addr::unchecked(owner.address())), "owner must be contract instantiate tx signer" ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs index 33823eeca..4b68bce12 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/mint.rs @@ -40,7 +40,9 @@ fn set_minter_performed_by_non_contract_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs index a3aa982bf..74810986d 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs @@ -19,7 +19,9 @@ fn test_set_before_send_hook() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ); // Owner can set before update hook, but hook is already set diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs index 005cdf7a1..7edb5efc6 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/tokenfactory_admin.rs @@ -30,6 +30,8 @@ fn transfer_token_factory_admin_by_non_contract_owner_should_fail() { assert_eq!( err, - TokenfactoryIssuer::execute_error(ContractError::Unauthorized {}) + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NotOwner + )) ) } diff --git a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs index 3e3691f1f..f6f4b3d1e 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/test_env.rs @@ -2,13 +2,12 @@ // see https://github.com/rust-lang/rust/issues/46379 #![allow(dead_code)] -use cosmwasm_std::{Coin, Uint128}; - +use cosmwasm_std::{Addr, Coin, Uint128}; use cw_tokenfactory_issuer::msg::{AllowlistResponse, DenylistResponse, Metadata, MigrateMsg}; use cw_tokenfactory_issuer::{ msg::{ AllowanceResponse, AllowancesResponse, DenomResponse, ExecuteMsg, InstantiateMsg, - IsFrozenResponse, OwnerResponse, QueryMsg, StatusResponse, + IsFrozenResponse, QueryMsg, StatusResponse, }, ContractError, }; @@ -171,15 +170,21 @@ impl TokenfactoryIssuer { pub fn update_contract_owner( &self, - new_owner: &str, + new_owner: &SigningAccount, signer: &SigningAccount, ) -> RunnerExecuteResult { self.execute( - &ExecuteMsg::UpdateContractOwner { - new_owner: new_owner.to_string(), - }, + &ExecuteMsg::UpdateOwnership(cw_ownable::Action::TransferOwnership { + new_owner: new_owner.address(), + expiry: None, + }), &[], signer, + )?; + self.execute( + &ExecuteMsg::UpdateOwnership(cw_ownable::Action::AcceptOwnership {}), + &[], + new_owner, ) } pub fn update_tokenfactory_admin( @@ -366,8 +371,8 @@ impl TokenfactoryIssuer { }) } - pub fn query_owner(&self) -> Result { - self.query(&QueryMsg::Owner {}) + pub fn query_owner(&self) -> Result, RunnerError> { + self.query(&QueryMsg::Ownership {}) } pub fn query_mint_allowance(&self, address: &str) -> Result { diff --git a/contracts/voting/dao-voting-cw721-staked/README.md b/contracts/voting/dao-voting-cw721-staked/README.md index 0e67eba17..ac91953ae 100644 --- a/contracts/voting/dao-voting-cw721-staked/README.md +++ b/contracts/voting/dao-voting-cw721-staked/README.md @@ -7,4 +7,4 @@ This is a basic implementation of an NFT staking contract. Staked tokens can be unbonded with a configurable unbonding period. Staked balances can be queried at any arbitrary height by external contracts. This contract implements the interface needed to be a DAO DAO [voting module](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design#the-voting-module). -`dao-voting-cw721-staked` can be used with existing NFT collections or create new `cw721` or `sg721` collections upon instantiation (with the DAO as admin and `minter`). +`dao-voting-cw721-staked` can be used with existing NFT collections or to create a new `cw721` or `sg721` collections upon instantiation (with the DAO as admin and `minter` / `creator`). diff --git a/contracts/voting/dao-voting-token-staked/Cargo.toml b/contracts/voting/dao-voting-token-staked/Cargo.toml index 553fc2db1..60d2b5c90 100644 --- a/contracts/voting/dao-voting-token-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-staked/Cargo.toml @@ -25,6 +25,7 @@ test-tube = [] cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } cosmwasm-schema = { workspace = true } cosmwasm-storage = { workspace = true } +cw-ownable = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } cw-utils = { workspace = true } @@ -43,6 +44,7 @@ anyhow = { workspace = true } # TODO use upstream when new release is tagged cw-multi-test = { git = "https://github.com/CosmWasm/cw-multi-test.git", rev = "d38db7752b9f054c395d6108453f8b321e4cab02" } cw-tokenfactory-issuer = { workspace = true } +dao-proposal-single = { workspace = true } dao-proposal-hook-counter = { workspace = true } dao-testing = { workspace = true, features = ["test-tube"] } osmosis-std = { workpsace = true } diff --git a/contracts/voting/dao-voting-token-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs index 31268f54c..91f065fb2 100644 --- a/contracts/voting/dao-voting-token-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -13,6 +13,7 @@ use cw_tokenfactory_issuer::msg::{ }; use cw_utils::{maybe_addr, must_pay, parse_reply_instantiate_data, Duration}; use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; +use dao_interface::state::ModuleInstantiateCallback; use dao_interface::voting::{ IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; @@ -661,19 +662,37 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result>(&cw_tokenfactory_issuer::msg::QueryMsg::Ownership {}) + .unwrap() + .owner; + assert_eq!( + issuer_admin, + Some(Addr::unchecked(dao.unwrap().contract_addr)) + ); +} + #[test] fn test_stake_unstake_new_denom() { let app = OsmosisTestApp::new(); @@ -20,7 +37,7 @@ fn test_stake_unstake_new_denom() { vp_contract, accounts, .. - } = env.default_setup(&app); + } = env.full_dao_setup(&app); let denom = vp_contract.query_denom().unwrap().denom; @@ -248,7 +265,7 @@ fn test_instantiate_invalid_active_threshold_count_fails() { assert_eq!( err, - TfDaoVotingContract::execute_submessage_error(ContractError::ActiveThresholdError( + TokenVotingContract::execute_submessage_error(ContractError::ActiveThresholdError( ActiveThresholdError::InvalidAbsoluteCount {} )) ); @@ -291,6 +308,6 @@ fn test_instantiate_no_initial_balances_fails() { .unwrap_err(); assert_eq!( err, - TfDaoVotingContract::execute_submessage_error(ContractError::InitialBalancesError {}) + TokenVotingContract::execute_submessage_error(ContractError::InitialBalancesError {}) ); } diff --git a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs index a65173cde..5287cec34 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs @@ -7,11 +7,22 @@ use crate::{ ContractError, }; -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{to_binary, Addr, Coin, Decimal, Uint128}; use cw_tokenfactory_issuer::msg::{DenomResponse, DenomUnit}; use cw_utils::Duration; -use dao_interface::voting::{IsActiveResponse, VotingPowerAtHeightResponse}; -use dao_testing::test_tube::cw_tokenfactory_issuer::TokenfactoryIssuer; +use dao_interface::{ + msg::QueryMsg as DaoQueryMsg, + state::{Admin, ModuleInstantiateInfo, ProposalModule}, + voting::{IsActiveResponse, VotingPowerAtHeightResponse}, +}; +use dao_voting::{ + pre_propose::PreProposeInfo, threshold::PercentageThreshold, threshold::Threshold, +}; + +use dao_testing::test_tube::{ + cw_tokenfactory_issuer::TokenfactoryIssuer, dao_dao_core::DaoCore, + dao_proposal_single::DaoProposalSingle, +}; use dao_voting::threshold::ActiveThreshold; use osmosis_std::types::{ cosmos::bank::v1beta1::QueryAllBalancesRequest, cosmwasm::wasm::v1::MsgExecuteContractResponse, @@ -28,7 +39,9 @@ pub const JUNO: &str = "ujuno"; pub struct TestEnv<'a> { pub app: &'a OsmosisTestApp, - pub vp_contract: TfDaoVotingContract<'a>, + pub dao: Option>, + pub proposal_single: Option>, + pub vp_contract: TokenVotingContract<'a>, pub tf_issuer: TokenfactoryIssuer<'a>, pub accounts: Vec, } @@ -38,8 +51,8 @@ impl<'a> TestEnv<'a> { &self, msg: &InstantiateMsg, signer: SigningAccount, - ) -> Result { - TfDaoVotingContract::<'a>::instantiate(self.app, self.vp_contract.code_id, msg, &signer) + ) -> Result { + TokenVotingContract::<'a>::instantiate(self.app, self.vp_contract.code_id, msg, &signer) } pub fn get_tf_issuer_code_id(&self) -> u64 { @@ -100,6 +113,7 @@ impl TestEnvBuilder { } } + // Minimal default setup with just the key contracts pub fn default_setup(self, app: &'_ OsmosisTestApp) -> TestEnv<'_> { let accounts = app .init_accounts(&[Coin::new(1000000000000000u128, "uosmo")], 10) @@ -115,7 +129,7 @@ impl TestEnvBuilder { let issuer_id = TokenfactoryIssuer::upload(app, &accounts[0]).unwrap(); - let vp_contract = TfDaoVotingContract::deploy( + let vp_contract = TokenVotingContract::deploy( app, &InstantiateMsg { token_info: TokenInfo::New(NewTokenInfo { @@ -145,49 +159,129 @@ impl TestEnvBuilder { .unwrap(); let issuer_addr = - TfDaoVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); + TokenVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); let tf_issuer = TokenfactoryIssuer::new_with_values(app, issuer_id, issuer_addr).unwrap(); TestEnv { app, - vp_contract, - tf_issuer, accounts, + dao: None, + proposal_single: None, + tf_issuer, + vp_contract, } } - pub fn build(self, app: &'_ OsmosisTestApp) -> TestEnv<'_> { - let accounts = self.accounts; + // Full DAO setup + pub fn full_dao_setup(self, app: &'_ OsmosisTestApp) -> TestEnv<'_> { + let accounts = app + .init_accounts(&[Coin::new(1000000000000000u128, "uosmo")], 10) + .unwrap(); + + let initial_balances: Vec = accounts + .iter() + .map(|acc| InitialBalance { + address: acc.address(), + amount: Uint128::new(100), + }) + .collect(); - let vp_contract = TfDaoVotingContract::deploy( + // Upload all needed code ids + let issuer_id = TokenfactoryIssuer::upload(app, &accounts[0]).unwrap(); + let vp_contract_id = TokenVotingContract::upload(app, &accounts[0]).unwrap(); + let proposal_single_id = DaoProposalSingle::upload(app, &accounts[0]).unwrap(); + + let msg = dao_interface::msg::InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that makes DAO tooling".to_string(), + image_url: None, + automatically_add_cw20s: false, + automatically_add_cw721s: false, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: vp_contract_id, + msg: to_binary(&InstantiateMsg { + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: issuer_id, + subdenom: DENOM.to_string(), + metadata: Some(crate::msg::NewDenomMetadata { + description: "Awesome token, get it meow!".to_string(), + additional_denom_units: Some(vec![DenomUnit { + denom: "cat".to_string(), + exponent: 6, + aliases: vec![], + }]), + display: "cat".to_string(), + name: "Cat Token".to_string(), + symbol: "CAT".to_string(), + }), + initial_balances, + initial_dao_balance: Some(Uint128::new(900)), + }), + unstaking_duration: Some(Duration::Time(2)), + active_threshold: Some(ActiveThreshold::AbsoluteCount { + count: Uint128::new(75), + }), + }) + .unwrap(), + admin: Some(Admin::CoreModule {}), + label: "DAO DAO Voting Module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: proposal_single_id, + msg: to_binary(&dao_proposal_single::msg::InstantiateMsg { + min_voting_period: None, + threshold: Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(35)), + }, + max_voting_period: Duration::Time(432000), + allow_revoting: false, + only_members_execute: true, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + }) + .unwrap(), + admin: Some(Admin::CoreModule {}), + label: "DAO DAO Proposal Module".to_string(), + }], + initial_items: None, + }; + + // Instantiate DAO + let dao = DaoCore::new(app, &msg, &accounts[0]).unwrap(); + + // Get voting module address, setup vp_contract helper + let vp_addr: Addr = dao.query(&DaoQueryMsg::VotingModule {}).unwrap(); + let vp_contract = + TokenVotingContract::new_with_values(app, vp_contract_id, vp_addr.to_string()).unwrap(); + + // Get proposal module address, setup proposal_single helper + let proposal_modules: Vec = dao + .query(&DaoQueryMsg::ProposalModules { + limit: None, + start_after: None, + }) + .unwrap(); + let proposal_single = DaoProposalSingle::new_with_values( app, - self.instantiate_msg - .as_ref() - .expect("instantiate msg not set"), - &accounts[0], + proposal_single_id, + proposal_modules[0].address.to_string(), ) .unwrap(); + // Get issuer address, setup tf_issuer helper let issuer_addr = - TfDaoVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); - - let issuer_id: u64; - if let TokenInfo::New(token) = self - .instantiate_msg - .expect("instantiate msg not set") - .token_info - { - issuer_id = token.token_issuer_code_id; - } else { - panic!("TokenInfo is not New"); - } - + TokenVotingContract::query(&vp_contract, &QueryMsg::TokenContract {}).unwrap(); let tf_issuer = TokenfactoryIssuer::new_with_values(app, issuer_id, issuer_addr).unwrap(); TestEnv { app, + dao: Some(dao), vp_contract, + proposal_single: Some(proposal_single), tf_issuer, accounts, } @@ -214,13 +308,13 @@ impl TestEnvBuilder { } #[derive(Debug)] -pub struct TfDaoVotingContract<'a> { +pub struct TokenVotingContract<'a> { pub app: &'a OsmosisTestApp, pub contract_addr: String, pub code_id: u64, } -impl<'a> TfDaoVotingContract<'a> { +impl<'a> TokenVotingContract<'a> { pub fn deploy( app: &'a OsmosisTestApp, instantiate_msg: &InstantiateMsg, @@ -252,6 +346,30 @@ impl<'a> TfDaoVotingContract<'a> { }) } + pub fn new_with_values( + app: &'a OsmosisTestApp, + code_id: u64, + contract_addr: String, + ) -> Result { + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + /// uploads contract and returns a code ID + pub fn upload(app: &OsmosisTestApp, signer: &SigningAccount) -> Result { + let wasm = Wasm::new(app); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + Ok(code_id) + } + pub fn instantiate( app: &'a OsmosisTestApp, code_id: u64, diff --git a/packages/dao-testing/src/test_tube/dao_dao_core.rs b/packages/dao-testing/src/test_tube/dao_dao_core.rs new file mode 100644 index 000000000..0b9f6752f --- /dev/null +++ b/packages/dao-testing/src/test_tube/dao_dao_core.rs @@ -0,0 +1,127 @@ +use cosmwasm_std::Coin; +use dao_dao_core::ContractError; +use dao_interface::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use osmosis_test_tube::{ + osmosis_std::types::cosmwasm::wasm::v1::MsgExecuteContractResponse, Account, Module, + OsmosisTestApp, RunnerError, RunnerExecuteResult, SigningAccount, Wasm, +}; +use serde::de::DeserializeOwned; +use std::fmt::Debug; +use std::path::PathBuf; + +#[derive(Debug)] +pub struct DaoCore<'a> { + pub app: &'a OsmosisTestApp, + pub code_id: u64, + pub contract_addr: String, +} + +impl<'a> DaoCore<'a> { + pub fn new( + app: &'a OsmosisTestApp, + instantiate_msg: &InstantiateMsg, + signer: &SigningAccount, + ) -> Result { + let wasm = Wasm::new(app); + let token_creation_fee = Coin::new(10000000, "uosmo"); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + let contract_addr = wasm + .instantiate( + code_id, + &instantiate_msg, + Some(&signer.address()), + None, + &[token_creation_fee], + signer, + )? + .data + .address; + + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + pub fn new_with_values( + app: &'a OsmosisTestApp, + code_id: u64, + contract_addr: String, + ) -> Result { + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + /// uploads contract and returns a code ID + pub fn upload(app: &OsmosisTestApp, signer: &SigningAccount) -> Result { + let wasm = Wasm::new(app); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + Ok(code_id) + } + + // executes + pub fn execute( + &self, + execute_msg: &ExecuteMsg, + funds: &[Coin], + signer: &SigningAccount, + ) -> RunnerExecuteResult { + let wasm = Wasm::new(self.app); + wasm.execute(&self.contract_addr, execute_msg, funds, signer) + } + + // queries + pub fn query(&self, query_msg: &QueryMsg) -> Result + where + T: DeserializeOwned, + { + let wasm = Wasm::new(self.app); + wasm.query(&self.contract_addr, query_msg) + } + + fn get_wasm_byte_code() -> Vec { + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let byte_code = std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_dao_core.wasm"), + ); + match byte_code { + Ok(byte_code) => byte_code, + // On arm processors, the above path is not found, so we try the following path + Err(_) => std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_dao_core-aarch64.wasm"), + ) + .unwrap(), + } + } + + pub fn execute_error(err: ContractError) -> RunnerError { + RunnerError::ExecuteError { + msg: format!( + "failed to execute message; message index: 0: {}: execute wasm contract failed", + err + ), + } + } +} diff --git a/packages/dao-testing/src/test_tube/dao_proposal_single.rs b/packages/dao-testing/src/test_tube/dao_proposal_single.rs new file mode 100644 index 000000000..4f6d51a96 --- /dev/null +++ b/packages/dao-testing/src/test_tube/dao_proposal_single.rs @@ -0,0 +1,129 @@ +use cosmwasm_std::Coin; +use dao_proposal_single::{ + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + ContractError, +}; +use osmosis_test_tube::{ + osmosis_std::types::cosmwasm::wasm::v1::MsgExecuteContractResponse, Account, Module, + OsmosisTestApp, RunnerError, RunnerExecuteResult, SigningAccount, Wasm, +}; +use serde::de::DeserializeOwned; +use std::fmt::Debug; +use std::path::PathBuf; + +#[derive(Debug)] +pub struct DaoProposalSingle<'a> { + pub app: &'a OsmosisTestApp, + pub code_id: u64, + pub contract_addr: String, +} + +impl<'a> DaoProposalSingle<'a> { + pub fn new( + app: &'a OsmosisTestApp, + instantiate_msg: &InstantiateMsg, + signer: &SigningAccount, + ) -> Result { + let wasm = Wasm::new(app); + let token_creation_fee = Coin::new(10000000, "uosmo"); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + let contract_addr = wasm + .instantiate( + code_id, + &instantiate_msg, + Some(&signer.address()), + None, + &[token_creation_fee], + signer, + )? + .data + .address; + + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + pub fn new_with_values( + app: &'a OsmosisTestApp, + code_id: u64, + contract_addr: String, + ) -> Result { + Ok(Self { + app, + code_id, + contract_addr, + }) + } + + /// uploads contract and returns a code ID + pub fn upload(app: &OsmosisTestApp, signer: &SigningAccount) -> Result { + let wasm = Wasm::new(app); + + let code_id = wasm + .store_code(&Self::get_wasm_byte_code(), None, signer)? + .data + .code_id; + + Ok(code_id) + } + + // executes + pub fn execute( + &self, + execute_msg: &ExecuteMsg, + funds: &[Coin], + signer: &SigningAccount, + ) -> RunnerExecuteResult { + let wasm = Wasm::new(self.app); + wasm.execute(&self.contract_addr, execute_msg, funds, signer) + } + + // queries + pub fn query(&self, query_msg: &QueryMsg) -> Result + where + T: DeserializeOwned, + { + let wasm = Wasm::new(self.app); + wasm.query(&self.contract_addr, query_msg) + } + + fn get_wasm_byte_code() -> Vec { + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let byte_code = std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_proposal_single.wasm"), + ); + match byte_code { + Ok(byte_code) => byte_code, + // On arm processors, the above path is not found, so we try the following path + Err(_) => std::fs::read( + manifest_path + .join("..") + .join("..") + .join("artifacts") + .join("dao_proposal_single-aarch64.wasm"), + ) + .unwrap(), + } + } + + pub fn execute_error(err: ContractError) -> RunnerError { + RunnerError::ExecuteError { + msg: format!( + "failed to execute message; message index: 0: {}: execute wasm contract failed", + err + ), + } + } +} diff --git a/packages/dao-testing/src/test_tube/mod.rs b/packages/dao-testing/src/test_tube/mod.rs index f87c833bc..a5f997792 100644 --- a/packages/dao-testing/src/test_tube/mod.rs +++ b/packages/dao-testing/src/test_tube/mod.rs @@ -7,3 +7,9 @@ // cargo test --features test-tube #[cfg(feature = "test-tube")] pub mod cw_tokenfactory_issuer; + +#[cfg(feature = "test-tube")] +pub mod dao_dao_core; + +#[cfg(feature = "test-tube")] +pub mod dao_proposal_single; From c232173f563f4f83d284175ecb457eded1bc2c81 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 18:12:44 -0700 Subject: [PATCH 55/59] Tests for renouncing ownership --- .../tests/cases/contract_owner.rs | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs index 4066e0f57..04cbc431b 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs @@ -1,5 +1,5 @@ -use cosmwasm_std::Addr; -use cw_tokenfactory_issuer::ContractError; +use cosmwasm_std::{Addr, Uint128}; +use cw_tokenfactory_issuer::{msg::ExecuteMsg, ContractError}; use osmosis_test_tube::Account; use crate::test_env::{TestEnv, TokenfactoryIssuer}; @@ -52,3 +52,69 @@ fn change_owner_by_non_owner_should_fail() { )) ); } + +#[test] +fn renounce_ownership() { + let env = TestEnv::default(); + let owner = &env.test_accs[0]; + let non_owner = &env.test_accs[1]; + let hook = &env.test_accs[1]; + + assert_eq!( + Some(Addr::unchecked(owner.address())), + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, + ); + + // Renounce ownership + env.cw_tokenfactory_issuer + .execute( + &ExecuteMsg::UpdateOwnership(cw_ownable::Action::RenounceOwnership), + &[], + owner, + ) + .unwrap(); + + assert_eq!( + env.cw_tokenfactory_issuer.query_owner().unwrap().owner, + None, + ); + + // Cannot perform actions that require ownership + assert_eq!( + env.cw_tokenfactory_issuer + .set_minter(&non_owner.address(), 10000, owner) + .unwrap_err(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) + ); + assert_eq!( + env.cw_tokenfactory_issuer + .set_burner(&non_owner.address(), 10000, owner) + .unwrap_err(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) + ); + assert_eq!( + env.cw_tokenfactory_issuer + .force_transfer( + non_owner, + Uint128::new(10000), + owner.address(), + non_owner.address(), + ) + .unwrap_err(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) + ); + assert_eq!( + env.cw_tokenfactory_issuer + .set_before_send_hook(hook.address(), owner) + .unwrap(), + TokenfactoryIssuer::execute_error(ContractError::Ownership( + cw_ownable::OwnershipError::NoOwner + )) + ); +} From be1ae921ca2dcc603564b54f2c1c2ad99fc54799 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 18:32:36 -0700 Subject: [PATCH 56/59] Fix package name. --- contracts/external/cw-tokenfactory-issuer/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/external/cw-tokenfactory-issuer/src/contract.rs b/contracts/external/cw-tokenfactory-issuer/src/contract.rs index a4d6fee73..8d550284e 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/contract.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/contract.rs @@ -18,7 +18,7 @@ use crate::queries; use crate::state::{BeforeSendHookInfo, BEFORE_SEND_HOOK_INFO, DENOM, IS_FROZEN}; // Version info for migration -const CONTRACT_NAME: &str = "CARGO_PKG_NAME"; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const CREATE_DENOM_REPLY_ID: u64 = 1; From 1c51285cc23edfaec24aff39c7ae64c7dd983cba Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 19:24:30 -0700 Subject: [PATCH 57/59] Fix integration tests --- .../cw-tokenfactory-issuer/tests/cases/contract_owner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs index 04cbc431b..f7cc5491f 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/contract_owner.rs @@ -112,7 +112,7 @@ fn renounce_ownership() { assert_eq!( env.cw_tokenfactory_issuer .set_before_send_hook(hook.address(), owner) - .unwrap(), + .unwrap_err(), TokenfactoryIssuer::execute_error(ContractError::Ownership( cw_ownable::OwnershipError::NoOwner )) From 26e24ddf486f8133e9723662d825a3c4be5a0690 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Mon, 11 Sep 2023 20:40:13 -0700 Subject: [PATCH 58/59] set_before_update_hook -> set_before_send_hook --- contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs | 2 +- .../{set_before_update_hook.rs => set_before_send_hook.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename contracts/external/cw-tokenfactory-issuer/tests/cases/{set_before_update_hook.rs => set_before_send_hook.rs} (100%) diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs index d8fc83720..056859504 100644 --- a/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs +++ b/contracts/external/cw-tokenfactory-issuer/tests/cases/mod.rs @@ -8,5 +8,5 @@ mod force_transfer; mod freeze; mod instantiate; mod mint; -mod set_before_update_hook; +mod set_before_send_hook; mod tokenfactory_admin; diff --git a/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs b/contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_send_hook.rs similarity index 100% rename from contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_update_hook.rs rename to contracts/external/cw-tokenfactory-issuer/tests/cases/set_before_send_hook.rs From 67e8b83c1281b4667e0a0fa1f546fece2465461c Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 12 Sep 2023 12:37:21 -0700 Subject: [PATCH 59/59] Address remaining TODOs --- .../dao-voting-token-staked/src/contract.rs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/contracts/voting/dao-voting-token-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs index 91f065fb2..b9d692348 100644 --- a/contracts/voting/dao-voting-token-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -28,7 +28,7 @@ use dao_voting::{ use crate::error::ContractError; use crate::msg::{ DenomResponse, ExecuteMsg, GetHooksResponse, InitialBalance, InstantiateMsg, - ListStakersResponse, MigrateMsg, QueryMsg, StakerBalanceResponse, TokenInfo, + ListStakersResponse, MigrateMsg, NewTokenInfo, QueryMsg, StakerBalanceResponse, TokenInfo, }; use crate::state::{ Config, ACTIVE_THRESHOLD, CLAIMS, CONFIG, DAO, DENOM, HOOKS, MAX_CLAIMS, STAKED_BALANCES, @@ -77,10 +77,6 @@ pub fn instantiate( ACTIVE_THRESHOLD.save(deps.storage, active_threshold)?; } - // TODO only needed for new tokens - // Save new token info for use in reply - TOKEN_INSTANTIATION_INFO.save(deps.storage, &msg.token_info)?; - match msg.token_info { TokenInfo::Existing { denom } => { // Validate active threshold absolute count if configured @@ -96,15 +92,24 @@ pub fn instantiate( .add_attribute("token", "existing_token") .add_attribute("denom", denom)) } - TokenInfo::New(token) => { + TokenInfo::New(ref token) => { + let NewTokenInfo { + subdenom, + token_issuer_code_id, + .. + } = token; + + // Save new token info for use in reply + TOKEN_INSTANTIATION_INFO.save(deps.storage, &msg.token_info)?; + // Tnstantiate cw-token-factory-issuer contract // DAO (sender) is set as contract admin let issuer_instantiate_msg = SubMsg::reply_on_success( WasmMsg::Instantiate { admin: Some(info.sender.to_string()), - code_id: token.token_issuer_code_id, + code_id: *token_issuer_code_id, msg: to_binary(&IssuerInstantiateMsg::NewToken { - subdenom: token.subdenom, + subdenom: subdenom.to_string(), })?, funds: info.funds, label: "cw-tokenfactory-issuer".to_string(), @@ -542,13 +547,6 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { - // TODO this should never be called? Throw error? Unreachable? - Ok( - Response::new() - .add_attribute("cw-tokenfactory-issuer-address", issuer_addr), - ) - } TokenInfo::New(token) => { // Load the DAO address let dao = DAO.load(deps.storage)?; @@ -694,6 +692,7 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result unreachable!(), } } _ => Err(ContractError::UnknownReplyId { id: msg.id }),