Skip to content

Commit

Permalink
add shuffle and purge_buckets
Browse files Browse the repository at this point in the history
  • Loading branch information
jhernandezb committed Dec 19, 2024
1 parent c57a45c commit 3e5688e
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 115 deletions.
154 changes: 44 additions & 110 deletions contracts/minters/vending-minter/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use cw721_base::Extension;
use cw_utils::{may_pay, maybe_addr, nonpayable, parse_reply_instantiate_data};

use semver::Version;
use sg1::distribute_mint_fees;
use sg1::{checked_fair_burn, distribute_mint_fees};
use sg2::query::Sg2QueryMsg;
use sg4::{MinterConfig, Status, StatusResponse, SudoMsg};
use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg};
Expand All @@ -31,7 +31,6 @@ use sg_whitelist::msg::{
ConfigResponse as WhitelistConfigResponse, HasMemberResponse, QueryMsg as WhitelistQueryMsg,
};
use sha2::{Digest, Sha256};

use url::Url;
use vending_factory::msg::{ParamsResponse, VendingMinterCreateMsg};
use vending_factory::state::VendingMinterParams;
Expand Down Expand Up @@ -342,42 +341,49 @@ pub fn execute_purge(
// Introduces another source of randomness to minting
// There's a fee because this action is expensive.
pub fn execute_shuffle(
_deps: DepsMut,
_env: Env,
deps: DepsMut,
env: Env,
info: MessageInfo,
) -> Result<Response, ContractError> {
let res = Response::new();

// let config = CONFIG.load(deps.storage)?;

// let factory: ParamsResponse = deps
// .querier
// .query_wasm_smart(config.factory, &Sg2QueryMsg::Params {})?;
// let factory_params = factory.params;

// // Check exact shuffle fee payment included in message
// checked_fair_burn(
// &info,
// factory_params.extension.shuffle_fee.amount.u128(),
// None,
// &mut res,
// )?;

// // Check not sold out
// let mintable_num_tokens = MINTABLE_NUM_TOKENS.load(deps.storage)?;
// if mintable_num_tokens == 0 {
// return Err(ContractError::SoldOut {});
// }

// // get positions and token_ids, then randomize token_ids and reassign positions
// let mut positions = vec![];
// let mut token_ids = vec![];
// for mapping in MINTABLE_TOKEN_POSITIONS.range(deps.storage, None, None, Order::Ascending) {
// let (position, token_id) = mapping?;
// positions.push(position);
// token_ids.push(token_id);
// }
let mut res = Response::new();

let config = CONFIG.load(deps.storage)?;

let factory: ParamsResponse = deps
.querier
.query_wasm_smart(config.factory, &Sg2QueryMsg::Params {})?;
let factory_params = factory.params;

// Check exact shuffle fee payment included in message
checked_fair_burn(
&info,
factory_params.extension.shuffle_fee.amount.u128(),
None,
&mut res,
)?;

// Check not sold out
let mintable_num_tokens = MINTABLE_NUM_TOKENS.load(deps.storage)?;
if mintable_num_tokens == 0 {
return Err(ContractError::SoldOut {});
}

let sha256 = Sha256::digest(
format!(
"{}{}{}{}{}",
info.sender,
env.block.height,
mintable_num_tokens,
env.block.time.nanos(),
env.transaction.map_or(0, |tx| tx.index),
)
.into_bytes(),
);
let randomness: [u8; 32] = sha256.to_vec()[0..32].try_into().map_err(|_| {
ContractError::Std(StdError::generic_err("Failed to convert sha256 to array"))
})?;

sg_minter_utils::shuffle(deps.storage, randomness)?;
Ok(res
.add_attribute("action", "shuffle")
.add_attribute("sender", info.sender))
Expand Down Expand Up @@ -750,70 +756,6 @@ fn _execute_mint(
))
}

// fn random_token_list(
// env: &Env,
// sender: Addr,
// mut tokens: Vec<u32>,
// ) -> Result<Vec<u32>, ContractError> {
// let tx_index = if let Some(tx) = &env.transaction {
// tx.index
// } else {
// 0
// };
// let sha256 = Sha256::digest(
// format!("{}{}{}{}", sender, env.block.height, tokens.len(), tx_index).into_bytes(),
// );
// // Cut first 16 bytes from 32 byte value
// let randomness: [u8; 16] = sha256.to_vec()[0..16].try_into().unwrap();
// let mut rng = Xoshiro128PlusPlus::from_seed(randomness);
// let mut shuffler = FisherYates::default();
// shuffler
// .shuffle(&mut tokens, &mut rng)
// .map_err(StdError::generic_err)?;
// Ok(tokens)
// }

// Does a baby shuffle, picking a token_id from the first or last 50 mintable positions.
// fn random_mintable_token_mapping(
// deps: Deps,
// env: Env,
// sender: Addr,
// ) -> Result<TokenPositionMapping, ContractError> {
// let num_tokens = MINTABLE_NUM_TOKENS.load(deps.storage)?;
// let tx_index = if let Some(tx) = &env.transaction {
// tx.index
// } else {
// 0
// };
// let sha256 = Sha256::digest(
// format!("{}{}{}{}", sender, num_tokens, env.block.height, tx_index).into_bytes(),
// );
// // Cut first 16 bytes from 32 byte value
// let randomness: [u8; 16] = sha256.to_vec()[0..16].try_into().unwrap();

// let mut rng = Xoshiro128PlusPlus::from_seed(randomness);

// let r = rng.next_u32();

// let order = match r % 2 {
// 1 => Order::Descending,
// _ => Order::Ascending,
// };
// let mut rem = 50;
// if rem > num_tokens {
// rem = num_tokens;
// }
// let n = r % rem;
// let position = MINTABLE_TOKEN_POSITIONS
// .keys(deps.storage, None, None, order)
// .skip(n as usize)
// .take(1)
// .collect::<StdResult<Vec<_>>>()?[0];

// let token_id = MINTABLE_TOKEN_POSITIONS.load(deps.storage, position)?;
// Ok(TokenPositionMapping { position, token_id })
// }

pub fn execute_update_mint_price(
deps: DepsMut,
env: Env,
Expand Down Expand Up @@ -1019,17 +961,9 @@ pub fn execute_burn_remaining(
if mintable_num_tokens == 0 {
return Err(ContractError::SoldOut {});
}

// TODO: implement purge in sg_minter_utils
// let keys = MINTABLE_TOKEN_POSITIONS
// .keys(deps.storage, None, None, Order::Ascending)
// .collect::<Vec<_>>();
// let mut total: u32 = 0;
// for key in keys {
// total += 1;
// MINTABLE_TOKEN_POSITIONS.remove(deps.storage, key?);
// }
// Decrement mintable num tokens
// purge all buckets
sg_minter_utils::purge_buckets(deps.storage, sg_minter_utils::MAX_BUCKETS)?;
// Set mintable num tokens to 0
MINTABLE_NUM_TOKENS.save(deps.storage, &0)?;

let event = Event::new("burn-remaining")
Expand Down
55 changes: 50 additions & 5 deletions packages/sg-minter-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use cosmwasm_schema::cw_serde;

use cosmwasm_std::{StdError, Storage};
use cw_storage_plus::Item;
use nois::{int_in_range, sub_randomness_with_key};
use nois::{int_in_range, shuffle as nois_shuffle, sub_randomness_with_key};
use thiserror::Error;
// BUCKET_SIZE is limited to 256 to efficiently store ids as u8 partitioning into multiple buckets
const BUCKET_SIZE: u32 = 256;

const MAX_SIZE: u32 = 256 * 256;
pub const BUCKET_SIZE: u32 = 256;
pub const MAX_BUCKETS: u32 = 256;
pub const MAX_SIZE: u32 = MAX_BUCKETS * BUCKET_SIZE;

// buckets returns the number of necessary buckets for a given collection size.
// it returns the number of buckets and the size of the last bucket
Expand Down Expand Up @@ -159,14 +159,25 @@ pub fn purge_buckets(storage: &mut dyn Storage, max_buckets: u32) -> Result<u32,
Ok(buckets_to_remove as u32)
}

pub fn shuffle(storage: &mut dyn Storage, random_seed: [u8; 32]) -> Result<(), MinterUtilsError> {
let mut provider = sub_randomness_with_key(random_seed, b"shuffle");
let Some(available_buckets) = storage.get(&AVAILABLE_BUCKETS_KEY) else {
return Err(MinterUtilsError::NoAvailableBuckets {});
};
let shuffled = nois_shuffle(provider.provide(), available_buckets);
storage.set(&AVAILABLE_BUCKETS_KEY, &shuffled);
Ok(())
}

pub fn pick_token(storage: &mut dyn Storage, token_id: u32) -> Result<u32, MinterUtilsError> {
let Some(mut available_buckets) = storage.get(&AVAILABLE_BUCKETS_KEY) else {
return Err(MinterUtilsError::NoAvailableBuckets {});
};
let (bucket_id, correlative) = get_bucket_and_index(token_id);

let bucket_id = bucket_id as u8;
let Ok(bucket_index) = available_buckets.binary_search(&bucket_id) else {
// bucket ids can be random
let Some(bucket_index) = available_buckets.iter().position(|item| *item == bucket_id) else {
return Err(MinterUtilsError::InvalidBucket {
bucket_id: bucket_id as u32,
});
Expand All @@ -179,6 +190,7 @@ pub fn pick_token(storage: &mut dyn Storage, token_id: u32) -> Result<u32, Minte
});
};
let correlative = correlative as u8;
// items within a bucket are sorted
let Ok(token_index) = bucket.binary_search(&correlative) else {
return Err(MinterUtilsError::InvalidTokenId { token_id });
};
Expand Down Expand Up @@ -438,4 +450,37 @@ mod tests {
let available_buckets = deps.storage.get(&AVAILABLE_BUCKETS_KEY);
assert!(available_buckets.is_none());
}
#[test]
fn test_shuffle() {
let mut deps = mock_dependencies();
let r = initialize(&mut deps.storage, 10_000);
assert!(r.is_ok());
let r = shuffle(&mut deps.storage, [0; 32]);
assert!(r.is_ok());
let available_buckets = deps.storage.get(&AVAILABLE_BUCKETS_KEY).unwrap();
assert_eq!(available_buckets.len(), 40);
assert_ne!(
available_buckets,
(0..40).map(|x| x as u8).collect::<Vec<u8>>()
);
assert_eq!(
available_buckets,
[
21, 30, 37, 23, 11, 2, 29, 27, 8, 0, 7, 5, 15, 17, 6, 32, 25, 9, 36, 26, 13, 31,
24, 10, 39, 35, 33, 12, 20, 16, 28, 18, 34, 19, 1, 4, 38, 22, 14, 3
]
);
}

#[test]
fn test_shuffle_and_pick_token() {
let mut deps = mock_dependencies();

let r = initialize(&mut deps.storage, 10_000);
assert!(r.is_ok());
let r = shuffle(&mut deps.storage, [0; 32]);
assert!(r.is_ok());
let token_id = pick_token(&mut deps.storage, 975).unwrap();
assert_eq!(token_id, 975);
}
}

0 comments on commit 3e5688e

Please sign in to comment.