Skip to content

Commit

Permalink
add support for multiple whitelist types
Browse files Browse the repository at this point in the history
  • Loading branch information
jason-c-child committed May 13, 2024
1 parent 3c94c42 commit ace3b83
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 36 deletions.
145 changes: 112 additions & 33 deletions contracts/name-minter/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ use sg_name::{Metadata, SgNameExecuteMsg};
use sg_name_common::{charge_fees, SECONDS_PER_YEAR};
use sg_name_minter::{Config, SudoParams, PUBLIC_MINT_START_TIME_IN_SECONDS};
use sg_std::{Response, SubMsg, NATIVE_DENOM};
use whitelist_updatable::helpers::WhitelistUpdatableContract;
use whitelist_updatable_flatrate::helpers::WhitelistUpdatableFlatrateContract;

use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg};
use crate::state::{
ADMIN, CONFIG, NAME_COLLECTION, NAME_MARKETPLACE, PAUSED, SUDO_PARAMS, WHITELISTS,
WhitelistContract, WhitelistContractType, ADMIN, CONFIG, NAME_COLLECTION, NAME_MARKETPLACE,
PAUSED, SUDO_PARAMS, WHITELISTS,
};

// version info for migration info
Expand Down Expand Up @@ -54,7 +56,10 @@ pub fn instantiate(
.whitelists
.iter()
.filter_map(|addr| api.addr_validate(addr).ok())
.map(WhitelistUpdatableFlatrateContract)
.map(|addr| WhitelistContract {
contract_type: WhitelistContractType::UpdatableFlatrateDiscount,
addr,
})
.collect::<Vec<_>>();

WHITELISTS.save(deps.storage, &lists)?;
Expand Down Expand Up @@ -99,6 +104,7 @@ pub fn instantiate(
verifier: msg.verifier,
base_init_msg: collection_init_msg,
};

let wasm_msg = WasmMsg::Instantiate {
code_id: msg.collection_code_id,
msg: to_json_binary(&name_collection_init_msg)?,
Expand Down Expand Up @@ -129,7 +135,10 @@ pub fn execute(
Ok(ADMIN.execute_update_admin(deps, info, maybe_addr(api, admin)?)?)
}
ExecuteMsg::Pause { pause } => execute_pause(deps, info, pause),
ExecuteMsg::AddWhitelist { address } => execute_add_whitelist(deps, info, address),
ExecuteMsg::AddWhitelist {
address,
whitelist_type,
} => execute_add_whitelist(deps, info, address, whitelist_type),
ExecuteMsg::RemoveWhitelist { address } => execute_remove_whitelist(deps, info, address),
ExecuteMsg::UpdateConfig { config } => execute_update_config(deps, info, env, config),
}
Expand Down Expand Up @@ -157,25 +166,63 @@ pub fn execute_mint_and_list(
// Assumes no duplicate addresses between whitelists
// Otherwise there will be edge cases with per addr limit between the whitelists

let list = whitelists.iter().find(|whitelist| {
whitelist
.includes(&deps.querier, sender.to_string())
.unwrap_or(false)
});
let list = whitelists
.iter()
.find(|whitelist| match whitelist.contract_type {
WhitelistContractType::UpdatableFlatrateDiscount => {
let contract = WhitelistUpdatableFlatrateContract(whitelist.addr.clone());
contract
.includes(&deps.querier, sender.to_string())
.unwrap_or(false)
}
WhitelistContractType::UpdatablePercentDiscount => {
let contract = WhitelistUpdatableContract(whitelist.addr.clone());
contract
.includes(&deps.querier, sender.to_string())
.unwrap_or(false)
}
});

// if not on any whitelist, check public mint start time
if list.is_none() && env.block.time < config.public_mint_start_time {
return Err(ContractError::MintingNotStarted {});
}

let discount = list
.map(|list| {
res.messages
.push(SubMsg::new(list.process_address(sender)?));
list.mint_discount_amount(&deps.querier)
})
.transpose()?
.unwrap_or(None);
for list in list.iter() {
match list.contract_type {
WhitelistContractType::UpdatableFlatrateDiscount => {
let contract = WhitelistUpdatableFlatrateContract(list.addr.clone());
res.messages
.push(SubMsg::new(contract.process_address(sender)?));
}
WhitelistContractType::UpdatablePercentDiscount => {
let contract = WhitelistUpdatableContract(list.addr.clone());
res.messages
.push(SubMsg::new(contract.process_address(sender)?));
}
}
}

let discount = list.map(|list| match list.contract_type {
WhitelistContractType::UpdatableFlatrateDiscount => {
let contract = WhitelistUpdatableFlatrateContract(list.addr.clone());
return Discount::Flatrate(
contract
.mint_discount_amount(&deps.querier)
.unwrap()
.unwrap(),
);
}
WhitelistContractType::UpdatablePercentDiscount => {
let contract = WhitelistUpdatableContract(list.addr.clone());
return Discount::Percent(
contract
.mint_discount_percent(&deps.querier)
.unwrap()
.unwrap(),
);
}
});

let price = validate_payment(name.len(), &info, params.base_price.u128(), discount)?;
if price.is_some() {
Expand Down Expand Up @@ -245,6 +292,7 @@ pub fn execute_add_whitelist(
deps: DepsMut,
info: MessageInfo,
address: String,
whitelist_type: String,
) -> Result<Response, ContractError> {
ADMIN.assert_admin(deps.as_ref(), &info.sender)?;

Expand All @@ -254,7 +302,21 @@ pub fn execute_add_whitelist(
.map(WhitelistUpdatableFlatrateContract)?;

let mut lists = WHITELISTS.load(deps.storage)?;
lists.push(whitelist);
match whitelist_type.as_str() {
"FlatrateDiscount" => {
lists.push(WhitelistContract {
contract_type: WhitelistContractType::UpdatableFlatrateDiscount,
addr: whitelist.addr(),
});
}
"PercentDiscount" => {
lists.push(WhitelistContract {
contract_type: WhitelistContractType::UpdatablePercentDiscount,
addr: whitelist.addr(),
});
}
_ => return Err(ContractError::InvalidWhitelistType {}),
}

WHITELISTS.save(deps.storage, &lists)?;

Expand All @@ -271,7 +333,7 @@ pub fn execute_remove_whitelist(

let whitelist = deps.api.addr_validate(&address)?;
let mut lists = WHITELISTS.load(deps.storage)?;
lists.retain(|addr| addr.addr() != whitelist);
lists.retain(|whitelist_contract| whitelist_contract.addr != whitelist);

WHITELISTS.save(deps.storage, &lists)?;

Expand Down Expand Up @@ -327,11 +389,16 @@ fn validate_name(name: &str, min: u32, max: u32) -> Result<(), ContractError> {
Ok(())
}

pub enum Discount {
Flatrate(u64),
Percent(Decimal),
}

fn validate_payment(
name_len: usize,
info: &MessageInfo,
base_price: u128,
discount: Option<u64>,
discount: Option<Discount>,
) -> Result<Option<Coin>, ContractError> {
// Because we know we are left with ASCII chars, a simple byte count is enough
let mut amount: Uint128 = (match name_len {
Expand All @@ -344,15 +411,22 @@ fn validate_payment(
})
.into();

if let Some(discount_value) = discount {
let discount_amount = Uint128::from(discount_value);
// TODO: should we handle the case where discount > amount (eg 1,000 discount but buying a 100 name)
if amount.ge(&discount_amount) {
amount = amount
.checked_sub(discount_amount)
.map_err(|_| StdError::generic_err("invalid discount amount"))?;
match discount {
Some(Discount::Flatrate(discount)) => {
let discount = Uint128::from(discount);
if amount.ge(&discount) {
amount = amount
.checked_sub(discount)
.map_err(|_| StdError::generic_err("invalid discount amount"))?;
}
}
Some(Discount::Percent(discount)) => {
amount = amount * (Decimal::one() - discount);
}
None => {
amount = amount;
}
}
}

if amount.is_zero() {
return Ok(None);
Expand Down Expand Up @@ -436,7 +510,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, Contra
mod tests {
use cosmwasm_std::{coin, Addr, MessageInfo};

use crate::contract::validate_name;
use crate::contract::{self, validate_name};

use super::validate_payment;

Expand Down Expand Up @@ -526,11 +600,16 @@ mod tests {
};
assert_eq!(
// we treat the discount as a flat amount given as 100.0
validate_payment(5, &info, base_price, Some(100),)
.unwrap()
.unwrap()
.amount
.u128(),
validate_payment(
5,
&info,
base_price,
Some(contract::Discount::Flatrate(100)),
)
.unwrap()
.unwrap()
.amount
.u128(),
base_price - 100
);
}
Expand Down
5 changes: 5 additions & 0 deletions contracts/name-minter/src/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ fn instantiate_contracts(
if let Some(admin) = admin {
let msg = ExecuteMsg::AddWhitelist {
address: wl.to_string(),
whitelist_type: "FlatrateDiscount".to_string()
};
let res = app.execute_contract(Addr::unchecked(admin), Addr::unchecked(minter), &msg, &[]);
assert!(res.is_ok());
Expand Down Expand Up @@ -2121,6 +2122,7 @@ mod whitelist {
let wl_count = whitelists.len();
let msg = ExecuteMsg::AddWhitelist {
address: "whitelist".to_string(),
whitelist_type: "FlatrateDiscount".to_string(),
};

let res = app.execute_contract(Addr::unchecked(ADMIN), Addr::unchecked(MINTER), &msg, &[]);
Expand Down Expand Up @@ -2166,6 +2168,7 @@ mod whitelist {
// add wl2 to minter
let msg = ExecuteMsg::AddWhitelist {
address: wl2.to_string(),
whitelist_type: "FlatrateDiscount".to_string(),
};
let res = app.execute_contract(
Addr::unchecked(ADMIN.to_string()),
Expand Down Expand Up @@ -2243,6 +2246,7 @@ mod whitelist {
// add wl2 to minter
let msg = ExecuteMsg::AddWhitelist {
address: wl2.to_string(),
whitelist_type: "FlatrateDiscount".to_string(),
};
let res = app.execute_contract(
Addr::unchecked(ADMIN.to_string()),
Expand Down Expand Up @@ -2277,6 +2281,7 @@ mod whitelist {

let msg = ExecuteMsg::AddWhitelist {
address: WHITELIST.to_string(),
whitelist_type: "FlatrateDiscount".to_string(),
};
let res = app.execute_contract(Addr::unchecked(ADMIN), Addr::unchecked(MINTER), &msg, &[]);
assert!(res.is_ok());
Expand Down
7 changes: 6 additions & 1 deletion contracts/name-minter/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Addr, Uint128};
use sg_name_minter::{Config, SudoParams};

use crate::state::{WhitelistContract, WhitelistContractType};

#[cw_serde]
pub struct InstantiateMsg {
/// Temporary admin for managing whitelists
Expand All @@ -27,7 +29,10 @@ pub enum ExecuteMsg {
/// Admin can pause minting during whitelist switching
Pause { pause: bool },
/// Add a whitelist address
AddWhitelist { address: String },
AddWhitelist {
address: String,
whitelist_type: String,
},
/// Remove a whitelist address
RemoveWhitelist { address: String },
/// Update config, only callable by admin
Expand Down
2 changes: 1 addition & 1 deletion contracts/name-minter/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {

fn query_whitelists(deps: Deps) -> StdResult<Vec<Addr>> {
let whitelists = WHITELISTS.load(deps.storage)?;
Ok(whitelists.iter().map(|w| w.addr()).collect())
Ok(whitelists.iter().map(|w| w.addr.clone()).collect())
}

fn query_collection(deps: Deps) -> StdResult<Addr> {
Expand Down
15 changes: 14 additions & 1 deletion contracts/name-minter/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,23 @@ use cosmwasm_std::Addr;
use cw_controllers::Admin;
use cw_storage_plus::Item;

use serde::{Deserialize, Serialize};
use sg_name_minter::{Config, SudoParams};

use whitelist_updatable_flatrate::helpers::WhitelistUpdatableFlatrateContract;

#[derive(Serialize, Deserialize)]
pub struct WhitelistContract {
pub contract_type: WhitelistContractType,
pub addr: Addr,
}

#[derive(Serialize, Deserialize, PartialEq)]
pub enum WhitelistContractType {
UpdatableFlatrateDiscount,
UpdatablePercentDiscount,
}

pub const SUDO_PARAMS: Item<SudoParams> = Item::new("params");

pub const NAME_COLLECTION: Item<Addr> = Item::new("name-collection");
Expand All @@ -15,7 +28,7 @@ pub const NAME_MARKETPLACE: Item<Addr> = Item::new("name-marketplace");
pub const ADMIN: Admin = Admin::new("admin");

/// Can only be updated by admin
pub const WHITELISTS: Item<Vec<WhitelistUpdatableFlatrateContract>> = Item::new("whitelists");
pub const WHITELISTS: Item<Vec<WhitelistContract>> = Item::new("whitelists");

/// Controls if minting is paused or not by admin
pub const PAUSED: Item<bool> = Item::new("paused");
Expand Down

0 comments on commit ace3b83

Please sign in to comment.