From 5463638c0ccedb25f3ea58f5d929ea051475d942 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Sun, 12 Jan 2025 22:38:21 +0300 Subject: [PATCH] Add optional mint count limit for whitelist stages --- .../src/contract.rs | 56 ++++++++++++++--- .../src/error.rs | 6 ++ .../src/state.rs | 3 + .../src/contract.rs | 60 +++++++++++++++---- .../open-edition-minter-wl-flex/src/error.rs | 6 ++ .../open-edition-minter-wl-flex/src/state.rs | 3 + .../open-edition-minter/src/contract.rs | 59 +++++++++++++++--- .../minters/open-edition-minter/src/error.rs | 6 ++ .../minters/open-edition-minter/src/state.rs | 3 + .../vending-minter-featured/src/contract.rs | 57 +++++++++++++++--- .../vending-minter-featured/src/error.rs | 6 ++ .../vending-minter-featured/src/state.rs | 4 +- .../src/contract.rs | 54 ++++++++++++++--- .../src/error.rs | 6 ++ .../src/state.rs | 3 + .../vending-minter-merkle-wl/src/contract.rs | 54 ++++++++++++++--- .../vending-minter-merkle-wl/src/error.rs | 6 ++ .../vending-minter-merkle-wl/src/state.rs | 3 + .../src/contract.rs | 56 ++++++++++++++--- .../src/error.rs | 6 ++ .../src/state.rs | 3 + .../vending-minter-wl-flex/src/contract.rs | 57 +++++++++++++++--- .../vending-minter-wl-flex/src/error.rs | 6 ++ .../vending-minter-wl-flex/src/state.rs | 3 + .../minters/vending-minter/src/contract.rs | 57 +++++++++++++++--- contracts/minters/vending-minter/src/error.rs | 6 ++ contracts/minters/vending-minter/src/state.rs | 3 + .../tiered-whitelist-flex/src/contract.rs | 9 ++- .../tiered-whitelist-flex/src/helpers.rs | 36 +++++++++++ .../tiered-whitelist-flex/src/msg.rs | 1 + .../tiered-whitelist-flex/src/state.rs | 1 + .../src/contract.rs | 7 ++- .../src/helpers/utils.rs | 51 ++++++++++++++++ .../tiered-whitelist-merkletree/src/msg.rs | 1 + .../tiered-whitelist-merkletree/src/state.rs | 1 + .../tiered-whitelist/src/contract.rs | 9 ++- .../tiered-whitelist/src/helpers.rs | 51 ++++++++++++++++ .../whitelists/tiered-whitelist/src/msg.rs | 1 + .../whitelists/tiered-whitelist/src/state.rs | 1 + 39 files changed, 680 insertions(+), 81 deletions(-) diff --git a/contracts/minters/open-edition-minter-merkle-wl/src/contract.rs b/contracts/minters/open-edition-minter-merkle-wl/src/contract.rs index 886f78249..5ffcb093c 100644 --- a/contracts/minters/open-edition-minter-merkle-wl/src/contract.rs +++ b/contracts/minters/open-edition-minter-merkle-wl/src/contract.rs @@ -6,8 +6,9 @@ use crate::msg::{ }; use crate::state::{ increment_token_index, Config, ConfigExtension, CONFIG, MINTABLE_NUM_TOKENS, MINTER_ADDRS, - SG721_ADDRESS, STATUS, TOTAL_MINT_COUNT, WHITELIST_FS_MINTER_ADDRS, WHITELIST_MINTER_ADDRS, - WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + SG721_ADDRESS, STATUS, TOTAL_MINT_COUNT, WHITELIST_FS_MINTER_ADDRS, WHITELIST_FS_MINT_COUNT, + WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_SS_MINT_COUNT, + WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -26,7 +27,7 @@ use sg2::query::Sg2QueryMsg; use sg4::{MinterConfig, Status, StatusResponse, SudoMsg}; use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg}; use sg_std::StargazeMsgWrapper; -use tiered_whitelist_merkletree::msg::QueryMsg as TieredWhitelistQueryMsg; +use tiered_whitelist_merkletree::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use url::Url; use whitelist_mtree::msg::{ ConfigResponse as WhitelistConfigResponse, HasMemberResponse, QueryMsg as WhitelistQueryMsg, @@ -436,15 +437,36 @@ fn is_public_mint( } // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; let max_count = match allocation { Some(allocation) => allocation, None => wl_config.per_address_limit, }; - if wl_mint_count >= max_count { + if wl_mint_count.0 >= max_count { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -1004,9 +1026,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/open-edition-minter-merkle-wl/src/error.rs b/contracts/minters/open-edition-minter-merkle-wl/src/error.rs index 89af45fc0..81ff705e5 100644 --- a/contracts/minters/open-edition-minter-merkle-wl/src/error.rs +++ b/contracts/minters/open-edition-minter-merkle-wl/src/error.rs @@ -31,6 +31,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -121,6 +124,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/open-edition-minter-merkle-wl/src/state.rs b/contracts/minters/open-edition-minter-merkle-wl/src/state.rs index 448f0c4db..29472ae55 100644 --- a/contracts/minters/open-edition-minter-merkle-wl/src/state.rs +++ b/contracts/minters/open-edition-minter-merkle-wl/src/state.rs @@ -27,6 +27,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); /// This keeps track of the mint count pub const TOTAL_MINT_COUNT: Item = Item::new("total_mint_count"); diff --git a/contracts/minters/open-edition-minter-wl-flex/src/contract.rs b/contracts/minters/open-edition-minter-wl-flex/src/contract.rs index 753cb1c96..8bcbca275 100644 --- a/contracts/minters/open-edition-minter-wl-flex/src/contract.rs +++ b/contracts/minters/open-edition-minter-wl-flex/src/contract.rs @@ -6,8 +6,9 @@ use crate::msg::{ }; use crate::state::{ increment_token_index, Config, ConfigExtension, CONFIG, MINTABLE_NUM_TOKENS, MINTER_ADDRS, - SG721_ADDRESS, STATUS, TOTAL_MINT_COUNT, WHITELIST_FS_MINTER_ADDRS, WHITELIST_MINTER_ADDRS, - WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + SG721_ADDRESS, STATUS, TOTAL_MINT_COUNT, WHITELIST_FS_MINTER_ADDRS, WHITELIST_FS_MINT_COUNT, + WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_SS_MINT_COUNT, + WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -26,7 +27,7 @@ use sg2::query::Sg2QueryMsg; use sg4::{MinterConfig, Status, StatusResponse, SudoMsg}; use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg}; use sg_std::StargazeMsgWrapper; -use sg_tiered_whitelist_flex::msg::QueryMsg as TieredWhitelistQueryMsg; +use sg_tiered_whitelist_flex::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use sg_whitelist_flex::msg::{ ConfigResponse as WhitelistConfigResponse, HasMemberResponse, Member, QueryMsg as WhitelistQueryMsg, @@ -416,24 +417,45 @@ fn is_public_mint(deps: Deps, info: &MessageInfo) -> Result } // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; if config.extension.num_tokens.is_none() { ensure!( - wl_mint_count < config.extension.per_address_limit, + wl_mint_count.0 < config.extension.per_address_limit, ContractError::MaxPerAddressLimitExceeded {} ); } let wl_limit: Member = deps.querier.query_wasm_smart( - whitelist, + whitelist.clone(), &WhitelistQueryMsg::Member { member: info.sender.to_string(), }, )?; - if wl_mint_count >= wl_limit.mint_count { + if wl_mint_count.0 >= wl_limit.mint_count { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -504,9 +526,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/open-edition-minter-wl-flex/src/error.rs b/contracts/minters/open-edition-minter-wl-flex/src/error.rs index 95d267a53..421bac1e7 100644 --- a/contracts/minters/open-edition-minter-wl-flex/src/error.rs +++ b/contracts/minters/open-edition-minter-wl-flex/src/error.rs @@ -31,6 +31,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -118,6 +121,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/open-edition-minter-wl-flex/src/state.rs b/contracts/minters/open-edition-minter-wl-flex/src/state.rs index 7134fa49b..6d4dc37a7 100644 --- a/contracts/minters/open-edition-minter-wl-flex/src/state.rs +++ b/contracts/minters/open-edition-minter-wl-flex/src/state.rs @@ -28,6 +28,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); /// This keeps track of the mint count pub const TOTAL_MINT_COUNT: Item = Item::new("total_mint_count"); diff --git a/contracts/minters/open-edition-minter/src/contract.rs b/contracts/minters/open-edition-minter/src/contract.rs index 9965615f5..4117a4da5 100644 --- a/contracts/minters/open-edition-minter/src/contract.rs +++ b/contracts/minters/open-edition-minter/src/contract.rs @@ -6,8 +6,9 @@ use crate::msg::{ }; use crate::state::{ increment_token_index, Config, ConfigExtension, CONFIG, MINTABLE_NUM_TOKENS, MINTER_ADDRS, - SG721_ADDRESS, STATUS, TOTAL_MINT_COUNT, WHITELIST_FS_MINTER_ADDRS, WHITELIST_MINTER_ADDRS, - WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + SG721_ADDRESS, STATUS, TOTAL_MINT_COUNT, WHITELIST_FS_MINTER_ADDRS, WHITELIST_FS_MINT_COUNT, + WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_SS_MINT_COUNT, + WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -26,7 +27,7 @@ use sg2::query::Sg2QueryMsg; use sg4::{MinterConfig, Status, StatusResponse, SudoMsg}; use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg}; use sg_std::StargazeMsgWrapper; -use sg_tiered_whitelist::msg::QueryMsg as TieredWhitelistQueryMsg; +use sg_tiered_whitelist::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use sg_whitelist::msg::{ ConfigResponse as WhitelistConfigResponse, HasMemberResponse, QueryMsg as WhitelistQueryMsg, }; @@ -410,12 +411,34 @@ fn is_public_mint(deps: Deps, info: &MessageInfo) -> Result }); } - // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; - if wl_mint_count >= wl_config.per_address_limit { + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; + + // Check if whitelist per address limit is reached + if wl_mint_count.0 >= wl_config.per_address_limit { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -489,9 +512,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/open-edition-minter/src/error.rs b/contracts/minters/open-edition-minter/src/error.rs index 95d267a53..421bac1e7 100644 --- a/contracts/minters/open-edition-minter/src/error.rs +++ b/contracts/minters/open-edition-minter/src/error.rs @@ -31,6 +31,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -118,6 +121,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/open-edition-minter/src/state.rs b/contracts/minters/open-edition-minter/src/state.rs index e3647b812..a40e816a2 100644 --- a/contracts/minters/open-edition-minter/src/state.rs +++ b/contracts/minters/open-edition-minter/src/state.rs @@ -27,6 +27,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); /// This keeps track of the mint count pub const TOTAL_MINT_COUNT: Item = Item::new("total_mint_count"); diff --git a/contracts/minters/vending-minter-featured/src/contract.rs b/contracts/minters/vending-minter-featured/src/contract.rs index de0cea1d6..050dea81c 100644 --- a/contracts/minters/vending-minter-featured/src/contract.rs +++ b/contracts/minters/vending-minter-featured/src/contract.rs @@ -6,7 +6,8 @@ use crate::msg::{ use crate::state::{ Config, ConfigExtension, CONFIG, LAST_DISCOUNT_TIME, MINTABLE_NUM_TOKENS, MINTABLE_TOKEN_POSITIONS, MINTER_ADDRS, SG721_ADDRESS, STATUS, WHITELIST_FS_MINTER_ADDRS, - WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + WHITELIST_FS_MINT_COUNT, WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, + WHITELIST_SS_MINT_COUNT, WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; use crate::validation::{check_dynamic_per_address_limit, get_three_percent_of_tokens}; #[cfg(not(feature = "library"))] @@ -27,7 +28,7 @@ use sg2::query::Sg2QueryMsg; use sg4::{MinterConfig, Status, StatusResponse, SudoMsg}; use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg}; use sg_std::{StargazeMsgWrapper, GENESIS_MINT_START_TIME}; -use sg_tiered_whitelist::msg::QueryMsg as TieredWhitelistQueryMsg; +use sg_tiered_whitelist::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use sg_whitelist::msg::{ ConfigResponse as WhitelistConfigResponse, HasMemberResponse, QueryMsg as WhitelistQueryMsg, }; @@ -550,12 +551,34 @@ fn is_public_mint(deps: Deps, info: &MessageInfo) -> Result }); } - // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; - if wl_mint_count >= wl_config.per_address_limit { + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; + + // Check if whitelist per address limit is reached + if wl_mint_count.0 >= wl_config.per_address_limit { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -1164,9 +1187,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/vending-minter-featured/src/error.rs b/contracts/minters/vending-minter-featured/src/error.rs index cfdd78c85..5b67d20ec 100644 --- a/contracts/minters/vending-minter-featured/src/error.rs +++ b/contracts/minters/vending-minter-featured/src/error.rs @@ -26,6 +26,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -101,6 +104,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/vending-minter-featured/src/state.rs b/contracts/minters/vending-minter-featured/src/state.rs index d790dd2de..20588053c 100644 --- a/contracts/minters/vending-minter-featured/src/state.rs +++ b/contracts/minters/vending-minter-featured/src/state.rs @@ -28,7 +28,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); - +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); pub const LAST_DISCOUNT_TIME: Item = Item::new("last_discount_time"); /// Holds the status of the minter. Can be changed with on-chain governance proposals. diff --git a/contracts/minters/vending-minter-merkle-wl-featured/src/contract.rs b/contracts/minters/vending-minter-merkle-wl-featured/src/contract.rs index 3781b5573..ea5946310 100644 --- a/contracts/minters/vending-minter-merkle-wl-featured/src/contract.rs +++ b/contracts/minters/vending-minter-merkle-wl-featured/src/contract.rs @@ -6,7 +6,8 @@ use crate::msg::{ use crate::state::{ Config, ConfigExtension, CONFIG, LAST_DISCOUNT_TIME, MINTABLE_NUM_TOKENS, MINTABLE_TOKEN_POSITIONS, MINTER_ADDRS, SG721_ADDRESS, STATUS, WHITELIST_FS_MINTER_ADDRS, - WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + WHITELIST_FS_MINT_COUNT, WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, + WHITELIST_SS_MINT_COUNT, WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; use crate::validation::{check_dynamic_per_address_limit, get_three_percent_of_tokens}; #[cfg(not(feature = "library"))] @@ -33,7 +34,7 @@ use sg_whitelist::msg::{ use sha2::{Digest, Sha256}; use shuffle::{fy::FisherYates, shuffler::Shuffler}; use std::convert::TryInto; -use tiered_whitelist_merkletree::msg::QueryMsg as TieredWhitelistQueryMsg; +use tiered_whitelist_merkletree::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use url::Url; use vending_factory::msg::{ParamsResponse, VendingMinterCreateMsg}; use vending_factory::state::VendingMinterParams; @@ -582,15 +583,36 @@ fn is_public_mint( } // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; let max_count = match allocation { Some(allocation) => allocation, None => wl_config.per_address_limit, }; - if wl_mint_count >= max_count { + if wl_mint_count.0 >= max_count { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -1203,9 +1225,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/vending-minter-merkle-wl-featured/src/error.rs b/contracts/minters/vending-minter-merkle-wl-featured/src/error.rs index cfdd78c85..5b67d20ec 100644 --- a/contracts/minters/vending-minter-merkle-wl-featured/src/error.rs +++ b/contracts/minters/vending-minter-merkle-wl-featured/src/error.rs @@ -26,6 +26,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -101,6 +104,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/vending-minter-merkle-wl-featured/src/state.rs b/contracts/minters/vending-minter-merkle-wl-featured/src/state.rs index c838c8866..20588053c 100644 --- a/contracts/minters/vending-minter-merkle-wl-featured/src/state.rs +++ b/contracts/minters/vending-minter-merkle-wl-featured/src/state.rs @@ -28,6 +28,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); pub const LAST_DISCOUNT_TIME: Item = Item::new("last_discount_time"); /// Holds the status of the minter. Can be changed with on-chain governance proposals. diff --git a/contracts/minters/vending-minter-merkle-wl/src/contract.rs b/contracts/minters/vending-minter-merkle-wl/src/contract.rs index ef9b72300..0f497cc36 100644 --- a/contracts/minters/vending-minter-merkle-wl/src/contract.rs +++ b/contracts/minters/vending-minter-merkle-wl/src/contract.rs @@ -6,7 +6,8 @@ use crate::msg::{ use crate::state::{ Config, ConfigExtension, CONFIG, LAST_DISCOUNT_TIME, MINTABLE_NUM_TOKENS, MINTABLE_TOKEN_POSITIONS, MINTER_ADDRS, SG721_ADDRESS, STATUS, WHITELIST_FS_MINTER_ADDRS, - WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + WHITELIST_FS_MINT_COUNT, WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, + WHITELIST_SS_MINT_COUNT, WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; use crate::validation::{check_dynamic_per_address_limit, get_three_percent_of_tokens}; #[cfg(not(feature = "library"))] @@ -33,7 +34,7 @@ use sg_whitelist::msg::{ use sha2::{Digest, Sha256}; use shuffle::{fy::FisherYates, shuffler::Shuffler}; use std::convert::TryInto; -use tiered_whitelist_merkletree::msg::QueryMsg as TieredWhitelistQueryMsg; +use tiered_whitelist_merkletree::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use url::Url; use vending_factory::msg::{ParamsResponse, VendingMinterCreateMsg}; use vending_factory::state::VendingMinterParams; @@ -582,15 +583,36 @@ fn is_public_mint( } // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; let max_count = match allocation { Some(allocation) => allocation, None => wl_config.per_address_limit, }; - if wl_mint_count >= max_count { + if wl_mint_count.0 >= max_count { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -1202,9 +1224,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/vending-minter-merkle-wl/src/error.rs b/contracts/minters/vending-minter-merkle-wl/src/error.rs index cfdd78c85..5b67d20ec 100644 --- a/contracts/minters/vending-minter-merkle-wl/src/error.rs +++ b/contracts/minters/vending-minter-merkle-wl/src/error.rs @@ -26,6 +26,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -101,6 +104,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/vending-minter-merkle-wl/src/state.rs b/contracts/minters/vending-minter-merkle-wl/src/state.rs index c838c8866..20588053c 100644 --- a/contracts/minters/vending-minter-merkle-wl/src/state.rs +++ b/contracts/minters/vending-minter-merkle-wl/src/state.rs @@ -28,6 +28,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); pub const LAST_DISCOUNT_TIME: Item = Item::new("last_discount_time"); /// Holds the status of the minter. Can be changed with on-chain governance proposals. diff --git a/contracts/minters/vending-minter-wl-flex-featured/src/contract.rs b/contracts/minters/vending-minter-wl-flex-featured/src/contract.rs index e4525002f..e7873ad69 100644 --- a/contracts/minters/vending-minter-wl-flex-featured/src/contract.rs +++ b/contracts/minters/vending-minter-wl-flex-featured/src/contract.rs @@ -6,7 +6,8 @@ use crate::msg::{ use crate::state::{ Config, ConfigExtension, CONFIG, LAST_DISCOUNT_TIME, MINTABLE_NUM_TOKENS, MINTABLE_TOKEN_POSITIONS, MINTER_ADDRS, SG721_ADDRESS, STATUS, WHITELIST_FS_MINTER_ADDRS, - WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + WHITELIST_FS_MINT_COUNT, WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, + WHITELIST_SS_MINT_COUNT, WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -26,7 +27,7 @@ use sg2::query::Sg2QueryMsg; use sg4::{MinterConfig, Status, StatusResponse, SudoMsg}; use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg}; use sg_std::{StargazeMsgWrapper, GENESIS_MINT_START_TIME}; -use sg_tiered_whitelist_flex::msg::QueryMsg as TieredWhitelistQueryMsg; +use sg_tiered_whitelist_flex::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use sg_whitelist_flex::msg::{ ConfigResponse as WhitelistConfigResponse, HasMemberResponse, Member, QueryMsg as WhitelistQueryMsg, @@ -534,17 +535,38 @@ fn is_public_mint(deps: Deps, info: &MessageInfo) -> Result } // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; let wl_limit: Member = deps.querier.query_wasm_smart( - whitelist, + whitelist.clone(), &WhitelistQueryMsg::Member { member: info.sender.to_string(), }, )?; - if wl_mint_count >= wl_limit.mint_count { + if wl_mint_count.0 >= wl_limit.mint_count { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -1137,9 +1159,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/vending-minter-wl-flex-featured/src/error.rs b/contracts/minters/vending-minter-wl-flex-featured/src/error.rs index cfdd78c85..5b67d20ec 100644 --- a/contracts/minters/vending-minter-wl-flex-featured/src/error.rs +++ b/contracts/minters/vending-minter-wl-flex-featured/src/error.rs @@ -26,6 +26,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -101,6 +104,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/vending-minter-wl-flex-featured/src/state.rs b/contracts/minters/vending-minter-wl-flex-featured/src/state.rs index c838c8866..20588053c 100644 --- a/contracts/minters/vending-minter-wl-flex-featured/src/state.rs +++ b/contracts/minters/vending-minter-wl-flex-featured/src/state.rs @@ -28,6 +28,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); pub const LAST_DISCOUNT_TIME: Item = Item::new("last_discount_time"); /// Holds the status of the minter. Can be changed with on-chain governance proposals. diff --git a/contracts/minters/vending-minter-wl-flex/src/contract.rs b/contracts/minters/vending-minter-wl-flex/src/contract.rs index 4d0d1cc05..546c23cb0 100644 --- a/contracts/minters/vending-minter-wl-flex/src/contract.rs +++ b/contracts/minters/vending-minter-wl-flex/src/contract.rs @@ -6,7 +6,8 @@ use crate::msg::{ use crate::state::{ Config, ConfigExtension, CONFIG, LAST_DISCOUNT_TIME, MINTABLE_NUM_TOKENS, MINTABLE_TOKEN_POSITIONS, MINTER_ADDRS, SG721_ADDRESS, STATUS, WHITELIST_FS_MINTER_ADDRS, - WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + WHITELIST_FS_MINT_COUNT, WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, + WHITELIST_SS_MINT_COUNT, WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -26,7 +27,7 @@ use sg2::query::Sg2QueryMsg; use sg4::{MinterConfig, Status, StatusResponse, SudoMsg}; use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg}; use sg_std::{StargazeMsgWrapper, GENESIS_MINT_START_TIME}; -use sg_tiered_whitelist_flex::msg::QueryMsg as TieredWhitelistQueryMsg; +use sg_tiered_whitelist_flex::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use sg_whitelist_flex::msg::{ ConfigResponse as WhitelistConfigResponse, HasMemberResponse, Member, QueryMsg as WhitelistQueryMsg, @@ -532,18 +533,38 @@ fn is_public_mint(deps: Deps, info: &MessageInfo) -> Result }); } - // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; let wl_limit: Member = deps.querier.query_wasm_smart( - whitelist, + whitelist.clone(), &WhitelistQueryMsg::Member { member: info.sender.to_string(), }, )?; - if wl_mint_count >= wl_limit.mint_count { + if wl_mint_count.0 >= wl_limit.mint_count { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -1136,9 +1157,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/vending-minter-wl-flex/src/error.rs b/contracts/minters/vending-minter-wl-flex/src/error.rs index cfdd78c85..5b67d20ec 100644 --- a/contracts/minters/vending-minter-wl-flex/src/error.rs +++ b/contracts/minters/vending-minter-wl-flex/src/error.rs @@ -26,6 +26,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -101,6 +104,9 @@ pub enum ContractError { #[error("Max minting limit per address exceeded")] MaxPerAddressLimitExceeded {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("Token id: {token_id} already sold")] TokenIdAlreadySold { token_id: u32 }, diff --git a/contracts/minters/vending-minter-wl-flex/src/state.rs b/contracts/minters/vending-minter-wl-flex/src/state.rs index c838c8866..20588053c 100644 --- a/contracts/minters/vending-minter-wl-flex/src/state.rs +++ b/contracts/minters/vending-minter-wl-flex/src/state.rs @@ -28,6 +28,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); pub const LAST_DISCOUNT_TIME: Item = Item::new("last_discount_time"); /// Holds the status of the minter. Can be changed with on-chain governance proposals. diff --git a/contracts/minters/vending-minter/src/contract.rs b/contracts/minters/vending-minter/src/contract.rs index 2be1f57f3..3efdb083c 100644 --- a/contracts/minters/vending-minter/src/contract.rs +++ b/contracts/minters/vending-minter/src/contract.rs @@ -6,7 +6,8 @@ use crate::msg::{ use crate::state::{ Config, ConfigExtension, CONFIG, LAST_DISCOUNT_TIME, MINTABLE_NUM_TOKENS, MINTABLE_TOKEN_POSITIONS, MINTER_ADDRS, SG721_ADDRESS, STATUS, WHITELIST_FS_MINTER_ADDRS, - WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, WHITELIST_TS_MINTER_ADDRS, + WHITELIST_FS_MINT_COUNT, WHITELIST_MINTER_ADDRS, WHITELIST_SS_MINTER_ADDRS, + WHITELIST_SS_MINT_COUNT, WHITELIST_TS_MINTER_ADDRS, WHITELIST_TS_MINT_COUNT, }; use crate::validation::{check_dynamic_per_address_limit, get_three_percent_of_tokens}; #[cfg(not(feature = "library"))] @@ -27,7 +28,7 @@ use sg2::query::Sg2QueryMsg; use sg4::{MinterConfig, Status, StatusResponse, SudoMsg}; use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg}; use sg_std::{StargazeMsgWrapper, GENESIS_MINT_START_TIME}; -use sg_tiered_whitelist::msg::QueryMsg as TieredWhitelistQueryMsg; +use sg_tiered_whitelist::msg::{QueryMsg as TieredWhitelistQueryMsg, StageResponse}; use sg_whitelist::msg::{ ConfigResponse as WhitelistConfigResponse, HasMemberResponse, QueryMsg as WhitelistQueryMsg, }; @@ -548,12 +549,34 @@ fn is_public_mint(deps: Deps, info: &MessageInfo) -> Result }); } - // Check wl per address limit - let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?.0; - if wl_mint_count >= wl_config.per_address_limit { + let wl_mint_count = whitelist_mint_count(deps, info, whitelist.clone())?; + + // Check if whitelist per address limit is reached + if wl_mint_count.0 >= wl_config.per_address_limit { return Err(ContractError::MaxPerAddressLimitExceeded {}); } + // Check if whitelist stage mint count limit is reached + if wl_mint_count.1 && wl_mint_count.2.is_some() { + let active_stage: StageResponse = deps.querier.query_wasm_smart( + whitelist.clone(), + &TieredWhitelistQueryMsg::Stage { + stage_id: wl_mint_count.2.unwrap() - 1, + }, + )?; + if active_stage.stage.mint_count_limit.is_some() { + let stage_mint_count = match wl_mint_count.2.unwrap() { + 1 => WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 2 => WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + 3 => WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0), + _ => return Err(ContractError::InvalidStageID {}), + }; + if stage_mint_count >= active_stage.stage.mint_count_limit.unwrap() { + return Err(ContractError::WhitelistMintCountLimitReached {}); + } + } + } + Ok(false) } @@ -1162,9 +1185,27 @@ fn save_whitelist_mint_count( ) -> StdResult<()> { if is_tiered_whitelist & stage_id.is_some() { match stage_id { - Some(1) => WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(2) => WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), - Some(3) => WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count), + Some(1) => { + let _ = WHITELIST_FS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_fs_mint_count = + WHITELIST_FS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_fs_mint_count += 1; + WHITELIST_FS_MINT_COUNT.save(deps.storage, &wl_fs_mint_count) + } + Some(2) => { + let _ = WHITELIST_SS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ss_mint_count = + WHITELIST_SS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ss_mint_count += 1; + WHITELIST_SS_MINT_COUNT.save(deps.storage, &wl_ss_mint_count) + } + Some(3) => { + let _ = WHITELIST_TS_MINTER_ADDRS.save(deps.storage, &info.sender, &count); + let mut wl_ts_mint_count = + WHITELIST_TS_MINT_COUNT.may_load(deps.storage)?.unwrap_or(0); + wl_ts_mint_count += 1; + WHITELIST_TS_MINT_COUNT.save(deps.storage, &wl_ts_mint_count) + } _ => Err(StdError::generic_err("Invalid stage ID")), } } else { diff --git a/contracts/minters/vending-minter/src/error.rs b/contracts/minters/vending-minter/src/error.rs index cfdd78c85..3d700f260 100644 --- a/contracts/minters/vending-minter/src/error.rs +++ b/contracts/minters/vending-minter/src/error.rs @@ -26,6 +26,9 @@ pub enum ContractError { #[error("Invalid reply ID")] InvalidReplyID {}, + #[error("Invalid stage ID")] + InvalidStageID {}, + #[error("Not enough funds sent")] NotEnoughFunds {}, @@ -77,6 +80,9 @@ pub enum ContractError { #[error("WhitelistAlreadyStarted")] WhitelistAlreadyStarted {}, + #[error("WhitelistMintCountLimitReached")] + WhitelistMintCountLimitReached {}, + #[error("InvalidStartTime {0} < {1}")] InvalidStartTime(Timestamp, Timestamp), diff --git a/contracts/minters/vending-minter/src/state.rs b/contracts/minters/vending-minter/src/state.rs index 395709578..1c5ce00b4 100644 --- a/contracts/minters/vending-minter/src/state.rs +++ b/contracts/minters/vending-minter/src/state.rs @@ -29,6 +29,9 @@ pub const WHITELIST_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlma"); pub const WHITELIST_FS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlfsma"); pub const WHITELIST_SS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wlssma"); pub const WHITELIST_TS_MINTER_ADDRS: Map<&Addr, u32> = Map::new("wltsma"); +pub const WHITELIST_FS_MINT_COUNT: Item = Item::new("wlfsmc"); +pub const WHITELIST_SS_MINT_COUNT: Item = Item::new("wlssmc"); +pub const WHITELIST_TS_MINT_COUNT: Item = Item::new("wltsmc"); pub const LAST_DISCOUNT_TIME: Item = Item::new("last_discount_time"); diff --git a/contracts/whitelists/tiered-whitelist-flex/src/contract.rs b/contracts/whitelists/tiered-whitelist-flex/src/contract.rs index df40bfeda..1e4c9edca 100644 --- a/contracts/whitelists/tiered-whitelist-flex/src/contract.rs +++ b/contracts/whitelists/tiered-whitelist-flex/src/contract.rs @@ -3,7 +3,9 @@ use crate::admin::{ }; use crate::error::ContractError; use crate::helpers::validators::map_validate; -use crate::helpers::{fetch_active_stage, fetch_active_stage_index, validate_stages}; +use crate::helpers::{ + fetch_active_stage, fetch_active_stage_index, validate_stages, validate_update, +}; use crate::msg::{ AddMembersMsg, AllStageMemberInfoResponse, ConfigResponse, ExecuteMsg, HasEndedResponse, HasMemberResponse, HasStartedResponse, InstantiateMsg, IsActiveResponse, Member, @@ -168,9 +170,12 @@ pub fn execute_update_stage_config( mint_price: msg .mint_price .unwrap_or(config.stages[stage_id].clone().mint_price), + mint_count_limit: msg + .mint_count_limit + .unwrap_or(config.stages[stage_id].clone().mint_count_limit), }; config.stages[stage_id] = updated_stage.clone(); - validate_stages(&env, &config.stages)?; + validate_update(&env, &config.stages)?; CONFIG.save(deps.storage, &config)?; Ok(Response::new() diff --git a/contracts/whitelists/tiered-whitelist-flex/src/helpers.rs b/contracts/whitelists/tiered-whitelist-flex/src/helpers.rs index 6495402f0..2820a9451 100644 --- a/contracts/whitelists/tiered-whitelist-flex/src/helpers.rs +++ b/contracts/whitelists/tiered-whitelist-flex/src/helpers.rs @@ -64,3 +64,39 @@ pub fn validate_stages(env: &Env, stages: &[Stage]) -> Result<(), ContractError> } Ok(()) } + +pub fn validate_update(_env: &Env, stages: &[Stage]) -> Result<(), ContractError> { + ensure!( + !stages.is_empty(), + StdError::generic_err("Must have at least one stage") + ); + ensure!( + stages.len() < 4, + StdError::generic_err("Cannot have more than 3 stages") + ); + + // Check stages have matching mint price denoms + let mint_denom = stages[0].mint_price.denom.clone(); + ensure!( + stages + .iter() + .all(|stage| stage.mint_price.denom == mint_denom), + StdError::generic_err("All stages must have the same mint price denom") + ); + + for i in 0..stages.len() { + let stage = &stages[i]; + ensure!( + stage.start_time < stage.end_time, + StdError::generic_err("Stage start time must be before the end time") + ); + + for other_stage in stages.iter().skip(i + 1) { + ensure!( + other_stage.start_time >= stage.end_time, + StdError::generic_err("Stages must have non-overlapping times") + ); + } + } + Ok(()) +} diff --git a/contracts/whitelists/tiered-whitelist-flex/src/msg.rs b/contracts/whitelists/tiered-whitelist-flex/src/msg.rs index b760ae4a8..c9ab0b825 100644 --- a/contracts/whitelists/tiered-whitelist-flex/src/msg.rs +++ b/contracts/whitelists/tiered-whitelist-flex/src/msg.rs @@ -64,6 +64,7 @@ pub struct UpdateStageConfigMsg { pub start_time: Option, pub end_time: Option, pub mint_price: Option, + pub mint_count_limit: Option>, } #[cw_serde] diff --git a/contracts/whitelists/tiered-whitelist-flex/src/state.rs b/contracts/whitelists/tiered-whitelist-flex/src/state.rs index 3cdd99568..fdffb5db8 100644 --- a/contracts/whitelists/tiered-whitelist-flex/src/state.rs +++ b/contracts/whitelists/tiered-whitelist-flex/src/state.rs @@ -8,6 +8,7 @@ pub struct Stage { pub start_time: Timestamp, pub end_time: Timestamp, pub mint_price: Coin, + pub mint_count_limit: Option, } #[cw_serde] diff --git a/contracts/whitelists/tiered-whitelist-merkletree/src/contract.rs b/contracts/whitelists/tiered-whitelist-merkletree/src/contract.rs index e329e7718..4806b8aa4 100644 --- a/contracts/whitelists/tiered-whitelist-merkletree/src/contract.rs +++ b/contracts/whitelists/tiered-whitelist-merkletree/src/contract.rs @@ -4,7 +4,7 @@ use crate::admin::{ use crate::error::ContractError; use crate::helpers::crypto::{string_to_byte_slice, valid_hash_string, verify_merkle_root}; use crate::helpers::utils::{ - fetch_active_stage, fetch_active_stage_index, validate_stages, verify_tree_uri, + fetch_active_stage, fetch_active_stage_index, validate_stages, validate_update, verify_tree_uri, }; use crate::helpers::validators::map_validate; use crate::msg::{ @@ -182,9 +182,12 @@ pub fn execute_update_stage_config( per_address_limit: msg .per_address_limit .unwrap_or(config.stages[stage_id].clone().per_address_limit), + mint_count_limit: msg + .mint_count_limit + .unwrap_or(config.stages[stage_id].clone().mint_count_limit), }; config.stages[stage_id] = updated_stage.clone(); - validate_stages(&env, &config.stages)?; + validate_update(&env, &config.stages)?; CONFIG.save(deps.storage, &config)?; Ok(Response::new() diff --git a/contracts/whitelists/tiered-whitelist-merkletree/src/helpers/utils.rs b/contracts/whitelists/tiered-whitelist-merkletree/src/helpers/utils.rs index bc5fae524..e1b78565e 100644 --- a/contracts/whitelists/tiered-whitelist-merkletree/src/helpers/utils.rs +++ b/contracts/whitelists/tiered-whitelist-merkletree/src/helpers/utils.rs @@ -88,3 +88,54 @@ pub fn validate_stages(env: &Env, stages: &[Stage]) -> Result<(), ContractError> } Ok(()) } + +pub fn validate_update(_env: &Env, stages: &[Stage]) -> Result<(), ContractError> { + ensure!( + !stages.is_empty(), + StdError::generic_err("Must have at least one stage") + ); + ensure!( + stages.len() < 4, + StdError::generic_err("Cannot have more than 3 stages") + ); + + // Check per address limit is valid + if stages.iter().any(|stage| { + stage.per_address_limit == 0 || stage.per_address_limit > MAX_PER_ADDRESS_LIMIT + }) { + return Err(ContractError::InvalidPerAddressLimit { + max: MAX_PER_ADDRESS_LIMIT.to_string(), + got: stages + .iter() + .map(|s| s.per_address_limit) + .max() + .unwrap() + .to_string(), + }); + } + + // Check stages have matching mint price denoms + let mint_denom = stages[0].mint_price.denom.clone(); + ensure!( + stages + .iter() + .all(|stage| stage.mint_price.denom == mint_denom), + StdError::generic_err("All stages must have the same mint price denom") + ); + + for i in 0..stages.len() { + let stage = &stages[i]; + ensure!( + stage.start_time < stage.end_time, + StdError::generic_err("Stage start time must be before the end time") + ); + + for other_stage in stages.iter().skip(i + 1) { + ensure!( + other_stage.start_time >= stage.end_time, + StdError::generic_err("Stages must have non-overlapping times") + ); + } + } + Ok(()) +} diff --git a/contracts/whitelists/tiered-whitelist-merkletree/src/msg.rs b/contracts/whitelists/tiered-whitelist-merkletree/src/msg.rs index 96e37b3b6..7d199c300 100644 --- a/contracts/whitelists/tiered-whitelist-merkletree/src/msg.rs +++ b/contracts/whitelists/tiered-whitelist-merkletree/src/msg.rs @@ -38,6 +38,7 @@ pub struct UpdateStageConfigMsg { pub end_time: Option, pub mint_price: Option, pub per_address_limit: Option, + pub mint_count_limit: Option>, } #[cw_serde] diff --git a/contracts/whitelists/tiered-whitelist-merkletree/src/state.rs b/contracts/whitelists/tiered-whitelist-merkletree/src/state.rs index 716e59c23..de623ad03 100644 --- a/contracts/whitelists/tiered-whitelist-merkletree/src/state.rs +++ b/contracts/whitelists/tiered-whitelist-merkletree/src/state.rs @@ -9,6 +9,7 @@ pub struct Stage { pub end_time: Timestamp, pub mint_price: Coin, pub per_address_limit: u32, + pub mint_count_limit: Option, } #[cw_serde] pub struct Config { diff --git a/contracts/whitelists/tiered-whitelist/src/contract.rs b/contracts/whitelists/tiered-whitelist/src/contract.rs index 35e7163a8..32c784f1f 100644 --- a/contracts/whitelists/tiered-whitelist/src/contract.rs +++ b/contracts/whitelists/tiered-whitelist/src/contract.rs @@ -3,7 +3,9 @@ use crate::admin::{ }; use crate::error::ContractError; use crate::helpers::validators::map_validate; -use crate::helpers::{fetch_active_stage, fetch_active_stage_index, validate_stages}; +use crate::helpers::{ + fetch_active_stage, fetch_active_stage_index, validate_stages, validate_update, +}; use crate::msg::{ AddMembersMsg, AllStageMemberInfoResponse, ConfigResponse, ExecuteMsg, HasEndedResponse, HasMemberResponse, HasStartedResponse, InstantiateMsg, IsActiveResponse, MembersResponse, @@ -164,9 +166,12 @@ pub fn execute_update_stage_config( per_address_limit: msg .per_address_limit .unwrap_or(config.stages[stage_id].clone().per_address_limit), + mint_count_limit: msg + .mint_count_limit + .unwrap_or(config.stages[stage_id].clone().mint_count_limit), }; config.stages[stage_id] = updated_stage.clone(); - validate_stages(&env, &config.stages)?; + validate_update(&env, &config.stages)?; CONFIG.save(deps.storage, &config)?; Ok(Response::new() diff --git a/contracts/whitelists/tiered-whitelist/src/helpers.rs b/contracts/whitelists/tiered-whitelist/src/helpers.rs index 846d2fe04..8904631f5 100644 --- a/contracts/whitelists/tiered-whitelist/src/helpers.rs +++ b/contracts/whitelists/tiered-whitelist/src/helpers.rs @@ -80,3 +80,54 @@ pub fn validate_stages(env: &Env, stages: &[Stage]) -> Result<(), ContractError> } Ok(()) } + +pub fn validate_update(_env: &Env, stages: &[Stage]) -> Result<(), ContractError> { + ensure!( + !stages.is_empty(), + StdError::generic_err("Must have at least one stage") + ); + ensure!( + stages.len() < 4, + StdError::generic_err("Cannot have more than 3 stages") + ); + + // Check per address limit is valid + if stages.iter().any(|stage| { + stage.per_address_limit == 0 || stage.per_address_limit > MAX_PER_ADDRESS_LIMIT + }) { + return Err(ContractError::InvalidPerAddressLimit { + max: MAX_PER_ADDRESS_LIMIT.to_string(), + got: stages + .iter() + .map(|s| s.per_address_limit) + .max() + .unwrap() + .to_string(), + }); + } + + // Check stages have matching mint price denoms + let mint_denom = stages[0].mint_price.denom.clone(); + ensure!( + stages + .iter() + .all(|stage| stage.mint_price.denom == mint_denom), + StdError::generic_err("All stages must have the same mint price denom") + ); + + for i in 0..stages.len() { + let stage = &stages[i]; + ensure!( + stage.start_time < stage.end_time, + StdError::generic_err("Stage start time must be before the end time") + ); + + for other_stage in stages.iter().skip(i + 1) { + ensure!( + other_stage.start_time >= stage.end_time, + StdError::generic_err("Stages must have non-overlapping times") + ); + } + } + Ok(()) +} diff --git a/contracts/whitelists/tiered-whitelist/src/msg.rs b/contracts/whitelists/tiered-whitelist/src/msg.rs index c3e4f42b0..b4dbcfb6a 100644 --- a/contracts/whitelists/tiered-whitelist/src/msg.rs +++ b/contracts/whitelists/tiered-whitelist/src/msg.rs @@ -59,6 +59,7 @@ pub struct UpdateStageConfigMsg { pub end_time: Option, pub mint_price: Option, pub per_address_limit: Option, + pub mint_count_limit: Option>, } #[cw_serde] diff --git a/contracts/whitelists/tiered-whitelist/src/state.rs b/contracts/whitelists/tiered-whitelist/src/state.rs index b157e10f6..8c257f742 100644 --- a/contracts/whitelists/tiered-whitelist/src/state.rs +++ b/contracts/whitelists/tiered-whitelist/src/state.rs @@ -9,6 +9,7 @@ pub struct Stage { pub end_time: Timestamp, pub mint_price: Coin, pub per_address_limit: u32, + pub mint_count_limit: Option, } #[cw_serde]