Skip to content

Commit

Permalink
Feat/min bet patch (#24)
Browse files Browse the repository at this point in the history
* add new GameConfig struct, state with game_duration_epochfield

* more min bet patch

* almost min bid base stable version

* working version, missing test coverage

* rm println

* fix raffle_denom_prize conversion; passing tests

* min bet time based tests. minor fixes and renamings

* code improvements

* fix discounted bid

* clippy happy

* fix

* next_game_start option

* test for future next game start

* more validation, more test

* contract fix

* contract schema

* mixins and store

* frontend ms

* frontend pp
  • Loading branch information
magiodev authored Jun 17, 2024
1 parent 750a803 commit 4785b8d
Show file tree
Hide file tree
Showing 31 changed files with 738 additions and 209 deletions.
47 changes: 41 additions & 6 deletions contracts/prudent-pots/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::query::{
query_winning_pots,
};
use crate::reply::game_end_reply;
use crate::state::{GAME_CONFIG, REALLOCATION_FEE_POOL};
use crate::state::{GameConfig, GAME_CONFIG, OLD_GAME_CONFIG, REALLOCATION_FEE_POOL};

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:prudent-pots";
Expand Down Expand Up @@ -51,7 +51,15 @@ pub fn instantiate(
REALLOCATION_FEE_POOL.save(deps.storage, &Uint128::zero())?;

// Initialize game state and pots for the next game
prepare_next_game(deps, &env, Uint128::zero(), None, None, None)?;
prepare_next_game(
deps,
&env,
Uint128::zero(),
None,
None,
None,
msg.next_game_start,
)?;

Ok(Response::new()
.add_attribute("method", "instantiate")
Expand All @@ -67,7 +75,7 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::UpdateConfig { config } => update_config(deps, env, info, config),
ExecuteMsg::UpdateConfig { config } => update_config(deps, env, info, *config),
ExecuteMsg::AllocateTokens { pot_id } => allocate_tokens(deps, env, info, pot_id),
ExecuteMsg::ReallocateTokens {
from_pot_id,
Expand All @@ -76,12 +84,14 @@ pub fn execute(
ExecuteMsg::GameEnd {
raffle_cw721_token_id,
raffle_cw721_token_addr,
next_game_start,
} => game_end(
deps,
env,
info,
raffle_cw721_token_id,
raffle_cw721_token_addr,
next_game_start,
),
}
}
Expand All @@ -95,11 +105,11 @@ pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, Contract
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GameConfig {} => to_json_binary(&query_game_config(deps)?),
QueryMsg::GameState {} => to_json_binary(&query_game_state(deps)?),
QueryMsg::BidRange { address } => to_json_binary(&query_bid_range(deps, address)?),
QueryMsg::BidRange { address } => to_json_binary(&query_bid_range(deps, env, address)?),
QueryMsg::PotState { pot_id } => to_json_binary(&query_pot_state(deps, pot_id)?),
QueryMsg::PotsState {} => to_json_binary(&query_pots_state(deps)?),
QueryMsg::WinningPots {} => to_json_binary(&query_winning_pots(deps)?),
Expand All @@ -118,6 +128,31 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
// load the old game config
let old_game_config = OLD_GAME_CONFIG.load(deps.storage)?;

// Save new GameConfig at game_config_v2 storage key
GAME_CONFIG.save(
deps.storage,
&GameConfig {
fee: old_game_config.fee,
fee_reallocation: old_game_config.fee_reallocation,
fee_address: old_game_config.fee_address,
game_denom: old_game_config.game_denom,
game_cw721_addrs: old_game_config.game_cw721_addrs,
game_duration: old_game_config.game_duration,
game_duration_epoch: msg.game_duration_epoch, // this is from migrateMsg
game_extend: old_game_config.game_extend,
game_end_threshold: old_game_config.game_end_threshold,
min_pot_initial_allocation: old_game_config.min_pot_initial_allocation,
decay_factor: msg.decay_factor, // this is from migrateMsg
reallocations_limit: old_game_config.reallocations_limit,
},
)?;

// remove the old game config
OLD_GAME_CONFIG.remove(deps.storage);

Ok(Response::new().add_attribute("migrate", "successful"))
}
6 changes: 6 additions & 0 deletions contracts/prudent-pots/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub enum ContractError {
#[error("The reallocations limit has been reached for your address.")]
ReallocationsLimitReached {},

#[error("This action cannot be performed as the game has not started yet.")]
GameNotStarted {},

#[error("This action cannot be performed as the game has already ended.")]
GameAlreadyEnded {},

Expand All @@ -42,6 +45,9 @@ pub enum ContractError {
#[error("Raffle NFT specified is invalid.")]
InvalidRaffleNft {},

#[error("The next game start time is invalid.")]
InvalidNextGameStart {},

#[error("Expected a CW721 token transfer but none was received.")]
Cw721TokenNotReceived {},

Expand Down
44 changes: 34 additions & 10 deletions contracts/prudent-pots/src/execute.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use cosmwasm_std::{attr, BankMsg, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128};
use std::str::FromStr;

use cosmwasm_std::{
attr, BankMsg, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, Uint128,
};

use crate::{
helpers::{
Expand All @@ -11,10 +15,9 @@ use crate::{
update_player_allocation, update_pot_state,
},
validate::{
validate_and_extend_game_time, validate_existing_allocation, validate_funds,
validate_game_end_time, validate_increase_player_reallocations,
validate_is_contract_admin, validate_is_contract_admin_game_end,
validate_pot_limit_not_exceeded,
extend_game_time, validate_existing_allocation, validate_funds, validate_game_end_time,
validate_game_time, validate_increase_player_reallocations, validate_is_contract_admin,
validate_is_contract_admin_game_end, validate_pot_limit_not_exceeded,
},
},
msg::UpdateGameConfig,
Expand Down Expand Up @@ -63,6 +66,9 @@ pub fn update_config(
if let Some(game_duration) = update_config.game_duration {
game_config.game_duration = game_duration;
}
if let Some(game_duration_epoch) = update_config.game_duration_epoch {
game_config.game_duration_epoch = game_duration_epoch;
}
if let Some(game_extend) = update_config.game_extend {
if game_extend > game_config.game_duration {
return Err(ContractError::InvalidInput {});
Expand All @@ -76,7 +82,9 @@ pub fn update_config(
game_config.min_pot_initial_allocation = min_pot_initial_allocation;
}
if let Some(decay_factor) = update_config.decay_factor {
if decay_factor.lt(&Uint128::new(50u128)) || decay_factor.gt(&Uint128::new(99u128)) {
if decay_factor.lt(&Decimal::from_str("0.01")?)
|| decay_factor.gt(&Decimal::from_str("0.99")?)
{
return Err(ContractError::InvalidInput {});
}
game_config.decay_factor = decay_factor;
Expand All @@ -103,20 +111,28 @@ pub fn allocate_tokens(
let game_config = GAME_CONFIG.load(deps.storage)?;
let game_state = GAME_STATE.load(deps.storage)?;

validate_and_extend_game_time(deps.storage, &env)?;
validate_game_time(deps.storage, &env)?;
let amount = validate_funds(&info.funds, &game_config.game_denom)?;
validate_pot_limit_not_exceeded(deps.storage, pot_id, amount)?;
validate_existing_allocation(deps.storage, &info.sender, pot_id)?;

// Dynamic bid constraints
let min_bid = calculate_min_bid(&deps.as_ref(), Some(info.sender.to_string()))?;
let max_bid = calculate_max_bid(&deps.as_ref())?;

// min bid based on current addy so we discount by NFT holding
let min_bid = calculate_min_bid(&deps.as_ref(), &env, Some(info.sender.to_string()))?;

// get the originial min bid calculation without taking in account NFT holding discount
let original_min_bid = calculate_min_bid(&deps.as_ref(), &env, None)?;
// max bid based on original min bid, so we don't discount by NFT holding
let max_bid = calculate_max_bid(&deps.as_ref(), original_min_bid)?;
if amount < min_bid || amount > max_bid {
return Err(ContractError::BidOutOfRange {
min: min_bid,
max: max_bid,
});
}
// we do that here so the extend_count doesnt increase before we evaluate the min max bid amounts
extend_game_time(deps.storage, &env)?;

// Update the player's allocation and pot state
update_player_allocation(deps.storage, &info.sender, pot_id, amount, true)?;
Expand Down Expand Up @@ -148,8 +164,9 @@ pub fn reallocate_tokens(
if from_pot_id == to_pot_id {
return Err(ContractError::InvalidPot {});
}
validate_game_time(deps.storage, &env)?;
extend_game_time(deps.storage, &env)?;
validate_increase_player_reallocations(deps.storage, &info.sender)?;
validate_and_extend_game_time(deps.storage, &env)?;
validate_existing_allocation(deps.storage, &info.sender, to_pot_id)?;

// Load and check the player's allocations
Expand Down Expand Up @@ -198,10 +215,16 @@ pub fn game_end(
info: MessageInfo,
new_raffle_cw721_id: Option<String>,
new_raffle_cw721_addr: Option<String>,
next_game_start: Option<u64>,
) -> Result<Response, ContractError> {
validate_game_end_time(deps.storage, &env)?;
validate_is_contract_admin_game_end(deps.storage, &deps.querier, &env, &info.sender)?;

// if passed, it should be in the future
if next_game_start.is_some() && next_game_start.unwrap() <= env.block.time.seconds() {
return Err(ContractError::InvalidNextGameStart {});
}

// Ensure both or neither options are provided
if new_raffle_cw721_id.is_some() != new_raffle_cw721_addr.is_some() {
return Err(ContractError::InvalidRaffleNft {});
Expand Down Expand Up @@ -267,6 +290,7 @@ pub fn game_end(
process_raffle_winner_resp.new_raffle_cw721_id,
process_raffle_winner_resp.new_raffle_cw721_addr,
Some(process_raffle_winner_resp.new_raffle_denom_amount),
next_game_start,
)?;

Ok(Response::new()
Expand Down
24 changes: 12 additions & 12 deletions contracts/prudent-pots/src/helpers/game_end.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use cosmwasm_std::{
attr, coins, to_json_binary, Attribute, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Env, Storage,
SubMsg, Uint128, WasmMsg,
attr, coins, to_json_binary, Attribute, BankMsg, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env,
Storage, SubMsg, Uint128, WasmMsg,
};

use crate::{
Expand Down Expand Up @@ -31,13 +31,15 @@ pub fn prepare_next_game(
raffle_cw721_token_id: Option<String>,
raffle_cw721_addr: Option<String>,
raffle_denom_amount: Option<Uint128>,
next_game_start: Option<u64>, // this is intended as a unix timestamp in seconds that should replace the current timestamp, but should also be higher than the current timestamp
) -> Result<(u64, u64, u32), ContractError> {
let config = GAME_CONFIG.load(deps.storage)?;
let game_state = GAME_STATE.may_load(deps.storage)?.unwrap_or_default(); // may load due instantiate invoke

// TODO: finish the impl
// Start the next game 1 second in the future
let game_duration = config.game_duration;
let next_game_start = env.block.time.seconds() + 1;
let next_game_start = next_game_start.unwrap_or(env.block.time.seconds());
let next_game_end = next_game_start + game_duration;

// Validate game start and end times
Expand Down Expand Up @@ -304,20 +306,18 @@ pub fn process_raffle_winner(

/// Helper to calculate the prize amount for raffle distribution based on game extensions.
pub fn get_raffle_denom_prize_amounts(deps: &Deps) -> Result<(Uint128, Uint128), ContractError> {
let game_config: GameConfig = GAME_CONFIG.load(deps.storage)?;
let game_state: GameState = GAME_STATE.load(deps.storage)?;
let raffle: Raffle = RAFFLE.load(deps.storage)?;
let game_config = GAME_CONFIG.load(deps.storage)?;
let game_state = GAME_STATE.load(deps.storage)?;
let raffle = RAFFLE.load(deps.storage)?;

// Apply the decay factor iteratively based on extend_count
let mut prize_percentage = Uint128::from(100u128); // Starting from 100%
let mut prize_percentage = Decimal::percent(100); // Starting from 100%
for _ in 0..game_state.extend_count {
prize_percentage =
prize_percentage.multiply_ratio(game_config.decay_factor, Uint128::from(100u128));
prize_percentage *= Decimal::one() - game_config.decay_factor;
}

let distributed_prize = raffle
.denom_amount
.multiply_ratio(prize_percentage, Uint128::from(100u128));
// Calculate the distributed prize and remaining prize we will send back tot reasury
let distributed_prize = raffle.denom_amount * prize_percentage;
let remaining_prize = raffle.denom_amount.checked_sub(distributed_prize)?;

Ok((distributed_prize, remaining_prize))
Expand Down
Loading

0 comments on commit 4785b8d

Please sign in to comment.