From 19669acda44450bedc4e919d52f1e50adf312277 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Wed, 30 Oct 2024 21:56:06 -0400 Subject: [PATCH 1/6] make cw721 claims paginatable with backwards compatibility --- Cargo.lock | 20 +- Cargo.toml | 3 + .../src/tests/dao_voting_cw721_staked_test.rs | 82 +---- .../voting/dao-voting-cw721-roles/Cargo.toml | 1 - .../voting/dao-voting-cw721-staked/Cargo.toml | 1 + .../schema/dao-voting-cw721-staked.json | 27 +- .../dao-voting-cw721-staked/src/contract.rs | 94 ++++-- .../dao-voting-cw721-staked/src/error.rs | 6 +- .../voting/dao-voting-cw721-staked/src/msg.rs | 13 +- .../dao-voting-cw721-staked/src/state.rs | 11 +- .../src/testing/adversarial.rs | 42 +-- .../src/testing/execute.rs | 4 +- .../src/testing/queries.rs | 2 + .../src/testing/tests.rs | 23 -- .../voting/dao-voting-onft-staked/Cargo.toml | 1 + .../schema/dao-voting-onft-staked.json | 27 +- .../dao-voting-onft-staked/src/contract.rs | 94 ++++-- .../dao-voting-onft-staked/src/error.rs | 6 +- .../voting/dao-voting-onft-staked/src/msg.rs | 13 +- .../dao-voting-onft-staked/src/state.rs | 11 +- .../src/testing/execute.rs | 4 +- .../src/testing/queries.rs | 2 + .../src/testing/tests.rs | 53 --- packages/cw721-controllers/src/lib.rs | 2 +- packages/cw721-controllers/src/nft_claim.rs | 312 +++++++++++++----- 25 files changed, 512 insertions(+), 342 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9304021ff..28d3876c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1621,6 +1621,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw721-controllers" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e48a8280ab1863169ce8caa395c72e7cbb0a4638323ddafbd3777da002fa89" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "thiserror", +] + [[package]] name = "cw721-controllers" version = "2.5.1" @@ -2499,7 +2512,6 @@ dependencies = [ "cw4 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "cw721-controllers", "cw721-roles", "dao-cw721-extensions", "dao-dao-macros 2.5.1", @@ -2523,7 +2535,8 @@ dependencies = [ "cw2 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "cw721-controllers", + "cw721-controllers 2.5.0", + "cw721-controllers 2.5.1", "dao-dao-macros 2.5.1", "dao-hooks 2.5.1", "dao-interface 2.5.1", @@ -2551,7 +2564,8 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw721-controllers", + "cw721-controllers 2.5.0", + "cw721-controllers 2.5.1", "dao-dao-macros 2.5.1", "dao-hooks 2.5.1", "dao-interface 2.5.1", diff --git a/Cargo.toml b/Cargo.toml index 8ccbe6e0e..63c019b5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,3 +159,6 @@ dao-proposal-multiple-v241 = { package = "dao-proposal-multiple", version = "=2. dao-proposal-single-v241 = { package = "dao-proposal-single", version = "=2.4.1" } dao-voting-cw4-v241 = { package = "dao-voting-cw4", version = "=2.4.1" } dao-voting-v241 = { package = "dao-voting", version = "=2.4.1" } + +# v2.5.0 dependencies. +cw721-controllers-v250 = { package = "cw721-controllers", version = "=2.5.0" } 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 dd13c47fb..c49cca134 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,4 +1,4 @@ -use cosm_orc::orchestrator::{ExecReq, SigningKey}; +use cosm_orc::orchestrator::SigningKey; use cosmwasm_std::{Binary, Empty, Uint128}; use cw_utils::Duration; use test_context::test_context; @@ -126,7 +126,9 @@ pub fn claim_nfts(chain: &mut Chain, sender: &SigningKey) { .execute( CONTRACT_NAME, "claim_nfts", - &module::msg::ExecuteMsg::ClaimNfts {}, + &module::msg::ExecuteMsg::ClaimNfts { + token_ids: Some(vec![]), + }, sender, vec![], ) @@ -189,79 +191,3 @@ fn cw721_stake_tokens(chain: &mut Chain) { let voting_power = query_voting_power(chain, &user_addr, None); assert_eq!(voting_power, Uint128::zero()); } - -#[test_context(Chain)] -#[test] -#[ignore] -fn cw721_stake_max_claims_works(chain: &mut Chain) { - use module::state::MAX_CLAIMS; - - let user_addr = chain.users["user1"].account.address.clone(); - let user_key = chain.users["user1"].key.clone(); - - let CommonTest { module, .. } = - setup_test(chain, Some(Duration::Height(1)), &user_key, &user_addr); - - // Create `MAX_CLAIMS` claims. - - // batch_size * 3 = the number of msgs to be batched per tx. - // We cant batch all of the msgs under a single tx because we hit MAX_BLOCK_GAS limits. - let batch_size = 10; - let mut total_msgs = 0; - - let mut reqs = vec![]; - for i in 0..MAX_CLAIMS { - let token_id = i.to_string(); - - reqs.push(ExecReq { - contract_name: CW721_NAME.to_string(), - msg: Box::new(cw721_base::ExecuteMsg::Mint:: { - token_id: token_id.clone(), - owner: user_addr.to_string(), - token_uri: None, - extension: Empty::default(), - }), - funds: vec![], - }); - - reqs.push(ExecReq { - contract_name: CW721_NAME.to_string(), - msg: Box::new(cw721::Cw721ExecuteMsg::SendNft { - contract: module.to_string(), - token_id: token_id.clone(), - msg: Binary::default(), - }), - funds: vec![], - }); - - reqs.push(ExecReq { - contract_name: CONTRACT_NAME.to_string(), - msg: Box::new(module::msg::ExecuteMsg::Unstake { - token_ids: vec![token_id], - }), - funds: vec![], - }); - - if (i != 0 && i % batch_size == 0) || i == MAX_CLAIMS - 1 { - total_msgs += reqs.len(); - - chain - .orc - .execute_batch("batch_cw721_stake_max_claims", reqs, &user_key) - .unwrap(); - - reqs = vec![]; - } - } - - assert_eq!(total_msgs as u64, MAX_CLAIMS * 3); - - chain - .orc - .poll_for_n_blocks(1, core::time::Duration::from_millis(20_000), false) - .unwrap(); - - // If this works, we're golden. Other tests make sure that the - // NFTs get returned as a result of this. - claim_nfts(chain, &user_key); -} diff --git a/contracts/voting/dao-voting-cw721-roles/Cargo.toml b/contracts/voting/dao-voting-cw721-roles/Cargo.toml index e816d763c..7fbf4360a 100644 --- a/contracts/voting/dao-voting-cw721-roles/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-roles/Cargo.toml @@ -22,7 +22,6 @@ dao-cw721-extensions = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } cw721-base = { workspace = true, features = ["library"] } -cw721-controllers = { workspace = true } cw-ownable = { workspace = true } cw721 = { workspace = true } cw-utils = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-staked/Cargo.toml b/contracts/voting/dao-voting-cw721-staked/Cargo.toml index af7d5532e..f63e4dc12 100644 --- a/contracts/voting/dao-voting-cw721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-staked/Cargo.toml @@ -31,6 +31,7 @@ cw-hooks = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } cw721-controllers = { workspace = true } +cw721-controllers-v250 = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } dao-dao-macros = { workspace = true } 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 346476689..a6f5473c8 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 @@ -265,7 +265,7 @@ "additionalProperties": false }, { - "description": "Claim NFTs that have been unstaked for the specified duration.", + "description": "Claim NFTs that have been unstaked for the specified duration. If none are provided, it attempts to claim all legacy claims. If token IDs are provided, only those are claimed. If an empty vector is provided, it attempts to claim all non-legacy claims.", "type": "object", "required": [ "claim_nfts" @@ -273,6 +273,17 @@ "properties": { "claim_nfts": { "type": "object", + "properties": { + "token_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, "additionalProperties": false } }, @@ -525,6 +536,20 @@ "properties": { "address": { "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index fc83cce13..ff8444cbe 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -20,7 +20,8 @@ use dao_voting::threshold::{ 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, + INITIAL_NFTS, LEGACY_NFT_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, + TOTAL_STAKED_NFTS, }; use crate::ContractError; @@ -204,7 +205,7 @@ pub fn execute( match msg { 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::ClaimNfts { token_ids } => execute_claim_nfts(deps, env, info, token_ids), 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), @@ -314,13 +315,6 @@ pub fn execute_unstake( } Some(duration) => { - let outstanding_claims = NFT_CLAIMS - .query_claims(deps.as_ref(), &info.sender)? - .nft_claims; - if outstanding_claims.len() + token_ids.len() > MAX_CLAIMS as usize { - return Err(ContractError::TooManyClaims {}); - } - // Out of gas here is fine - just try again with fewer // tokens. NFT_CLAIMS.create_nft_claims( @@ -343,22 +337,57 @@ pub fn execute_claim_nfts( deps: DepsMut, env: Env, info: MessageInfo, + token_ids: Option>, ) -> Result { - let nfts = NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; - if nfts.is_empty() { - return Err(ContractError::NothingToClaim {}); - } + let token_ids = match token_ids { + // attempt to claim all legacy NFTs + None => { + // claim all legacy NFTs + let legacy_nfts = + LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; + + if legacy_nfts.is_empty() { + return Err(ContractError::NothingToClaim {}); + } + + legacy_nfts + } + // attempt to claim non-legacy NFTs + Some(token_ids) => { + let token_ids = if token_ids.is_empty() { + // query all NFT claims if none are provided + NFT_CLAIMS + .query_claims(deps.as_ref(), &info.sender, None, None)? + .nft_claims + .into_iter() + .filter(|nft| nft.release_at.is_expired(&env.block)) + .map(|nft| nft.token_id) + .collect::>() + } else { + // use provided NFTs if any + token_ids + }; + + if token_ids.is_empty() { + return Err(ContractError::NothingToClaim {}); + } + + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; + + token_ids + } + }; let config = CONFIG.load(deps.storage)?; - let msgs = nfts + let msgs = token_ids .into_iter() - .map(|nft| -> StdResult { + .map(|token_id| -> StdResult { Ok(WasmMsg::Execute { contract_addr: config.nft_address.to_string(), msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { recipient: info.sender.to_string(), - token_id: nft, + token_id, })?, funds: vec![], } @@ -485,7 +514,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Dao {} => query_dao(deps), QueryMsg::Info {} => query_info(deps), QueryMsg::IsActive {} => query_is_active(deps, env), - QueryMsg::NftClaims { address } => query_nft_claims(deps, address), + QueryMsg::NftClaims { + address, + start_after, + limit, + } => query_nft_claims(deps, address, start_after, limit), QueryMsg::Hooks {} => query_hooks(deps), QueryMsg::StakedNfts { address, @@ -606,8 +639,31 @@ pub fn query_dao(deps: Deps) -> StdResult { to_json_binary(&dao) } -pub fn query_nft_claims(deps: Deps, address: String) -> StdResult { - to_json_binary(&NFT_CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?)?) +pub fn query_nft_claims( + deps: Deps, + address: String, + start_after: Option, + limit: Option, +) -> StdResult { + let addr = deps.api.addr_validate(&address)?; + + // load all legacy claims since it does not support pagination + let legacy_claims = LEGACY_NFT_CLAIMS + .query_claims(deps, &addr)? + .nft_claims + .into_iter() + .map(|c| cw721_controllers::NftClaim::new(c.token_id, c.release_at)) + .collect::>(); + + // paginate all new claims + let claims = NFT_CLAIMS + .query_claims(deps, &addr, start_after.as_ref(), limit)? + .nft_claims; + + // combine legacy and new claims + let nft_claims = legacy_claims.into_iter().chain(claims).collect(); + + to_json_binary(&cw721_controllers::NftClaimsResponse { nft_claims }) } pub fn query_hooks(deps: Deps) -> StdResult { diff --git a/contracts/voting/dao-voting-cw721-staked/src/error.rs b/contracts/voting/dao-voting-cw721-staked/src/error.rs index df9526bd2..97f41436b 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/error.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/error.rs @@ -20,6 +20,9 @@ pub enum ContractError { #[error(transparent)] UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + #[error(transparent)] + NftClaimError(#[from] cw721_controllers::NftClaimError), + #[error("Can not stake that which has already been staked")] AlreadyStaked {}, @@ -44,9 +47,6 @@ pub enum ContractError { #[error("Can not unstake that which you have not staked (unstaking {token_id})")] NotStaked { token_id: String }, - #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] - TooManyClaims {}, - #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 837851ed3..8643d6ddc 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -54,8 +54,11 @@ pub enum ExecuteMsg { /// sender. token_ids must have unique values and have non-zero /// length. Unstake { token_ids: Vec }, - /// Claim NFTs that have been unstaked for the specified duration. - ClaimNfts {}, + /// Claim NFTs that have been unstaked for the specified duration. If none + /// are provided, it attempts to claim all legacy claims. If token IDs are + /// provided, only those are claimed. If an empty vector is provided, it + /// attempts to claim all non-legacy claims. + ClaimNfts { token_ids: Option> }, /// Updates the contract configuration, namely unstaking duration. /// Only callable by the DAO that initialized this voting contract. UpdateConfig { duration: Option }, @@ -80,7 +83,11 @@ pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, #[returns(::cw721_controllers::NftClaimsResponse)] - NftClaims { address: String }, + NftClaims { + address: String, + start_after: Option, + limit: Option, + }, #[returns(::cw_controllers::HooksResponse)] Hooks {}, // List the staked NFTs for a given address. diff --git a/contracts/voting/dao-voting-cw721-staked/src/state.rs b/contracts/voting/dao-voting-cw721-staked/src/state.rs index 0d8e8b62f..99341499a 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/state.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/state.rs @@ -42,9 +42,14 @@ pub const TOTAL_STAKED_NFTS: SnapshotItem = SnapshotItem::new( Strategy::EveryBlock, ); -/// The maximum number of claims that may be outstanding. -pub const MAX_CLAIMS: u64 = 70; -pub const NFT_CLAIMS: NftClaims = NftClaims::new("nft_claims"); +/// The legacy NFT claims storage uses a non-paginatable vector, which limits +/// the number of claims that may be outstanding. This is horrible UX, +/// especially for large NFT collections. To allow DAOs to upgrade, we must keep +/// the legacy NFT claims storage, but we can paginate the new storage. +pub const LEGACY_NFT_CLAIMS: cw721_controllers_v250::NftClaims = + cw721_controllers_v250::NftClaims::new("nft_claims"); + +pub const NFT_CLAIMS: NftClaims = NftClaims::new("nc"); // Hooks to contracts that will receive staking and unstaking // messages. 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 e5c587564..bc81a2119 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs @@ -1,14 +1,10 @@ use cosmwasm_std::Uint128; use cw_multi_test::next_block; -use cw_utils::Duration; - -use crate::{ - state::MAX_CLAIMS, - testing::{ - execute::{stake_nft, unstake_nfts}, - instantiate::instantiate_cw721_base, - queries::query_voting_power, - }, + +use crate::testing::{ + execute::{stake_nft, unstake_nfts}, + instantiate::instantiate_cw721_base, + queries::query_voting_power, }; use super::{ @@ -145,31 +141,3 @@ fn test_query_the_future() -> anyhow::Result<()> { Ok(()) } - -/// I can not unstake more than one NFT in a TX in order to bypass the -/// MAX_CLAIMS limit. -#[test] -fn test_bypass_max_claims() -> anyhow::Result<()> { - let CommonTest { - mut app, - module, - nft, - } = setup_test(Some(Duration::Height(1))); - let mut to_stake = vec![]; - for i in 1..(MAX_CLAIMS + 10) { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, i_str)?; - if i < MAX_CLAIMS { - // unstake MAX_CLAMS - 1 NFTs - unstake_nfts(&mut app, &module, CREATOR_ADDR, &[i_str])?; - } else { - // push rest of NFT ids to vec - to_stake.push(i_str.clone()); - } - } - let binding = to_stake.iter().map(|s| s.as_str()).collect::>(); - let to_stake_slice: &[&str] = binding.as_slice(); - let res = unstake_nfts(&mut app, &module, CREATOR_ADDR, to_stake_slice); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); - Ok(()) -} 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 dd5b60877..08a1b397a 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs @@ -110,7 +110,9 @@ pub fn claim_nfts(app: &mut App, module: &Addr, sender: &str) -> AnyResult StdResult anyhow::Result<()> { Ok(()) } -// I can not have more than MAX_CLAIMS claims pending. -#[test] -fn test_max_claims() -> anyhow::Result<()> { - let CommonTest { - mut app, - module, - nft, - } = setup_test(Some(Duration::Height(1))); - - for i in 0..MAX_CLAIMS { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, i_str)?; - unstake_nfts(&mut app, &module, CREATOR_ADDR, &[i_str])?; - } - - mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "a")?; - let res = unstake_nfts(&mut app, &module, CREATOR_ADDR, &["a"]); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); - - Ok(()) -} - // I can list all of the currently staked NFTs for an address. #[test] fn test_list_staked_nfts() -> anyhow::Result<()> { diff --git a/contracts/voting/dao-voting-onft-staked/Cargo.toml b/contracts/voting/dao-voting-onft-staked/Cargo.toml index 9c96ec2d7..eaf1f53fa 100644 --- a/contracts/voting/dao-voting-onft-staked/Cargo.toml +++ b/contracts/voting/dao-voting-onft-staked/Cargo.toml @@ -33,6 +33,7 @@ cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } cw-hooks = { workspace = true } cw721-controllers = { workspace = true } +cw721-controllers-v250 = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } dao-dao-macros = { workspace = true } diff --git a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json index ede91d3be..76958af84 100644 --- a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json +++ b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json @@ -274,7 +274,7 @@ "additionalProperties": false }, { - "description": "Claim NFTs that have been unstaked for the specified duration.", + "description": "Claim NFTs that have been unstaked for the specified duration. If none are provided, it attempts to claim all legacy claims. If token IDs are provided, only those are claimed. If an empty vector is provided, it attempts to claim all non-legacy claims.", "type": "object", "required": [ "claim_nfts" @@ -282,6 +282,17 @@ "properties": { "claim_nfts": { "type": "object", + "properties": { + "token_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, "additionalProperties": false } }, @@ -509,6 +520,20 @@ "properties": { "address": { "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/contracts/voting/dao-voting-onft-staked/src/contract.rs b/contracts/voting/dao-voting-onft-staked/src/contract.rs index 2ab1ebd73..01efa76dd 100644 --- a/contracts/voting/dao-voting-onft-staked/src/contract.rs +++ b/contracts/voting/dao-voting-onft-staked/src/contract.rs @@ -19,7 +19,8 @@ use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, OnftCollection, QueryMs use crate::omniflix::{get_onft_transfer_msg, query_onft_owner, query_onft_supply}; use crate::state::{ register_staked_nfts, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, - MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, PREPARED_ONFTS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, + LEGACY_NFT_CLAIMS, NFT_BALANCES, NFT_CLAIMS, PREPARED_ONFTS, STAKED_NFTS_PER_OWNER, + TOTAL_STAKED_NFTS, }; use crate::ContractError; @@ -97,7 +98,7 @@ pub fn execute( recipient, } => execute_cancel_stake(deps, env, info, token_ids, recipient), ExecuteMsg::Unstake { token_ids } => execute_unstake(deps, env, info, token_ids), - ExecuteMsg::ClaimNfts {} => execute_claim_nfts(deps, env, info), + ExecuteMsg::ClaimNfts { token_ids } => execute_claim_nfts(deps, env, info, token_ids), 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), @@ -371,13 +372,6 @@ pub fn execute_unstake( } Some(duration) => { - let outstanding_claims = NFT_CLAIMS - .query_claims(deps.as_ref(), &info.sender)? - .nft_claims; - if outstanding_claims.len() + token_ids.len() > MAX_CLAIMS as usize { - return Err(ContractError::TooManyClaims {}); - } - // Out of gas here is fine - just try again with fewer // tokens. NFT_CLAIMS.create_nft_claims( @@ -400,20 +394,55 @@ pub fn execute_claim_nfts( deps: DepsMut, env: Env, info: MessageInfo, + token_ids: Option>, ) -> Result { - let nfts = NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; - if nfts.is_empty() { - return Err(ContractError::NothingToClaim {}); - } + let token_ids = match token_ids { + // attempt to claim all legacy NFTs + None => { + // claim all legacy NFTs + let legacy_nfts = + LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; + + if legacy_nfts.is_empty() { + return Err(ContractError::NothingToClaim {}); + } + + legacy_nfts + } + // attempt to claim non-legacy NFTs + Some(token_ids) => { + let token_ids = if token_ids.is_empty() { + // query all NFT claims if none are provided + NFT_CLAIMS + .query_claims(deps.as_ref(), &info.sender, None, None)? + .nft_claims + .into_iter() + .filter(|nft| nft.release_at.is_expired(&env.block)) + .map(|nft| nft.token_id) + .collect::>() + } else { + // use provided NFTs if any + token_ids + }; + + if token_ids.is_empty() { + return Err(ContractError::NothingToClaim {}); + } + + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; + + token_ids + } + }; let config = CONFIG.load(deps.storage)?; - let msgs = nfts + let msgs = token_ids .into_iter() - .map(|nft| -> CosmosMsg { + .map(|token_id| -> CosmosMsg { get_onft_transfer_msg( &config.onft_collection_id, - &nft, + &token_id, env.contract.address.as_str(), info.sender.as_str(), ) @@ -534,7 +563,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Dao {} => query_dao(deps), QueryMsg::Info {} => query_info(deps), QueryMsg::IsActive {} => query_is_active(deps, env), - QueryMsg::NftClaims { address } => query_nft_claims(deps, address), + QueryMsg::NftClaims { + address, + start_after, + limit, + } => query_nft_claims(deps, address, start_after, limit), QueryMsg::Hooks {} => query_hooks(deps), QueryMsg::StakedNfts { address, @@ -652,8 +685,31 @@ pub fn query_dao(deps: Deps) -> StdResult { to_json_binary(&dao) } -pub fn query_nft_claims(deps: Deps, address: String) -> StdResult { - to_json_binary(&NFT_CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?)?) +pub fn query_nft_claims( + deps: Deps, + address: String, + start_after: Option, + limit: Option, +) -> StdResult { + let addr = deps.api.addr_validate(&address)?; + + // load all legacy claims since it does not support pagination + let legacy_claims = LEGACY_NFT_CLAIMS + .query_claims(deps, &addr)? + .nft_claims + .into_iter() + .map(|c| cw721_controllers::NftClaim::new(c.token_id, c.release_at)) + .collect::>(); + + // paginate all new claims + let claims = NFT_CLAIMS + .query_claims(deps, &addr, start_after.as_ref(), limit)? + .nft_claims; + + // combine legacy and new claims + let nft_claims = legacy_claims.into_iter().chain(claims).collect(); + + to_json_binary(&cw721_controllers::NftClaimsResponse { nft_claims }) } pub fn query_hooks(deps: Deps) -> StdResult { diff --git a/contracts/voting/dao-voting-onft-staked/src/error.rs b/contracts/voting/dao-voting-onft-staked/src/error.rs index 252e8caf4..a57d785b5 100644 --- a/contracts/voting/dao-voting-onft-staked/src/error.rs +++ b/contracts/voting/dao-voting-onft-staked/src/error.rs @@ -16,6 +16,9 @@ pub enum ContractError { #[error(transparent)] UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + #[error(transparent)] + NftClaimError(#[from] cw721_controllers::NftClaimError), + #[error("Nothing to claim")] NothingToClaim {}, @@ -34,9 +37,6 @@ pub enum ContractError { #[error("Can not unstake that which you have not staked (unstaking {token_id})")] NotStaked { token_id: String }, - #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] - TooManyClaims {}, - #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/voting/dao-voting-onft-staked/src/msg.rs b/contracts/voting/dao-voting-onft-staked/src/msg.rs index 08e589bc5..0567aa93d 100644 --- a/contracts/voting/dao-voting-onft-staked/src/msg.rs +++ b/contracts/voting/dao-voting-onft-staked/src/msg.rs @@ -75,8 +75,11 @@ pub enum ExecuteMsg { /// Unstakes the specified token_ids on behalf of the sender. token_ids must /// have unique values and have non-zero length. Unstake { token_ids: Vec }, - /// Claim NFTs that have been unstaked for the specified duration. - ClaimNfts {}, + /// Claim NFTs that have been unstaked for the specified duration. If none + /// are provided, it attempts to claim all legacy claims. If token IDs are + /// provided, only those are claimed. If an empty vector is provided, it + /// attempts to claim all non-legacy claims. + ClaimNfts { token_ids: Option> }, /// Updates the contract configuration, namely unstaking duration. Only /// callable by the DAO that initialized this voting contract. UpdateConfig { duration: Option }, @@ -101,7 +104,11 @@ pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, #[returns(::cw721_controllers::NftClaimsResponse)] - NftClaims { address: String }, + NftClaims { + address: String, + start_after: Option, + limit: Option, + }, #[returns(::cw_controllers::HooksResponse)] Hooks {}, // List the staked NFTs for a given address. diff --git a/contracts/voting/dao-voting-onft-staked/src/state.rs b/contracts/voting/dao-voting-onft-staked/src/state.rs index 0e85822ab..0fab86f74 100644 --- a/contracts/voting/dao-voting-onft-staked/src/state.rs +++ b/contracts/voting/dao-voting-onft-staked/src/state.rs @@ -48,9 +48,14 @@ pub const TOTAL_STAKED_NFTS: SnapshotItem = SnapshotItem::new( Strategy::EveryBlock, ); -/// The maximum number of claims that may be outstanding. -pub const MAX_CLAIMS: u64 = 70; -pub const NFT_CLAIMS: NftClaims = NftClaims::new("nft_claims"); +/// The legacy NFT claims storage uses a non-paginatable vector, which limits +/// the number of claims that may be outstanding. This is horrible UX, +/// especially for large NFT collections. To allow DAOs to upgrade, we must keep +/// the legacy NFT claims storage, but we can paginate the new storage. +pub const LEGACY_NFT_CLAIMS: cw721_controllers_v250::NftClaims = + cw721_controllers_v250::NftClaims::new("nft_claims"); + +pub const NFT_CLAIMS: NftClaims = NftClaims::new("nc"); // Hooks to contracts that will receive staking and unstaking // messages. diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs index fd79fd25e..bef18ff42 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs @@ -198,7 +198,9 @@ pub fn claim_nfts(app: &mut OmniflixApp, module: &Addr, sender: &str) -> AnyResu app.execute_contract( addr!(sender), module.clone(), - &ExecuteMsg::ClaimNfts {}, + &ExecuteMsg::ClaimNfts { + token_ids: Some(vec![]), + }, &[], ) } diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs b/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs index 3ff1facde..5cd6cb45d 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs @@ -20,6 +20,8 @@ pub fn query_claims(app: &OmniflixApp, module: &Addr, addr: &str) -> StdResult anyhow::Result<()> { Ok(()) } -// I can not have more than MAX_CLAIMS claims pending. -#[test] -fn test_max_claims() -> anyhow::Result<()> { - let CommonTest { - mut app, - module, - nft, - .. - } = setup_test(Some(Duration::Height(1)), None); - - for i in 0..MAX_CLAIMS { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, STAKER, i_str)?; - unstake_nfts(&mut app, &module, STAKER, &[i_str])?; - } - - mint_and_stake_nft(&mut app, &nft, &module, STAKER, "a")?; - let res = unstake_nfts(&mut app, &module, STAKER, &["a"]); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); - - Ok(()) -} - // I can list all of the currently staked NFTs for an address. #[test] fn test_list_staked_nfts() -> anyhow::Result<()> { @@ -987,35 +963,6 @@ fn test_query_the_future() -> anyhow::Result<()> { Ok(()) } -/// I can not unstake more than one NFT in a TX in order to bypass the -/// MAX_CLAIMS limit. -#[test] -fn test_bypass_max_claims() -> anyhow::Result<()> { - let CommonTest { - mut app, - module, - nft, - .. - } = setup_test(Some(Duration::Height(1)), None); - let mut to_stake = vec![]; - for i in 1..(MAX_CLAIMS + 10) { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, STAKER, i_str)?; - if i < MAX_CLAIMS { - // unstake MAX_CLAMS - 1 NFTs - unstake_nfts(&mut app, &module, STAKER, &[i_str])?; - } else { - // push rest of NFT ids to vec - to_stake.push(i_str.clone()); - } - } - let binding = to_stake.iter().map(|s| s.as_str()).collect::>(); - let to_stake_slice: &[&str] = binding.as_slice(); - let res = unstake_nfts(&mut app, &module, STAKER, to_stake_slice); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); - Ok(()) -} - /// I can cancel my own prepared stake. #[test] fn test_preparer_cancel_prepared_stake() -> anyhow::Result<()> { diff --git a/packages/cw721-controllers/src/lib.rs b/packages/cw721-controllers/src/lib.rs index d4f58091a..66823d06a 100644 --- a/packages/cw721-controllers/src/lib.rs +++ b/packages/cw721-controllers/src/lib.rs @@ -2,4 +2,4 @@ mod nft_claim; -pub use nft_claim::{NftClaim, NftClaims, NftClaimsResponse}; +pub use nft_claim::{NftClaim, NftClaimError, NftClaims, NftClaimsResponse}; diff --git a/packages/cw721-controllers/src/nft_claim.rs b/packages/cw721-controllers/src/nft_claim.rs index 4b8702f9a..f2fe60b9e 100644 --- a/packages/cw721-controllers/src/nft_claim.rs +++ b/packages/cw721-controllers/src/nft_claim.rs @@ -1,7 +1,20 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, BlockInfo, CustomQuery, Deps, StdResult, Storage}; -use cw_storage_plus::Map; +use cosmwasm_std::{Addr, BlockInfo, CustomQuery, Deps, Order, StdError, StdResult, Storage}; +use cw_storage_plus::{Bound, Map}; use cw_utils::Expiration; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum NftClaimError { + #[error(transparent)] + Std(#[from] StdError), + + #[error("NFT claim not found for {token_id}")] + NotFound { token_id: String }, + + #[error("One or more NFTs is not ready to be claimed")] + NotReady {}, +} #[cw_serde] pub struct NftClaimsResponse { @@ -15,22 +28,22 @@ pub struct NftClaim { } impl NftClaim { - pub fn new(token_id: String, released: Expiration) -> Self { + pub fn new(token_id: String, release_at: Expiration) -> Self { NftClaim { token_id, - release_at: released, + release_at, } } } -pub struct NftClaims<'a>(Map<'a, &'a Addr, Vec>); +pub struct NftClaims<'a>(Map<'a, (&'a Addr, &'a String), Expiration>); impl<'a> NftClaims<'a> { pub const fn new(storage_key: &'a str) -> Self { NftClaims(Map::new(storage_key)) } - /// Creates a number of NFT claims simeltaniously for a given + /// Creates a number of NFT claims simultaneously for a given /// address. /// /// # Invariants @@ -46,51 +59,70 @@ impl<'a> NftClaims<'a> { token_ids: Vec, release_at: Expiration, ) -> StdResult<()> { - self.0.update(storage, addr, |old| -> StdResult<_> { - Ok(old - .unwrap_or_default() - .into_iter() - .chain(token_ids.into_iter().map(|token_id| NftClaim { - token_id, - release_at, - })) - .collect::>()) - })?; + token_ids + .into_iter() + .map(|token_id| self.0.save(storage, (addr, &token_id), &release_at)) + .collect::>>()?; Ok(()) } - /// This iterates over all mature claims for the address, and removes them, up to an optional cap. - /// it removes the finished claims and returns the total amount of tokens to be released. + /// This iterates over all claims for the given IDs, removing them if they + /// are all mature and erroring if any is not. pub fn claim_nfts( &self, storage: &mut dyn Storage, addr: &Addr, + token_ids: &[String], block: &BlockInfo, - ) -> StdResult> { - let mut to_send = vec![]; - self.0.update(storage, addr, |nft_claims| -> StdResult<_> { - let (_send, waiting): (Vec<_>, _) = - nft_claims.unwrap_or_default().into_iter().partition(|c| { - // if mature and we can pay fully, then include in _send - if c.release_at.is_expired(block) { - to_send.push(c.token_id.clone()); - true - } else { - // not to send, leave in waiting and save again - false + ) -> Result<(), NftClaimError> { + token_ids + .iter() + .map(|token_id| -> Result<(), NftClaimError> { + match self.0.may_load(storage, (addr, token_id)) { + Ok(Some(expiration)) => { + // if claim is expired, continue + if expiration.is_expired(block) { + Ok(()) + } else { + // if claim is not expired, error + Err(NftClaimError::NotReady {}) + } } - }); - Ok(waiting) - })?; - Ok(to_send) + // if claim is not found, error + Ok(None) => Err(NftClaimError::NotFound { + token_id: token_id.clone(), + }), + Err(e) => Err(e.into()), + } + }) + .collect::, NftClaimError>>()?; + + // remove all now that we've confirmed they're mature + token_ids + .iter() + .for_each(|token_id| self.0.remove(storage, (addr, token_id))); + + Ok(()) } pub fn query_claims( &self, deps: Deps, address: &Addr, + start_after: Option<&String>, + limit: Option, ) -> StdResult { - let nft_claims = self.0.may_load(deps.storage, address)?.unwrap_or_default(); + let limit = limit.map(|l| l as usize).unwrap_or(usize::MAX); + let start = start_after.map(Bound::<&String>::exclusive); + + let nft_claims = self + .0 + .prefix(address) + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| item.map(|(token_id, v)| NftClaim::new(token_id, v))) + .collect::>>()?; + Ok(NftClaimsResponse { nft_claims }) } } @@ -147,11 +179,13 @@ mod test { // Assert that claims creates a map and there is one claim for the address. let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); assert_eq!(saved_claims.len(), 1); - assert_eq!(saved_claims[0].token_id, TEST_BAYC_TOKEN_ID.to_string()); - assert_eq!(saved_claims[0].release_at, TEST_EXPIRATION); + assert_eq!(saved_claims[0].0, TEST_BAYC_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, TEST_EXPIRATION); // Adding another claim to same address, make sure that both claims are saved. claims @@ -166,16 +200,15 @@ mod test { // Assert that both claims exist for the address. let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); assert_eq!(saved_claims.len(), 2); - assert_eq!(saved_claims[0].token_id, TEST_BAYC_TOKEN_ID.to_string()); - assert_eq!(saved_claims[0].release_at, TEST_EXPIRATION); - assert_eq!( - saved_claims[1].token_id, - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ); - assert_eq!(saved_claims[1].release_at, TEST_EXPIRATION); + assert_eq!(saved_claims[0].0, TEST_BAYC_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, TEST_EXPIRATION); + assert_eq!(saved_claims[1].0, TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()); + assert_eq!(saved_claims[1].1, TEST_EXPIRATION); // Adding another claim to different address, make sure that other address only has one claim. claims @@ -190,12 +223,16 @@ mod test { // Assert that both claims exist for the address. let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); let saved_claims_addr2 = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr2")) + .prefix(&Addr::unchecked("addr2")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); assert_eq!(saved_claims.len(), 2); assert_eq!(saved_claims_addr2.len(), 1); @@ -206,19 +243,37 @@ mod test { let mut deps = mock_dependencies(); let claims = NftClaims::new("claims"); - let nfts = claims + let env = mock_env(); + let error = claims .claim_nfts( deps.as_mut().storage, &Addr::unchecked("addr"), + &["404".to_string()], + &env.block, + ) + .unwrap_err(); + assert_eq!( + error, + NftClaimError::NotFound { + token_id: "404".to_string() + } + ); + + claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[], &mock_env().block, ) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range_raw(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!(nfts.len(), 0); assert_eq!(saved_claims.len(), 0); } @@ -248,24 +303,31 @@ mod test { let mut env = mock_env(); env.block.height = 0; // the address has two claims however they are both not expired - let nfts = claims - .claim_nfts(deps.as_mut().storage, &Addr::unchecked("addr"), &env.block) - .unwrap(); + let error = claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[ + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + TEST_BAYC_TOKEN_ID.to_string(), + ], + &env.block, + ) + .unwrap_err(); + assert_eq!(error, NftClaimError::NotReady {}); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!(nfts.len(), 0); assert_eq!(saved_claims.len(), 2); - assert_eq!( - saved_claims[0].token_id, - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ); - assert_eq!(saved_claims[0].release_at, Expiration::AtHeight(10)); - assert_eq!(saved_claims[1].token_id, TEST_BAYC_TOKEN_ID.to_string()); - assert_eq!(saved_claims[1].release_at, Expiration::AtHeight(100)); + assert_eq!(saved_claims[0].0, TEST_BAYC_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, Expiration::AtHeight(100)); + assert_eq!(saved_claims[1].0, TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()); + assert_eq!(saved_claims[1].1, Expiration::AtHeight(10)); } #[test] @@ -294,23 +356,25 @@ mod test { let mut env = mock_env(); env.block.height = 20; // the address has two claims and the first one can be released - let nfts = claims - .claim_nfts(deps.as_mut().storage, &Addr::unchecked("addr"), &env.block) + claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[TEST_BAYC_TOKEN_ID.to_string()], + &env.block, + ) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!(nfts.len(), 1); - assert_eq!(nfts[0], TEST_BAYC_TOKEN_ID.to_string()); assert_eq!(saved_claims.len(), 1); - assert_eq!( - saved_claims[0].token_id, - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ); - assert_eq!(saved_claims[0].release_at, Expiration::AtHeight(100)); + assert_eq!(saved_claims[0].0, TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, Expiration::AtHeight(100)); } #[test] @@ -339,22 +403,25 @@ mod test { let mut env = mock_env(); env.block.height = 1000; // the address has two claims and both can be released - let nfts = claims - .claim_nfts(deps.as_mut().storage, &Addr::unchecked("addr"), &env.block) + claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[ + TEST_BAYC_TOKEN_ID.to_string(), + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + ], + &env.block, + ) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!( - nfts, - vec![ - TEST_BAYC_TOKEN_ID.to_string(), - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ] - ); assert_eq!(saved_claims.len(), 0); } @@ -373,15 +440,88 @@ mod test { .unwrap(); let queried_claims = claims - .query_claims(deps.as_ref(), &Addr::unchecked("addr")) + .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, None) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .map(|item| item.map(|(token_id, v)| NftClaim::new(token_id, v))) + .collect::>>() .unwrap(); + assert_eq!(queried_claims.nft_claims, saved_claims); } + #[test] + fn test_query_claims_returns_correct_claims_paginated() { + let mut deps = mock_dependencies(); + let claims = NftClaims::new("claims"); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![ + TEST_BAYC_TOKEN_ID.to_string(), + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + ], + Expiration::AtHeight(10), + ) + .unwrap(); + + let queried_claims = claims + .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, None) + .unwrap(); + assert_eq!( + queried_claims.nft_claims, + vec![ + NftClaim::new(TEST_BAYC_TOKEN_ID.to_string(), Expiration::AtHeight(10)), + NftClaim::new( + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + Expiration::AtHeight(10) + ), + ] + ); + + let queried_claims = claims + .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, Some(1)) + .unwrap(); + assert_eq!( + queried_claims.nft_claims, + vec![NftClaim::new( + TEST_BAYC_TOKEN_ID.to_string(), + Expiration::AtHeight(10) + ),] + ); + + let queried_claims = claims + .query_claims( + deps.as_ref(), + &Addr::unchecked("addr"), + Some(&TEST_BAYC_TOKEN_ID.to_string()), + None, + ) + .unwrap(); + assert_eq!( + queried_claims.nft_claims, + vec![NftClaim::new( + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + Expiration::AtHeight(10) + )] + ); + + let queried_claims = claims + .query_claims( + deps.as_ref(), + &Addr::unchecked("addr"), + Some(&TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()), + None, + ) + .unwrap(); + assert_eq!(queried_claims.nft_claims.len(), 0); + } + #[test] fn test_query_claims_returns_empty_for_non_existent_user() { let mut deps = mock_dependencies(); @@ -397,7 +537,7 @@ mod test { .unwrap(); let queried_claims = claims - .query_claims(deps.as_ref(), &Addr::unchecked("addr2")) + .query_claims(deps.as_ref(), &Addr::unchecked("addr2"), None, None) .unwrap(); assert_eq!(queried_claims.nft_claims.len(), 0); From 8999c828434751f58234e27bc01ec51f65f205e0 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Thu, 31 Oct 2024 12:21:38 -0400 Subject: [PATCH 2/6] update to v2.6.0 --- Cargo.lock | 472 +++++++++--------- Cargo.toml | 94 ++-- .../dao-dao-core/schema/dao-dao-core.json | 2 +- .../schema/cw-fund-distributor.json | 2 +- .../schema/dao-rewards-distributor.json | 2 +- .../schema/btsg-ft-factory.json | 2 +- .../schema/cw-admin-factory.json | 2 +- .../schema/cw-payroll-factory.json | 2 +- .../cw-token-swap/schema/cw-token-swap.json | 2 +- .../schema/cw-tokenfactory-issuer.json | 2 +- .../cw-vesting/schema/cw-vesting.json | 2 +- .../cw721-roles/schema/cw721-roles.json | 2 +- .../dao-migrator/schema/dao-migrator.json | 2 +- .../dao-pre-propose-approval-multiple.json | 2 +- .../dao-pre-propose-approval-single.json | 2 +- .../schema/dao-pre-propose-approver.json | 2 +- .../schema/dao-pre-propose-multiple.json | 2 +- .../schema/dao-pre-propose-single.json | 2 +- .../schema/dao-proposal-condorcet.json | 2 +- .../schema/dao-proposal-multiple.json | 2 +- .../schema/dao-proposal-single.json | 2 +- .../schema/cw20-stake-external-rewards.json | 2 +- .../schema/cw20-stake-reward-distributor.json | 2 +- .../staking/cw20-stake/schema/cw20-stake.json | 2 +- .../schema/dao-voting-cw20-staked.json | 2 +- .../dao-voting-cw4/schema/dao-voting-cw4.json | 2 +- .../schema/dao-voting-cw721-roles.json | 2 +- .../schema/dao-voting-cw721-staked.json | 2 +- .../schema/dao-voting-onft-staked.json | 2 +- .../schema/dao-voting-token-staked.json | 2 +- 30 files changed, 311 insertions(+), 311 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28d3876c8..79a71d9af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,12 +271,12 @@ dependencies = [ "cw-admin-factory", "cw-utils 1.0.3", "cw20 1.1.2", - "cw20-stake 2.5.1", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-single 2.5.1", - "dao-proposal-single 2.5.1", - "dao-voting 2.5.1", + "cw20-stake 2.6.0", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-single 2.6.0", + "dao-proposal-single 2.6.0", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "env_logger", "serde", @@ -295,7 +295,7 @@ dependencies = [ [[package]] name = "btsg-ft-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -304,11 +304,11 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-token-staked", "osmosis-std-derive", "prost 0.12.3", @@ -686,7 +686,7 @@ dependencies = [ [[package]] name = "cw-admin-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "bech32", "cosmwasm-schema", @@ -698,11 +698,11 @@ dependencies = [ "cw2 1.1.2", "cw20-base 1.1.2", "cw4 1.1.2", - "dao-interface 2.5.1", - "dao-proposal-single 2.5.1", + "dao-interface 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", - "dao-voting-cw4 2.5.1", + "dao-voting 2.6.0", + "dao-voting-cw4 2.6.0", "osmosis-test-tube", "thiserror", ] @@ -831,7 +831,7 @@ dependencies = [ [[package]] name = "cw-denom" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -844,21 +844,21 @@ dependencies = [ [[package]] name = "cw-fund-distributor" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-fund-distributor", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", + "cw20-stake 2.6.0", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", "dao-testing", "dao-voting-cw20-staked", "thiserror", @@ -878,7 +878,7 @@ dependencies = [ [[package]] name = "cw-hooks" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -957,7 +957,7 @@ dependencies = [ [[package]] name = "cw-paginate-storage" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-std", "cw-multi-test", @@ -967,11 +967,11 @@ dependencies = [ [[package]] name = "cw-payroll-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-ownable", "cw-payroll-factory", @@ -1013,7 +1013,7 @@ dependencies = [ [[package]] name = "cw-stake-tracker" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1066,7 +1066,7 @@ dependencies = [ [[package]] name = "cw-token-swap" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1083,7 +1083,7 @@ dependencies = [ [[package]] name = "cw-tokenfactory-issuer" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1092,7 +1092,7 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-tokenfactory-types", "cw2 1.1.2", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "osmosis-std", "osmosis-test-tube", "prost 0.12.3", @@ -1105,11 +1105,11 @@ dependencies = [ [[package]] name = "cw-tokenfactory-types" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "osmosis-std", "osmosis-std-derive", "prost 0.12.3", @@ -1176,12 +1176,12 @@ dependencies = [ [[package]] name = "cw-vesting" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-ownable", "cw-stake-tracker", @@ -1200,7 +1200,7 @@ dependencies = [ [[package]] name = "cw-wormhole" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1367,16 +1367,16 @@ dependencies = [ [[package]] name = "cw20-stake" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-ownable", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", "cw-utils 1.0.3", @@ -1384,16 +1384,16 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw20-stake 0.2.6", - "cw20-stake 2.5.1", - "dao-hooks 2.5.1", + "cw20-stake 2.6.0", + "dao-hooks 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "thiserror", ] [[package]] name = "cw20-stake-external-rewards" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1407,9 +1407,9 @@ dependencies = [ "cw20 0.13.4", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-stake-external-rewards", - "dao-hooks 2.5.1", + "dao-hooks 2.6.0", "dao-testing", "stake-cw20-external-rewards", "thiserror", @@ -1417,7 +1417,7 @@ dependencies = [ [[package]] name = "cw20-stake-reward-distributor" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1428,7 +1428,7 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-stake-reward-distributor", "dao-testing", "stake-cw20-reward-distributor", @@ -1636,7 +1636,7 @@ dependencies = [ [[package]] name = "cw721-controllers" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1647,7 +1647,7 @@ dependencies = [ [[package]] name = "cw721-roles" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1669,7 +1669,7 @@ dependencies = [ [[package]] name = "dao-cw721-extensions" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1699,13 +1699,13 @@ dependencies = [ [[package]] name = "dao-dao-core" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-core", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1713,9 +1713,9 @@ dependencies = [ "cw20-base 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "dao-dao-core 2.5.1", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-proposal-sudo", "dao-testing", "dao-voting-cw20-balance", @@ -1736,13 +1736,13 @@ dependencies = [ [[package]] name = "dao-dao-macros" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-voting 2.5.1", + "cw-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-voting 2.6.0", "proc-macro2", "quote", "syn 1.0.109", @@ -1764,14 +1764,14 @@ dependencies = [ [[package]] name = "dao-hooks" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw4 1.1.2", - "dao-pre-propose-base 2.5.1", - "dao-voting 2.5.1", + "dao-pre-propose-base 2.6.0", + "dao-voting 2.6.0", ] [[package]] @@ -1791,7 +1791,7 @@ dependencies = [ [[package]] name = "dao-interface" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1804,7 +1804,7 @@ dependencies = [ [[package]] name = "dao-migrator" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1821,31 +1821,31 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw20-stake 0.2.6", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-staked-balance-voting", "cw4 0.13.4", "cw4-voting", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", "dao-migrator", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 0.1.0", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-pre-propose-approval-multiple" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1853,15 +1853,15 @@ dependencies = [ "cw20-base 1.1.2", "cw4 1.1.2", "cw4-group 1.1.2", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-proposal-multiple 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-proposal-multiple 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] @@ -1884,13 +1884,13 @@ dependencies = [ [[package]] name = "dao-pre-propose-approval-single" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1899,30 +1899,30 @@ dependencies = [ "cw4 1.1.2", "cw4-group 1.1.2", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "dao-pre-propose-approval-single 2.4.1", - "dao-pre-propose-base 2.5.1", + "dao-pre-propose-base 2.6.0", "dao-proposal-single 2.4.1", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-pre-propose-approver" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1930,18 +1930,18 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw4-group 1.1.2", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-pre-propose-approval-multiple", - "dao-pre-propose-approval-single 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-proposal-multiple 2.5.1", - "dao-proposal-single 2.5.1", + "dao-pre-propose-approval-single 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-proposal-multiple 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", ] [[package]] @@ -1965,18 +1965,18 @@ dependencies = [ [[package]] name = "dao-pre-propose-base" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "dao-interface 2.5.1", - "dao-voting 2.5.1", + "dao-interface 2.6.0", + "dao-voting 2.6.0", "semver", "serde", "thiserror", @@ -1997,11 +1997,11 @@ dependencies = [ [[package]] name = "dao-pre-propose-multiple" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2010,20 +2010,20 @@ dependencies = [ "cw4 1.1.2", "cw4-group 1.1.2", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", "dao-pre-propose-multiple 2.4.1", "dao-proposal-multiple 2.4.1", - "dao-proposal-multiple 2.5.1", + "dao-proposal-multiple 2.6.0", "dao-testing", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", ] [[package]] @@ -2041,12 +2041,12 @@ dependencies = [ [[package]] name = "dao-pre-propose-single" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2055,25 +2055,25 @@ dependencies = [ "cw4 1.1.2", "cw4-group 1.1.2", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", "dao-pre-propose-single 2.4.1", "dao-proposal-single 2.4.1", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", ] [[package]] name = "dao-proposal-condorcet" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2084,34 +2084,34 @@ dependencies = [ "cw2 1.1.2", "cw4 1.1.2", "cw4-group 1.1.2", - "dao-dao-core 2.5.1", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", - "dao-voting 2.5.1", - "dao-voting-cw4 2.5.1", + "dao-voting 2.6.0", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-proposal-hook-counter" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "thiserror", ] @@ -2141,35 +2141,35 @@ dependencies = [ [[package]] name = "dao-proposal-multiple" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw4 1.1.2", "cw4-group 1.1.2", "cw721-base 0.18.0", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-pre-propose-multiple 2.5.1", - "dao-proposal-multiple 2.5.1", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-pre-propose-multiple 2.6.0", + "dao-proposal-multiple 2.6.0", "dao-testing", "dao-voting 0.1.0", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-staked", "dao-voting-token-staked", "rand", @@ -2202,14 +2202,14 @@ dependencies = [ [[package]] name = "dao-proposal-single" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-core", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-proposal-single", "cw-storage-plus 1.2.0", @@ -2218,23 +2218,23 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw4 1.1.2", "cw4-group 1.1.2", "cw721-base 0.18.0", - "dao-dao-core 2.5.1", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-pre-propose-single 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-pre-propose-single 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 0.1.0", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-staked", "dao-voting-token-staked", "thiserror", @@ -2242,21 +2242,21 @@ dependencies = [ [[package]] name = "dao-proposal-sudo" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "thiserror", ] [[package]] name = "dao-rewards-distributor" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2269,17 +2269,17 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw4 1.1.2", "cw4-group 1.1.2", "cw721-base 0.18.0", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-rewards-distributor", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-staked", "dao-voting-token-staked", "semver", @@ -2288,7 +2288,7 @@ dependencies = [ [[package]] name = "dao-test-custom-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2300,15 +2300,15 @@ dependencies = [ "cw2 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", - "dao-voting 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", + "dao-voting 2.6.0", "thiserror", ] [[package]] name = "dao-testing" -version = "2.5.1" +version = "2.6.0" dependencies = [ "btsg-ft-factory", "cosmwasm-schema", @@ -2316,7 +2316,7 @@ dependencies = [ "cw-admin-factory", "cw-core", "cw-fund-distributor", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-payroll-factory", "cw-proposal-single", @@ -2328,7 +2328,7 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw20-stake 0.2.6", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-stake-external-rewards", "cw20-stake-reward-distributor", "cw4 1.1.2", @@ -2337,33 +2337,33 @@ dependencies = [ "cw721-base 0.18.0", "cw721-roles", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", + "dao-dao-core 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "dao-migrator", "dao-pre-propose-approval-single 2.4.1", - "dao-pre-propose-approval-single 2.5.1", + "dao-pre-propose-approval-single 2.6.0", "dao-pre-propose-approver", "dao-pre-propose-multiple 2.4.1", - "dao-pre-propose-multiple 2.5.1", + "dao-pre-propose-multiple 2.6.0", "dao-pre-propose-single 2.4.1", - "dao-pre-propose-single 2.5.1", + "dao-pre-propose-single 2.6.0", "dao-proposal-condorcet", "dao-proposal-hook-counter", "dao-proposal-multiple 2.4.1", - "dao-proposal-multiple 2.5.1", + "dao-proposal-multiple 2.6.0", "dao-proposal-single 2.4.1", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-proposal-sudo", "dao-rewards-distributor", "dao-test-custom-factory", "dao-voting 0.1.0", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-roles", "dao-voting-cw721-staked", "dao-voting-onft-staked", @@ -2409,22 +2409,22 @@ dependencies = [ [[package]] name = "dao-voting" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw20 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "thiserror", ] [[package]] name = "dao-voting-cw20-balance" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2434,15 +2434,15 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", "thiserror", ] [[package]] name = "dao-voting-cw20-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2452,11 +2452,11 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "cw20-stake 2.6.0", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "thiserror", ] @@ -2480,7 +2480,7 @@ dependencies = [ [[package]] name = "dao-voting-cw4" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2490,16 +2490,16 @@ dependencies = [ "cw2 1.1.2", "cw4 1.1.2", "cw4-group 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-voting-cw721-roles" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2514,21 +2514,21 @@ dependencies = [ "cw721-base 0.18.0", "cw721-roles", "dao-cw721-extensions", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", "thiserror", ] [[package]] name = "dao-voting-cw721-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -2536,15 +2536,15 @@ dependencies = [ "cw721 0.18.0", "cw721-base 0.18.0", "cw721-controllers 2.5.0", - "cw721-controllers 2.5.1", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "cw721-controllers 2.6.0", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-proposal-hook-counter", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "osmosis-std", "osmosis-test-tube", "serde", @@ -2553,27 +2553,27 @@ dependencies = [ [[package]] name = "dao-voting-onft-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw721-controllers 2.5.0", - "cw721-controllers 2.5.1", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "cw721-controllers 2.6.0", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-proposal-hook-counter", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "omniflix-std", "osmosis-test-tube", "prost 0.12.3", @@ -2584,27 +2584,27 @@ dependencies = [ [[package]] name = "dao-voting-token-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", "cw-utils 1.0.3", "cw2 1.1.2", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-proposal-hook-counter", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "osmosis-std", "osmosis-test-tube", "serde", @@ -3292,16 +3292,16 @@ dependencies = [ "cw-vesting", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw721 0.18.0", "cw721-base 0.18.0", "cw721-roles", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-single 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-single 2.6.0", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw721-staked", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 63c019b5f..e2e18cab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ resolver = "2" edition = "2021" license = "BSD-3-Clause" repository = "https://github.com/DA0-DA0/dao-contracts" -version = "2.5.1" +version = "2.6.0" [profile.release] codegen-units = 1 @@ -85,52 +85,52 @@ wynd-utils = "0.4" # optional owner. cw-ownable = "0.5" -btsg-ft-factory = { path = "./contracts/external/btsg-ft-factory", version = "2.5.1" } -cw-admin-factory = { path = "./contracts/external/cw-admin-factory", version = "2.5.1" } -cw-denom = { path = "./packages/cw-denom", version = "2.5.1" } -cw-fund-distributor = { path = "./contracts/distribution/cw-fund-distributor", version = "2.5.1" } -cw-hooks = { path = "./packages/cw-hooks", version = "2.5.1" } -cw-paginate-storage = { path = "./packages/cw-paginate-storage", version = "2.5.1" } -cw-payroll-factory = { path = "./contracts/external/cw-payroll-factory", version = "2.5.1" } -cw-stake-tracker = { path = "./packages/cw-stake-tracker", version = "2.5.1" } -cw-token-swap = { path = "./contracts/external/cw-token-swap", version = "2.5.1" } -cw-tokenfactory-issuer = { path = "./contracts/external/cw-tokenfactory-issuer", version = "2.5.1", default-features = false } -cw-tokenfactory-types = { path = "./packages/cw-tokenfactory-types", version = "2.5.1", default-features = false } -cw-vesting = { path = "./contracts/external/cw-vesting", version = "2.5.1" } -cw-wormhole = { path = "./packages/cw-wormhole", version = "2.5.1" } -cw20-stake = { path = "./contracts/staking/cw20-stake", version = "2.5.1" } -cw20-stake-external-rewards = { path = "./contracts/staking/cw20-stake-external-rewards", version = "2.5.1" } -cw20-stake-reward-distributor = { path = "./contracts/staking/cw20-stake-reward-distributor", version = "2.5.1" } -cw721-controllers = { path = "./packages/cw721-controllers", version = "2.5.1" } -cw721-roles = { path = "./contracts/external/cw721-roles", version = "2.5.1" } -dao-cw721-extensions = { path = "./packages/dao-cw721-extensions", version = "2.5.1" } -dao-dao-core = { path = "./contracts/dao-dao-core", version = "2.5.1" } -dao-dao-macros = { path = "./packages/dao-dao-macros", version = "2.5.1" } -dao-hooks = { path = "./packages/dao-hooks", version = "2.5.1" } -dao-interface = { path = "./packages/dao-interface", version = "2.5.1" } -dao-pre-propose-approval-multiple = { path = "./contracts/pre-propose/dao-pre-propose-approval-multiple", version = "2.5.1" } -dao-migrator = { path = "./contracts/external/dao-migrator", version = "2.5.1" } -dao-pre-propose-approval-single = { path = "./contracts/pre-propose/dao-pre-propose-approval-single", version = "2.5.1" } -dao-pre-propose-approver = { path = "./contracts/pre-propose/dao-pre-propose-approver", version = "2.5.1" } -dao-pre-propose-base = { path = "./packages/dao-pre-propose-base", version = "2.5.1" } -dao-pre-propose-multiple = { path = "./contracts/pre-propose/dao-pre-propose-multiple", version = "2.5.1" } -dao-pre-propose-single = { path = "./contracts/pre-propose/dao-pre-propose-single", version = "2.5.1" } -dao-proposal-condorcet = { path = "./contracts/proposal/dao-proposal-condorcet", version = "2.5.1" } -dao-proposal-hook-counter = { path = "./contracts/test/dao-proposal-hook-counter", version = "2.5.1" } -dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", version = "2.5.1" } -dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.5.1" } -dao-proposal-sudo = { path = "./contracts/test/dao-proposal-sudo", version = "2.5.1" } -dao-rewards-distributor = { path = "./contracts/distribution/dao-rewards-distributor", version = "2.5.1" } -dao-test-custom-factory = { path = "./contracts/test/dao-test-custom-factory", version = "2.5.1" } -dao-testing = { path = "./packages/dao-testing", version = "2.5.1" } -dao-voting = { path = "./packages/dao-voting", version = "2.5.1" } -dao-voting-cw20-balance = { path = "./contracts/test/dao-voting-cw20-balance", version = "2.5.1" } -dao-voting-cw20-staked = { path = "./contracts/voting/dao-voting-cw20-staked", version = "2.5.1" } -dao-voting-cw4 = { path = "./contracts/voting/dao-voting-cw4", version = "2.5.1" } -dao-voting-cw721-roles = { path = "./contracts/voting/dao-voting-cw721-roles", version = "2.5.1" } -dao-voting-cw721-staked = { path = "./contracts/voting/dao-voting-cw721-staked", version = "2.5.1" } -dao-voting-onft-staked = { path = "./contracts/voting/dao-voting-onft-staked", version = "2.5.1" } -dao-voting-token-staked = { path = "./contracts/voting/dao-voting-token-staked", version = "2.5.1" } +btsg-ft-factory = { path = "./contracts/external/btsg-ft-factory", version = "2.6.0" } +cw-admin-factory = { path = "./contracts/external/cw-admin-factory", version = "2.6.0" } +cw-denom = { path = "./packages/cw-denom", version = "2.6.0" } +cw-fund-distributor = { path = "./contracts/distribution/cw-fund-distributor", version = "2.6.0" } +cw-hooks = { path = "./packages/cw-hooks", version = "2.6.0" } +cw-paginate-storage = { path = "./packages/cw-paginate-storage", version = "2.6.0" } +cw-payroll-factory = { path = "./contracts/external/cw-payroll-factory", version = "2.6.0" } +cw-stake-tracker = { path = "./packages/cw-stake-tracker", version = "2.6.0" } +cw-token-swap = { path = "./contracts/external/cw-token-swap", version = "2.6.0" } +cw-tokenfactory-issuer = { path = "./contracts/external/cw-tokenfactory-issuer", version = "2.6.0", default-features = false } +cw-tokenfactory-types = { path = "./packages/cw-tokenfactory-types", version = "2.6.0", default-features = false } +cw-vesting = { path = "./contracts/external/cw-vesting", version = "2.6.0" } +cw-wormhole = { path = "./packages/cw-wormhole", version = "2.6.0" } +cw20-stake = { path = "./contracts/staking/cw20-stake", version = "2.6.0" } +cw20-stake-external-rewards = { path = "./contracts/staking/cw20-stake-external-rewards", version = "2.6.0" } +cw20-stake-reward-distributor = { path = "./contracts/staking/cw20-stake-reward-distributor", version = "2.6.0" } +cw721-controllers = { path = "./packages/cw721-controllers", version = "2.6.0" } +cw721-roles = { path = "./contracts/external/cw721-roles", version = "2.6.0" } +dao-cw721-extensions = { path = "./packages/dao-cw721-extensions", version = "2.6.0" } +dao-dao-core = { path = "./contracts/dao-dao-core", version = "2.6.0" } +dao-dao-macros = { path = "./packages/dao-dao-macros", version = "2.6.0" } +dao-hooks = { path = "./packages/dao-hooks", version = "2.6.0" } +dao-interface = { path = "./packages/dao-interface", version = "2.6.0" } +dao-pre-propose-approval-multiple = { path = "./contracts/pre-propose/dao-pre-propose-approval-multiple", version = "2.6.0" } +dao-migrator = { path = "./contracts/external/dao-migrator", version = "2.6.0" } +dao-pre-propose-approval-single = { path = "./contracts/pre-propose/dao-pre-propose-approval-single", version = "2.6.0" } +dao-pre-propose-approver = { path = "./contracts/pre-propose/dao-pre-propose-approver", version = "2.6.0" } +dao-pre-propose-base = { path = "./packages/dao-pre-propose-base", version = "2.6.0" } +dao-pre-propose-multiple = { path = "./contracts/pre-propose/dao-pre-propose-multiple", version = "2.6.0" } +dao-pre-propose-single = { path = "./contracts/pre-propose/dao-pre-propose-single", version = "2.6.0" } +dao-proposal-condorcet = { path = "./contracts/proposal/dao-proposal-condorcet", version = "2.6.0" } +dao-proposal-hook-counter = { path = "./contracts/test/dao-proposal-hook-counter", version = "2.6.0" } +dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", version = "2.6.0" } +dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.6.0" } +dao-proposal-sudo = { path = "./contracts/test/dao-proposal-sudo", version = "2.6.0" } +dao-rewards-distributor = { path = "./contracts/distribution/dao-rewards-distributor", version = "2.6.0" } +dao-test-custom-factory = { path = "./contracts/test/dao-test-custom-factory", version = "2.6.0" } +dao-testing = { path = "./packages/dao-testing", version = "2.6.0" } +dao-voting = { path = "./packages/dao-voting", version = "2.6.0" } +dao-voting-cw20-balance = { path = "./contracts/test/dao-voting-cw20-balance", version = "2.6.0" } +dao-voting-cw20-staked = { path = "./contracts/voting/dao-voting-cw20-staked", version = "2.6.0" } +dao-voting-cw4 = { path = "./contracts/voting/dao-voting-cw4", version = "2.6.0" } +dao-voting-cw721-roles = { path = "./contracts/voting/dao-voting-cw721-roles", version = "2.6.0" } +dao-voting-cw721-staked = { path = "./contracts/voting/dao-voting-cw721-staked", version = "2.6.0" } +dao-voting-onft-staked = { path = "./contracts/voting/dao-voting-onft-staked", version = "2.6.0" } +dao-voting-token-staked = { path = "./contracts/voting/dao-voting-token-staked", version = "2.6.0" } # v1 dependencies. used for state migrations. cw-core-v1 = { package = "cw-core", version = "0.1.0" } diff --git a/contracts/dao-dao-core/schema/dao-dao-core.json b/contracts/dao-dao-core/schema/dao-dao-core.json index 7ca1635ea..b9f673d8e 100644 --- a/contracts/dao-dao-core/schema/dao-dao-core.json +++ b/contracts/dao-dao-core/schema/dao-dao-core.json @@ -1,6 +1,6 @@ { "contract_name": "dao-dao-core", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json b/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json index 8c7e8ac22..3666612bd 100644 --- a/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json +++ b/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json @@ -1,6 +1,6 @@ { "contract_name": "cw-fund-distributor", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json b/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json index 93f017585..07d8eec5c 100644 --- a/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json +++ b/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json @@ -1,6 +1,6 @@ { "contract_name": "dao-rewards-distributor", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json b/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json index 784d1ca5a..e937ca5bb 100644 --- a/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json +++ b/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json @@ -1,6 +1,6 @@ { "contract_name": "btsg-ft-factory", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-admin-factory/schema/cw-admin-factory.json b/contracts/external/cw-admin-factory/schema/cw-admin-factory.json index 743c9ceed..edff0be9f 100644 --- a/contracts/external/cw-admin-factory/schema/cw-admin-factory.json +++ b/contracts/external/cw-admin-factory/schema/cw-admin-factory.json @@ -1,6 +1,6 @@ { "contract_name": "cw-admin-factory", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json b/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json index 1544e81a9..6bf66ae5d 100644 --- a/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json +++ b/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json @@ -1,6 +1,6 @@ { "contract_name": "cw-payroll-factory", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-token-swap/schema/cw-token-swap.json b/contracts/external/cw-token-swap/schema/cw-token-swap.json index 66b147cd2..1f6dade46 100644 --- a/contracts/external/cw-token-swap/schema/cw-token-swap.json +++ b/contracts/external/cw-token-swap/schema/cw-token-swap.json @@ -1,6 +1,6 @@ { "contract_name": "cw-token-swap", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 a7bed322d..8240fba03 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -1,6 +1,6 @@ { "contract_name": "cw-tokenfactory-issuer", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-vesting/schema/cw-vesting.json b/contracts/external/cw-vesting/schema/cw-vesting.json index 6a3fd952d..c85fc4d39 100644 --- a/contracts/external/cw-vesting/schema/cw-vesting.json +++ b/contracts/external/cw-vesting/schema/cw-vesting.json @@ -1,6 +1,6 @@ { "contract_name": "cw-vesting", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw721-roles/schema/cw721-roles.json b/contracts/external/cw721-roles/schema/cw721-roles.json index 18e12c74b..e4d7c61a2 100644 --- a/contracts/external/cw721-roles/schema/cw721-roles.json +++ b/contracts/external/cw721-roles/schema/cw721-roles.json @@ -1,6 +1,6 @@ { "contract_name": "cw721-roles", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/dao-migrator/schema/dao-migrator.json b/contracts/external/dao-migrator/schema/dao-migrator.json index 954647379..c83063c9b 100644 --- a/contracts/external/dao-migrator/schema/dao-migrator.json +++ b/contracts/external/dao-migrator/schema/dao-migrator.json @@ -1,6 +1,6 @@ { "contract_name": "dao-migrator", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json b/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json index bfcff069a..520973ce4 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json +++ b/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-approval-multiple", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 ef0e213b4..c6a999b80 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 @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-approval-single", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json index 1168bb676..bfb71f2fd 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json +++ b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-approver", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 9d2b4a9b5..614783775 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 @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-multiple", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 ca2a8348c..136a7800b 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 @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-single", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 49c48e13b..fdc97d640 100644 --- a/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json +++ b/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json @@ -1,6 +1,6 @@ { "contract_name": "dao-proposal-condorcet", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 0921abb8a..43f5f9941 100644 --- a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json +++ b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json @@ -1,6 +1,6 @@ { "contract_name": "dao-proposal-multiple", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 cd90bc875..c08edfbc4 100644 --- a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json +++ b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json @@ -1,6 +1,6 @@ { "contract_name": "dao-proposal-single", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json b/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json index 9bb4e69d7..58653c88d 100644 --- a/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json +++ b/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json @@ -1,6 +1,6 @@ { "contract_name": "cw20-stake-external-rewards", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json b/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json index d1328d9d4..da228880e 100644 --- a/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json +++ b/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json @@ -1,6 +1,6 @@ { "contract_name": "cw20-stake-reward-distributor", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/staking/cw20-stake/schema/cw20-stake.json b/contracts/staking/cw20-stake/schema/cw20-stake.json index 8716e1d57..b12f6659f 100644 --- a/contracts/staking/cw20-stake/schema/cw20-stake.json +++ b/contracts/staking/cw20-stake/schema/cw20-stake.json @@ -1,6 +1,6 @@ { "contract_name": "cw20-stake", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json b/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json index ce7327e21..fca963e8e 100644 --- a/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json +++ b/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw20-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json b/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json index cee956c6e..e6595e7d7 100644 --- a/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json +++ b/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw4", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json b/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json index e804bb47a..3d581ddbd 100644 --- a/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json +++ b/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw721-roles", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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 a6f5473c8..0be42988b 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 @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw721-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json index 76958af84..03e221792 100644 --- a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json +++ b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-onft-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json index 68d20647d..da1db7de1 100644 --- a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json +++ b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-token-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", From ed909609e6a0713f880891f8a2e60aa75f033b05 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Thu, 31 Oct 2024 13:12:12 -0400 Subject: [PATCH 3/6] added legacy NFT claim test --- .../src/testing/execute.rs | 25 +++ .../src/testing/tests.rs | 169 ++++++++++++++++- .../src/testing/execute.rs | 29 +++ .../src/testing/tests.rs | 172 +++++++++++++++++- 4 files changed, 392 insertions(+), 3 deletions(-) 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 08a1b397a..a80b0b90c 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs @@ -117,6 +117,31 @@ pub fn claim_nfts(app: &mut App, module: &Addr, sender: &str) -> AnyResult AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { + token_ids: Some(token_ids.to_vec()), + }, + &[], + ) +} + +pub fn claim_legacy_nfts(app: &mut App, module: &Addr, sender: &str) -> AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { token_ids: None }, + &[], + ) +} + pub fn add_hook(app: &mut App, module: &Addr, sender: &str, hook: &str) -> AnyResult { app.execute_contract( addr!(sender), 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 b16fdbf43..7b690933a 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs @@ -1,8 +1,12 @@ +use cosmwasm_std::storage_keys::to_length_prefixed_nested; use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{to_json_binary, Addr, Coin, Decimal, Empty, Uint128, WasmMsg}; +use cosmwasm_std::{ + to_json_binary, to_json_vec, Addr, Coin, Decimal, Empty, Storage, Uint128, WasmMsg, +}; use cw721_base::msg::{ExecuteMsg as Cw721ExecuteMsg, InstantiateMsg as Cw721InstantiateMsg}; use cw721_controllers::{NftClaim, NftClaimsResponse}; use cw_multi_test::{next_block, App, BankSudo, Executor, SudoMsg}; +use cw_storage_plus::Map; use cw_utils::Duration; use dao_interface::voting::IsActiveResponse; use dao_testing::contracts::{ @@ -10,6 +14,7 @@ use dao_testing::contracts::{ }; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; +use crate::testing::execute::{claim_legacy_nfts, claim_specific_nfts}; use crate::{ contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}, @@ -300,6 +305,168 @@ fn test_claims() -> anyhow::Result<()> { Ok(()) } +// I can query and claim my pending legacy claims and non-legacy claims. +#[test] +pub fn test_legacy_claims_work() -> anyhow::Result<()> { + let CommonTest { + mut app, + module, + nft, + } = 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")?; + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "3")?; + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "4")?; + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "5")?; + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!(claims.nft_claims, vec![]); + + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + // insert legacy claims manually + + // taken from cw-multi-test's WasmKeeper::contract_storage in wasm.rs + let mut module_namespace = b"contract_data/".to_vec(); + module_namespace.extend_from_slice(module.as_bytes()); + let prefix = to_length_prefixed_nested(&[b"wasm", &module_namespace]); + let key = Map::<&Addr, Vec>::new("nft_claims").key(&Addr::unchecked(CREATOR_ADDR)); + let mut legacy_nft_claims_key = prefix; + legacy_nft_claims_key.extend_from_slice(&key); + + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "4".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "4".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }] + ); + + // claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no non-legacy claims + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + // legacy claim works + claim_legacy_nfts(&mut app, &module, CREATOR_ADDR).unwrap(); + let owner = query_nft_owner(&app, &nft, "4")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // unstake non-legacy + unstake_nfts(&mut app, &module, CREATOR_ADDR, &["2"])?; + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "2".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }] + ); + + // Claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no legacy claims + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + claim_nfts(&mut app, &module, CREATOR_ADDR)?; + + let owner = query_nft_owner(&app, &nft, "2")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // unstake another non-legacy + unstake_nfts(&mut app, &module, CREATOR_ADDR, &["3"])?; + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "3".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }] + ); + + app.update_block(next_block); + + claim_specific_nfts(&mut app, &module, CREATOR_ADDR, &["3".to_string()])?; + let owner = query_nft_owner(&app, &nft, "3")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // unstake legacy + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "5".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + // unstake non-legacy + unstake_nfts(&mut app, &module, CREATOR_ADDR, &["1"])?; + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![ + NftClaim { + token_id: "5".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }, + NftClaim { + token_id: "1".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + } + ] + ); + + app.update_block(next_block); + + // both claims should be ready to claim + claim_legacy_nfts(&mut app, &module, CREATOR_ADDR)?; + claim_nfts(&mut app, &module, CREATOR_ADDR)?; + + let owner = query_nft_owner(&app, &nft, "1")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + let owner = query_nft_owner(&app, &nft, "5")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // no claims left + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!(claims.nft_claims, vec![]); + + Ok(()) +} + // I can list all of the currently staked NFTs for an address. #[test] fn test_list_staked_nfts() -> anyhow::Result<()> { diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs index bef18ff42..2936f8fb7 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs @@ -205,6 +205,35 @@ pub fn claim_nfts(app: &mut OmniflixApp, module: &Addr, sender: &str) -> AnyResu ) } +pub fn claim_specific_nfts( + app: &mut OmniflixApp, + module: &Addr, + sender: &str, + token_ids: &[String], +) -> AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { + token_ids: Some(token_ids.to_vec()), + }, + &[], + ) +} + +pub fn claim_legacy_nfts( + app: &mut OmniflixApp, + module: &Addr, + sender: &str, +) -> AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { token_ids: None }, + &[], + ) +} + pub fn add_hook( app: &mut OmniflixApp, module: &Addr, diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs b/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs index a906771db..2f120403a 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs @@ -1,13 +1,18 @@ +use cosmwasm_std::storage_keys::to_length_prefixed_nested; use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{Addr, Decimal, Uint128}; +use cosmwasm_std::{to_json_vec, Addr, Decimal, Storage, Uint128}; use cw721_controllers::{NftClaim, NftClaimsResponse}; use cw_multi_test::{next_block, Executor}; +use cw_storage_plus::Map; use cw_utils::Duration; use dao_interface::voting::IsActiveResponse; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; use crate::msg::OnftCollection; -use crate::testing::execute::{cancel_stake, confirm_stake_nft, prepare_stake_nft, send_nft}; +use crate::testing::execute::{ + cancel_stake, claim_legacy_nfts, claim_specific_nfts, confirm_stake_nft, prepare_stake_nft, + send_nft, +}; use crate::testing::queries::query_dao; use crate::testing::DAO; use crate::{ @@ -293,6 +298,169 @@ fn test_claims() -> anyhow::Result<()> { Ok(()) } +// I can query and claim my pending legacy claims and non-legacy claims. +#[test] +pub fn test_legacy_claims_work() -> anyhow::Result<()> { + let CommonTest { + mut app, + module, + nft, + .. + } = setup_test(Some(Duration::Height(1)), None); + + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "1")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "2")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "3")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "4")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "5")?; + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!(claims.nft_claims, vec![]); + + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + // insert legacy claims manually + + // taken from cw-multi-test's WasmKeeper::contract_storage in wasm.rs + let mut module_namespace = b"contract_data/".to_vec(); + module_namespace.extend_from_slice(module.as_bytes()); + let prefix = to_length_prefixed_nested(&[b"wasm", &module_namespace]); + let key = Map::<&Addr, Vec>::new("nft_claims").key(&Addr::unchecked(STAKER)); + let mut legacy_nft_claims_key = prefix; + legacy_nft_claims_key.extend_from_slice(&key); + + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "4".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "4".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }] + ); + + // claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no non-legacy claims + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + // legacy claim works + claim_legacy_nfts(&mut app, &module, STAKER).unwrap(); + let owner = query_nft_owner(&app, &nft, "4")?; + assert_eq!(owner, STAKER.to_string()); + + // unstake non-legacy + unstake_nfts(&mut app, &module, STAKER, &["2"])?; + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "2".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }] + ); + + // Claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no legacy claims + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + claim_nfts(&mut app, &module, STAKER)?; + + let owner = query_nft_owner(&app, &nft, "2")?; + assert_eq!(owner, STAKER.to_string()); + + // unstake another non-legacy + unstake_nfts(&mut app, &module, STAKER, &["3"])?; + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "3".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }] + ); + + app.update_block(next_block); + + claim_specific_nfts(&mut app, &module, STAKER, &["3".to_string()])?; + let owner = query_nft_owner(&app, &nft, "3")?; + assert_eq!(owner, STAKER.to_string()); + + // unstake legacy + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "5".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + // unstake non-legacy + unstake_nfts(&mut app, &module, STAKER, &["1"])?; + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![ + NftClaim { + token_id: "5".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + }, + NftClaim { + token_id: "1".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + } + ] + ); + + app.update_block(next_block); + + // both claims should be ready to claim + claim_legacy_nfts(&mut app, &module, STAKER)?; + claim_nfts(&mut app, &module, STAKER)?; + + let owner = query_nft_owner(&app, &nft, "1")?; + assert_eq!(owner, STAKER.to_string()); + let owner = query_nft_owner(&app, &nft, "5")?; + assert_eq!(owner, STAKER.to_string()); + + // no claims left + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!(claims.nft_claims, vec![]); + + Ok(()) +} + // I can list all of the currently staked NFTs for an address. #[test] fn test_list_staked_nfts() -> anyhow::Result<()> { From 195da149921bc6472a702799edb2899054a146f9 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Thu, 31 Oct 2024 18:46:55 -0400 Subject: [PATCH 4/6] pr review fixes --- .../schema/dao-voting-cw721-staked.json | 49 +++++++++++++--- .../dao-voting-cw721-staked/src/contract.rs | 58 +++++++++---------- .../voting/dao-voting-cw721-staked/src/msg.rs | 17 ++++-- .../src/testing/execute.rs | 10 ++-- .../schema/dao-voting-onft-staked.json | 49 +++++++++++++--- .../dao-voting-onft-staked/src/contract.rs | 58 +++++++++---------- .../voting/dao-voting-onft-staked/src/msg.rs | 17 ++++-- .../src/testing/execute.rs | 10 ++-- packages/cw721-controllers/src/nft_claim.rs | 35 ++++++----- 9 files changed, 191 insertions(+), 112 deletions(-) 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 0be42988b..731433436 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 @@ -265,7 +265,7 @@ "additionalProperties": false }, { - "description": "Claim NFTs that have been unstaked for the specified duration. If none are provided, it attempts to claim all legacy claims. If token IDs are provided, only those are claimed. If an empty vector is provided, it attempts to claim all non-legacy claims.", + "description": "Claim NFTs that have been unstaked for the specified duration.", "type": "object", "required": [ "claim_nfts" @@ -273,15 +273,12 @@ "properties": { "claim_nfts": { "type": "object", + "required": [ + "type" + ], "properties": { - "token_ids": { - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + "type": { + "$ref": "#/definitions/ClaimType" } }, "additionalProperties": false @@ -440,6 +437,40 @@ "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" }, + "ClaimType": { + "oneOf": [ + { + "description": "Claims all legacy claims.", + "type": "string", + "enum": [ + "legacy" + ] + }, + { + "description": "Claims all non-legacy claims.", + "type": "string", + "enum": [ + "all" + ] + }, + { + "description": "Claims specific non-legacy NFTs.", + "type": "object", + "required": [ + "specific" + ], + "properties": { + "specific": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, "Cw721ReceiveMsg": { "description": "Cw721ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", "type": "object", diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index ff8444cbe..493225702 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -17,7 +17,7 @@ use dao_voting::threshold::{ ActiveThresholdResponse, }; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; +use crate::msg::{ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; use crate::state::{ register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, INITIAL_NFTS, LEGACY_NFT_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, @@ -205,7 +205,7 @@ pub fn execute( match msg { ExecuteMsg::ReceiveNft(msg) => execute_stake(deps, env, info, msg), ExecuteMsg::Unstake { token_ids } => execute_unstake(deps, env, info, token_ids), - ExecuteMsg::ClaimNfts { token_ids } => execute_claim_nfts(deps, env, info, token_ids), + ExecuteMsg::ClaimNfts { r#type } => execute_claim_nfts(deps, env, info, r#type), 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), @@ -337,47 +337,43 @@ pub fn execute_claim_nfts( deps: DepsMut, env: Env, info: MessageInfo, - token_ids: Option>, + claim_type: ClaimType, ) -> Result { - let token_ids = match token_ids { + let token_ids = match claim_type { // attempt to claim all legacy NFTs - None => { - // claim all legacy NFTs - let legacy_nfts = - LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; + ClaimType::Legacy => { + LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)? + } + // attempt to claim all non-legacy NFTs + ClaimType::All => { + let token_ids = NFT_CLAIMS + .query_claims(deps.as_ref(), &info.sender, None, None)? + .nft_claims + .into_iter() + .filter(|nft| nft.release_at.is_expired(&env.block)) + .map(|nft| nft.token_id) + .collect::>(); - if legacy_nfts.is_empty() { - return Err(ContractError::NothingToClaim {}); + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; } - legacy_nfts + token_ids } - // attempt to claim non-legacy NFTs - Some(token_ids) => { - let token_ids = if token_ids.is_empty() { - // query all NFT claims if none are provided - NFT_CLAIMS - .query_claims(deps.as_ref(), &info.sender, None, None)? - .nft_claims - .into_iter() - .filter(|nft| nft.release_at.is_expired(&env.block)) - .map(|nft| nft.token_id) - .collect::>() - } else { - // use provided NFTs if any - token_ids - }; - - if token_ids.is_empty() { - return Err(ContractError::NothingToClaim {}); + // attempt to claim specific non-legacy NFTs + ClaimType::Specific(token_ids) => { + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; } - NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; - token_ids } }; + if token_ids.is_empty() { + return Err(ContractError::NothingToClaim {}); + } + let config = CONFIG.load(deps.storage)?; let msgs = token_ids diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 8643d6ddc..6722d0262 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -54,11 +54,8 @@ pub enum ExecuteMsg { /// sender. token_ids must have unique values and have non-zero /// length. Unstake { token_ids: Vec }, - /// Claim NFTs that have been unstaked for the specified duration. If none - /// are provided, it attempts to claim all legacy claims. If token IDs are - /// provided, only those are claimed. If an empty vector is provided, it - /// attempts to claim all non-legacy claims. - ClaimNfts { token_ids: Option> }, + /// Claim NFTs that have been unstaked for the specified duration. + ClaimNfts { r#type: ClaimType }, /// Updates the contract configuration, namely unstaking duration. /// Only callable by the DAO that initialized this voting contract. UpdateConfig { duration: Option }, @@ -75,6 +72,16 @@ pub enum ExecuteMsg { }, } +#[cw_serde] +pub enum ClaimType { + /// Claims all legacy claims. + Legacy, + /// Claims all non-legacy claims. + All, + /// Claims specific non-legacy NFTs. + Specific(Vec), +} + #[active_query] #[voting_module_query] #[cw_serde] 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 a80b0b90c..acfbeea1c 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs @@ -5,7 +5,7 @@ use cw_multi_test::{App, AppResponse, Executor}; use anyhow::Result as AnyResult; use cw_utils::Duration; -use crate::msg::ExecuteMsg; +use crate::msg::{ClaimType, ExecuteMsg}; // Shorthand for an unchecked address. macro_rules! addr { @@ -111,7 +111,7 @@ pub fn claim_nfts(app: &mut App, module: &Addr, sender: &str) -> AnyResult AnyResul app.execute_contract( addr!(sender), module.clone(), - &ExecuteMsg::ClaimNfts { token_ids: None }, + &ExecuteMsg::ClaimNfts { + r#type: ClaimType::Legacy, + }, &[], ) } diff --git a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json index 03e221792..f462e41c0 100644 --- a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json +++ b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json @@ -274,7 +274,7 @@ "additionalProperties": false }, { - "description": "Claim NFTs that have been unstaked for the specified duration. If none are provided, it attempts to claim all legacy claims. If token IDs are provided, only those are claimed. If an empty vector is provided, it attempts to claim all non-legacy claims.", + "description": "Claim NFTs that have been unstaked for the specified duration.", "type": "object", "required": [ "claim_nfts" @@ -282,15 +282,12 @@ "properties": { "claim_nfts": { "type": "object", + "required": [ + "type" + ], "properties": { - "token_ids": { - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + "type": { + "$ref": "#/definitions/ClaimType" } }, "additionalProperties": false @@ -445,6 +442,40 @@ } ] }, + "ClaimType": { + "oneOf": [ + { + "description": "Claims all legacy claims.", + "type": "string", + "enum": [ + "legacy" + ] + }, + { + "description": "Claims all non-legacy claims.", + "type": "string", + "enum": [ + "all" + ] + }, + { + "description": "Claims specific non-legacy NFTs.", + "type": "object", + "required": [ + "specific" + ], + "properties": { + "specific": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "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" diff --git a/contracts/voting/dao-voting-onft-staked/src/contract.rs b/contracts/voting/dao-voting-onft-staked/src/contract.rs index 01efa76dd..9bb5f5e0b 100644 --- a/contracts/voting/dao-voting-onft-staked/src/contract.rs +++ b/contracts/voting/dao-voting-onft-staked/src/contract.rs @@ -15,7 +15,7 @@ use dao_voting::threshold::{ ActiveThresholdResponse, }; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, OnftCollection, QueryMsg}; +use crate::msg::{ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, OnftCollection, QueryMsg}; use crate::omniflix::{get_onft_transfer_msg, query_onft_owner, query_onft_supply}; use crate::state::{ register_staked_nfts, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, @@ -98,7 +98,7 @@ pub fn execute( recipient, } => execute_cancel_stake(deps, env, info, token_ids, recipient), ExecuteMsg::Unstake { token_ids } => execute_unstake(deps, env, info, token_ids), - ExecuteMsg::ClaimNfts { token_ids } => execute_claim_nfts(deps, env, info, token_ids), + ExecuteMsg::ClaimNfts { r#type } => execute_claim_nfts(deps, env, info, r#type), 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), @@ -394,47 +394,43 @@ pub fn execute_claim_nfts( deps: DepsMut, env: Env, info: MessageInfo, - token_ids: Option>, + claim_type: ClaimType, ) -> Result { - let token_ids = match token_ids { + let token_ids = match claim_type { // attempt to claim all legacy NFTs - None => { - // claim all legacy NFTs - let legacy_nfts = - LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; + ClaimType::Legacy => { + LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)? + } + // attempt to claim all non-legacy NFTs + ClaimType::All => { + let token_ids = NFT_CLAIMS + .query_claims(deps.as_ref(), &info.sender, None, None)? + .nft_claims + .into_iter() + .filter(|nft| nft.release_at.is_expired(&env.block)) + .map(|nft| nft.token_id) + .collect::>(); - if legacy_nfts.is_empty() { - return Err(ContractError::NothingToClaim {}); + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; } - legacy_nfts + token_ids } - // attempt to claim non-legacy NFTs - Some(token_ids) => { - let token_ids = if token_ids.is_empty() { - // query all NFT claims if none are provided - NFT_CLAIMS - .query_claims(deps.as_ref(), &info.sender, None, None)? - .nft_claims - .into_iter() - .filter(|nft| nft.release_at.is_expired(&env.block)) - .map(|nft| nft.token_id) - .collect::>() - } else { - // use provided NFTs if any - token_ids - }; - - if token_ids.is_empty() { - return Err(ContractError::NothingToClaim {}); + // attempt to claim specific non-legacy NFTs + ClaimType::Specific(token_ids) => { + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; } - NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; - token_ids } }; + if token_ids.is_empty() { + return Err(ContractError::NothingToClaim {}); + } + let config = CONFIG.load(deps.storage)?; let msgs = token_ids diff --git a/contracts/voting/dao-voting-onft-staked/src/msg.rs b/contracts/voting/dao-voting-onft-staked/src/msg.rs index 0567aa93d..49de8c410 100644 --- a/contracts/voting/dao-voting-onft-staked/src/msg.rs +++ b/contracts/voting/dao-voting-onft-staked/src/msg.rs @@ -75,11 +75,8 @@ pub enum ExecuteMsg { /// Unstakes the specified token_ids on behalf of the sender. token_ids must /// have unique values and have non-zero length. Unstake { token_ids: Vec }, - /// Claim NFTs that have been unstaked for the specified duration. If none - /// are provided, it attempts to claim all legacy claims. If token IDs are - /// provided, only those are claimed. If an empty vector is provided, it - /// attempts to claim all non-legacy claims. - ClaimNfts { token_ids: Option> }, + /// Claim NFTs that have been unstaked for the specified duration. + ClaimNfts { r#type: ClaimType }, /// Updates the contract configuration, namely unstaking duration. Only /// callable by the DAO that initialized this voting contract. UpdateConfig { duration: Option }, @@ -96,6 +93,16 @@ pub enum ExecuteMsg { }, } +#[cw_serde] +pub enum ClaimType { + /// Claims all legacy claims. + Legacy, + /// Claims all non-legacy claims. + All, + /// Claims specific non-legacy NFTs. + Specific(Vec), +} + #[active_query] #[voting_module_query] #[cw_serde] diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs index 2936f8fb7..cff409ccf 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs @@ -1,4 +1,4 @@ -use crate::msg::ExecuteMsg; +use crate::msg::{ClaimType, ExecuteMsg}; use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw_multi_test::AppResponse; @@ -199,7 +199,7 @@ pub fn claim_nfts(app: &mut OmniflixApp, module: &Addr, sender: &str) -> AnyResu addr!(sender), module.clone(), &ExecuteMsg::ClaimNfts { - token_ids: Some(vec![]), + r#type: ClaimType::All, }, &[], ) @@ -215,7 +215,7 @@ pub fn claim_specific_nfts( addr!(sender), module.clone(), &ExecuteMsg::ClaimNfts { - token_ids: Some(token_ids.to_vec()), + r#type: ClaimType::Specific(token_ids.to_vec()), }, &[], ) @@ -229,7 +229,9 @@ pub fn claim_legacy_nfts( app.execute_contract( addr!(sender), module.clone(), - &ExecuteMsg::ClaimNfts { token_ids: None }, + &ExecuteMsg::ClaimNfts { + r#type: ClaimType::Legacy, + }, &[], ) } diff --git a/packages/cw721-controllers/src/nft_claim.rs b/packages/cw721-controllers/src/nft_claim.rs index f2fe60b9e..ca1c186fe 100644 --- a/packages/cw721-controllers/src/nft_claim.rs +++ b/packages/cw721-controllers/src/nft_claim.rs @@ -12,8 +12,8 @@ pub enum NftClaimError { #[error("NFT claim not found for {token_id}")] NotFound { token_id: String }, - #[error("One or more NFTs is not ready to be claimed")] - NotReady {}, + #[error("NFT with ID {token_id} is not ready to be claimed")] + NotReady { token_id: String }, } #[cw_serde] @@ -80,12 +80,15 @@ impl<'a> NftClaims<'a> { .map(|token_id| -> Result<(), NftClaimError> { match self.0.may_load(storage, (addr, token_id)) { Ok(Some(expiration)) => { - // if claim is expired, continue + // if claim is expired, remove it and continue if expiration.is_expired(block) { + self.0.remove(storage, (addr, token_id)); Ok(()) } else { // if claim is not expired, error - Err(NftClaimError::NotReady {}) + Err(NftClaimError::NotReady { + token_id: token_id.to_string(), + }) } } // if claim is not found, error @@ -95,14 +98,8 @@ impl<'a> NftClaims<'a> { Err(e) => Err(e.into()), } }) - .collect::, NftClaimError>>()?; - - // remove all now that we've confirmed they're mature - token_ids - .iter() - .for_each(|token_id| self.0.remove(storage, (addr, token_id))); - - Ok(()) + .collect::, NftClaimError>>() + .map(|_| ()) } pub fn query_claims( @@ -120,7 +117,12 @@ impl<'a> NftClaims<'a> { .prefix(address) .range(deps.storage, start, None, Order::Ascending) .take(limit) - .map(|item| item.map(|(token_id, v)| NftClaim::new(token_id, v))) + .map(|item| { + item.map(|(token_id, release_at)| NftClaim { + token_id, + release_at, + }) + }) .collect::>>()?; Ok(NftClaimsResponse { nft_claims }) @@ -314,7 +316,12 @@ mod test { &env.block, ) .unwrap_err(); - assert_eq!(error, NftClaimError::NotReady {}); + assert_eq!( + error, + NftClaimError::NotReady { + token_id: TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() + } + ); let saved_claims = claims .0 From 84a74c2a59e51037231026a5a723b01d0ab69e64 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Thu, 31 Oct 2024 18:51:42 -0400 Subject: [PATCH 5/6] fixed integration test --- ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c49cca134..390407979 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 @@ -127,7 +127,7 @@ pub fn claim_nfts(chain: &mut Chain, sender: &SigningKey) { CONTRACT_NAME, "claim_nfts", &module::msg::ExecuteMsg::ClaimNfts { - token_ids: Some(vec![]), + r#type: module::msg::ClaimType::All, }, sender, vec![], From 4c4eab568c86d5ae419ea25be2cf8e15fa9ae23f Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Fri, 1 Nov 2024 13:28:36 -0400 Subject: [PATCH 6/6] added legacy flag to NFT claims response --- .../schema/dao-voting-cw721-staked.json | 13 ++++++- .../dao-voting-cw721-staked/src/contract.rs | 22 +++++++++--- .../voting/dao-voting-cw721-staked/src/msg.rs | 19 ++++++++-- .../src/testing/queries.rs | 6 ++-- .../src/testing/tests.rs | 35 ++++++++++++------- .../schema/dao-voting-onft-staked.json | 13 ++++++- .../dao-voting-onft-staked/src/contract.rs | 22 +++++++++--- .../voting/dao-voting-onft-staked/src/msg.rs | 19 ++++++++-- .../src/testing/queries.rs | 6 ++-- .../src/testing/tests.rs | 33 ++++++++++------- packages/cw721-controllers/src/lib.rs | 2 +- packages/cw721-controllers/src/nft_claim.rs | 26 +++++--------- 12 files changed, 154 insertions(+), 62 deletions(-) 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 731433436..05661b18a 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 @@ -1019,14 +1019,25 @@ "NftClaim": { "type": "object", "required": [ + "legacy", "release_at", "token_id" ], "properties": { + "legacy": { + "description": "Whether the claim is a legacy claim.", + "type": "boolean" + }, "release_at": { - "$ref": "#/definitions/Expiration" + "description": "The expiration time of the claim.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] }, "token_id": { + "description": "The token ID of the NFT being claimed.", "type": "string" } }, diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index 493225702..53f574c24 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -17,7 +17,10 @@ use dao_voting::threshold::{ ActiveThresholdResponse, }; -use crate::msg::{ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; +use crate::msg::{ + ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, NftClaim, NftClaimsResponse, NftContract, + QueryMsg, +}; use crate::state::{ register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, INITIAL_NFTS, LEGACY_NFT_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, @@ -348,7 +351,6 @@ pub fn execute_claim_nfts( ClaimType::All => { let token_ids = NFT_CLAIMS .query_claims(deps.as_ref(), &info.sender, None, None)? - .nft_claims .into_iter() .filter(|nft| nft.release_at.is_expired(&env.block)) .map(|nft| nft.token_id) @@ -648,18 +650,28 @@ pub fn query_nft_claims( .query_claims(deps, &addr)? .nft_claims .into_iter() - .map(|c| cw721_controllers::NftClaim::new(c.token_id, c.release_at)) + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: true, + }) .collect::>(); // paginate all new claims let claims = NFT_CLAIMS .query_claims(deps, &addr, start_after.as_ref(), limit)? - .nft_claims; + .into_iter() + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: false, + }) + .collect::>(); // combine legacy and new claims let nft_claims = legacy_claims.into_iter().chain(claims).collect(); - to_json_binary(&cw721_controllers::NftClaimsResponse { nft_claims }) + to_json_binary(&NftClaimsResponse { nft_claims }) } pub fn query_hooks(deps: Deps) -> StdResult { diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 6722d0262..62ca2efd4 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Binary; -use cw721::Cw721ReceiveMsg; +use cw721::{Cw721ReceiveMsg, Expiration}; use cw_utils::Duration; use dao_dao_macros::{active_query, voting_module_query}; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; @@ -89,7 +89,7 @@ pub enum ClaimType { pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, - #[returns(::cw721_controllers::NftClaimsResponse)] + #[returns(NftClaimsResponse)] NftClaims { address: String, start_after: Option, @@ -108,5 +108,20 @@ pub enum QueryMsg { ActiveThreshold {}, } +#[cw_serde] +pub struct NftClaimsResponse { + pub nft_claims: Vec, +} + +#[cw_serde] +pub struct NftClaim { + /// The token ID of the NFT being claimed. + pub token_id: String, + /// The expiration time of the claim. + pub release_at: Expiration, + /// Whether the claim is a legacy claim. + pub legacy: bool, +} + #[cw_serde] pub struct MigrateMsg {} diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs index 1284fc5c6..8807538b5 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs @@ -1,12 +1,14 @@ use cosmwasm_std::{Addr, StdResult, Uint128}; -use cw721_controllers::NftClaimsResponse; use cw_controllers::HooksResponse; use cw_multi_test::App; use dao_interface::voting::{ InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; -use crate::{msg::QueryMsg, state::Config}; +use crate::{ + msg::{NftClaimsResponse, QueryMsg}, + state::Config, +}; pub fn query_config(app: &App, module: &Addr) -> StdResult { let config = app.wrap().query_wasm_smart(module, &QueryMsg::Config {})?; 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 7b690933a..619aee4ab 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/tests.rs @@ -4,7 +4,6 @@ use cosmwasm_std::{ to_json_binary, to_json_vec, Addr, Coin, Decimal, Empty, Storage, Uint128, WasmMsg, }; use cw721_base::msg::{ExecuteMsg as Cw721ExecuteMsg, InstantiateMsg as Cw721InstantiateMsg}; -use cw721_controllers::{NftClaim, NftClaimsResponse}; use cw_multi_test::{next_block, App, BankSudo, Executor, SudoMsg}; use cw_storage_plus::Map; use cw_utils::Duration; @@ -17,7 +16,9 @@ use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; use crate::testing::execute::{claim_legacy_nfts, claim_specific_nfts}; use crate::{ contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}, + msg::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, NftClaim, NftClaimsResponse, NftContract, QueryMsg, + }, testing::{ execute::{ claim_nfts, mint_and_stake_nft, mint_nft, stake_nft, unstake_nfts, update_config, @@ -196,7 +197,8 @@ fn test_update_config() -> anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -218,7 +220,8 @@ fn test_update_config() -> anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -233,11 +236,13 @@ fn test_update_config() -> anyhow::Result<()> { nft_claims: vec![ NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }, NftClaim { token_id: "2".to_string(), - release_at: Duration::Time(1).after(&app.block_info()) + release_at: Duration::Time(1).after(&app.block_info()), + legacy: false, } ] } @@ -288,7 +293,8 @@ fn test_claims() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "2".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -353,7 +359,8 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "4".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, }] ); @@ -382,7 +389,8 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "2".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -411,7 +419,8 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "3".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -440,11 +449,13 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { vec![ NftClaim { token_id: "5".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, }, NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, } ] ); diff --git a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json index f462e41c0..1aec3afc9 100644 --- a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json +++ b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json @@ -999,14 +999,25 @@ "NftClaim": { "type": "object", "required": [ + "legacy", "release_at", "token_id" ], "properties": { + "legacy": { + "description": "Whether the claim is a legacy claim.", + "type": "boolean" + }, "release_at": { - "$ref": "#/definitions/Expiration" + "description": "The expiration time of the claim.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] }, "token_id": { + "description": "The token ID of the NFT being claimed.", "type": "string" } }, diff --git a/contracts/voting/dao-voting-onft-staked/src/contract.rs b/contracts/voting/dao-voting-onft-staked/src/contract.rs index 9bb5f5e0b..2ac9b87ba 100644 --- a/contracts/voting/dao-voting-onft-staked/src/contract.rs +++ b/contracts/voting/dao-voting-onft-staked/src/contract.rs @@ -15,7 +15,10 @@ use dao_voting::threshold::{ ActiveThresholdResponse, }; -use crate::msg::{ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, OnftCollection, QueryMsg}; +use crate::msg::{ + ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, NftClaim, NftClaimsResponse, OnftCollection, + QueryMsg, +}; use crate::omniflix::{get_onft_transfer_msg, query_onft_owner, query_onft_supply}; use crate::state::{ register_staked_nfts, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, @@ -405,7 +408,6 @@ pub fn execute_claim_nfts( ClaimType::All => { let token_ids = NFT_CLAIMS .query_claims(deps.as_ref(), &info.sender, None, None)? - .nft_claims .into_iter() .filter(|nft| nft.release_at.is_expired(&env.block)) .map(|nft| nft.token_id) @@ -694,18 +696,28 @@ pub fn query_nft_claims( .query_claims(deps, &addr)? .nft_claims .into_iter() - .map(|c| cw721_controllers::NftClaim::new(c.token_id, c.release_at)) + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: true, + }) .collect::>(); // paginate all new claims let claims = NFT_CLAIMS .query_claims(deps, &addr, start_after.as_ref(), limit)? - .nft_claims; + .into_iter() + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: false, + }) + .collect::>(); // combine legacy and new claims let nft_claims = legacy_claims.into_iter().chain(claims).collect(); - to_json_binary(&cw721_controllers::NftClaimsResponse { nft_claims }) + to_json_binary(&NftClaimsResponse { nft_claims }) } pub fn query_hooks(deps: Deps) -> StdResult { diff --git a/contracts/voting/dao-voting-onft-staked/src/msg.rs b/contracts/voting/dao-voting-onft-staked/src/msg.rs index 49de8c410..0970ea266 100644 --- a/contracts/voting/dao-voting-onft-staked/src/msg.rs +++ b/contracts/voting/dao-voting-onft-staked/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cw_utils::Duration; +use cw_utils::{Duration, Expiration}; use dao_dao_macros::{active_query, voting_module_query}; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; @@ -110,7 +110,7 @@ pub enum ClaimType { pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, - #[returns(::cw721_controllers::NftClaimsResponse)] + #[returns(NftClaimsResponse)] NftClaims { address: String, start_after: Option, @@ -129,5 +129,20 @@ pub enum QueryMsg { ActiveThreshold {}, } +#[cw_serde] +pub struct NftClaimsResponse { + pub nft_claims: Vec, +} + +#[cw_serde] +pub struct NftClaim { + /// The token ID of the NFT being claimed. + pub token_id: String, + /// The expiration time of the claim. + pub release_at: Expiration, + /// Whether the claim is a legacy claim. + pub legacy: bool, +} + #[cw_serde] pub struct MigrateMsg {} diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs b/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs index 5cd6cb45d..50789e2b5 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs @@ -1,12 +1,14 @@ use cosmwasm_std::{Addr, StdResult, Uint128}; -use cw721_controllers::NftClaimsResponse; use cw_controllers::HooksResponse; use dao_interface::voting::{ InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; use omniflix_std::types::omniflix::onft::v1beta1::{QueryOnftRequest, QueryOnftResponse}; -use crate::{msg::QueryMsg, state::Config}; +use crate::{ + msg::{NftClaimsResponse, QueryMsg}, + state::Config, +}; use super::app::OmniflixApp; diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs b/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs index 2f120403a..eb65099fa 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/tests.rs @@ -1,7 +1,6 @@ use cosmwasm_std::storage_keys::to_length_prefixed_nested; use cosmwasm_std::testing::{mock_dependencies, mock_env}; use cosmwasm_std::{to_json_vec, Addr, Decimal, Storage, Uint128}; -use cw721_controllers::{NftClaim, NftClaimsResponse}; use cw_multi_test::{next_block, Executor}; use cw_storage_plus::Map; use cw_utils::Duration; @@ -17,7 +16,7 @@ use crate::testing::queries::query_dao; use crate::testing::DAO; use crate::{ contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftClaim, NftClaimsResponse, QueryMsg}, testing::{ execute::{ claim_nfts, mint_and_stake_nft, mint_nft, stake_nft, unstake_nfts, update_config, @@ -188,7 +187,8 @@ fn test_update_config() -> anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -210,7 +210,8 @@ fn test_update_config() -> anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -225,11 +226,13 @@ fn test_update_config() -> anyhow::Result<()> { nft_claims: vec![ NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }, NftClaim { token_id: "2".to_string(), - release_at: Duration::Time(1).after(&app.block_info()) + release_at: Duration::Time(1).after(&app.block_info()), + legacy: false, } ] } @@ -281,7 +284,8 @@ fn test_claims() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "2".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -347,7 +351,8 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "4".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, }] ); @@ -376,7 +381,8 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "2".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -405,7 +411,8 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "3".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -434,11 +441,13 @@ pub fn test_legacy_claims_work() -> anyhow::Result<()> { vec![ NftClaim { token_id: "5".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, }, NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, } ] ); diff --git a/packages/cw721-controllers/src/lib.rs b/packages/cw721-controllers/src/lib.rs index 66823d06a..97c53ffe0 100644 --- a/packages/cw721-controllers/src/lib.rs +++ b/packages/cw721-controllers/src/lib.rs @@ -2,4 +2,4 @@ mod nft_claim; -pub use nft_claim::{NftClaim, NftClaimError, NftClaims, NftClaimsResponse}; +pub use nft_claim::{NftClaim, NftClaimError, NftClaims}; diff --git a/packages/cw721-controllers/src/nft_claim.rs b/packages/cw721-controllers/src/nft_claim.rs index ca1c186fe..1feb4f3a1 100644 --- a/packages/cw721-controllers/src/nft_claim.rs +++ b/packages/cw721-controllers/src/nft_claim.rs @@ -16,11 +16,6 @@ pub enum NftClaimError { NotReady { token_id: String }, } -#[cw_serde] -pub struct NftClaimsResponse { - pub nft_claims: Vec, -} - #[cw_serde] pub struct NftClaim { pub token_id: String, @@ -108,12 +103,11 @@ impl<'a> NftClaims<'a> { address: &Addr, start_after: Option<&String>, limit: Option, - ) -> StdResult { + ) -> StdResult> { let limit = limit.map(|l| l as usize).unwrap_or(usize::MAX); let start = start_after.map(Bound::<&String>::exclusive); - let nft_claims = self - .0 + self.0 .prefix(address) .range(deps.storage, start, None, Order::Ascending) .take(limit) @@ -123,9 +117,7 @@ impl<'a> NftClaims<'a> { release_at, }) }) - .collect::>>()?; - - Ok(NftClaimsResponse { nft_claims }) + .collect() } } @@ -457,7 +449,7 @@ mod test { .collect::>>() .unwrap(); - assert_eq!(queried_claims.nft_claims, saved_claims); + assert_eq!(queried_claims, saved_claims); } #[test] @@ -481,7 +473,7 @@ mod test { .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, None) .unwrap(); assert_eq!( - queried_claims.nft_claims, + queried_claims, vec![ NftClaim::new(TEST_BAYC_TOKEN_ID.to_string(), Expiration::AtHeight(10)), NftClaim::new( @@ -495,7 +487,7 @@ mod test { .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, Some(1)) .unwrap(); assert_eq!( - queried_claims.nft_claims, + queried_claims, vec![NftClaim::new( TEST_BAYC_TOKEN_ID.to_string(), Expiration::AtHeight(10) @@ -511,7 +503,7 @@ mod test { ) .unwrap(); assert_eq!( - queried_claims.nft_claims, + queried_claims, vec![NftClaim::new( TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), Expiration::AtHeight(10) @@ -526,7 +518,7 @@ mod test { None, ) .unwrap(); - assert_eq!(queried_claims.nft_claims.len(), 0); + assert_eq!(queried_claims.len(), 0); } #[test] @@ -547,6 +539,6 @@ mod test { .query_claims(deps.as_ref(), &Addr::unchecked("addr2"), None, None) .unwrap(); - assert_eq!(queried_claims.nft_claims.len(), 0); + assert_eq!(queried_claims.len(), 0); } }