Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mystery box v2 #35

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions contracts/mystery-box/interaction/devnet.snippets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ CHAIN_ID="D"

MB_WASM_PATH="~/mx-contracts-rs/contracts/mystery-box/output/mystery-box.wasm"

CONTRACT_ADDRESS="erd1qqqqqqqqqqqqqpgql3ustfa2ac3d47y496865xkfrcxy48465dsqfzmxx5"
CONTRACT_ADDRESS="erd1qqqqqqqqqqqqqpgqwewz976lq7g68g4eaddkntru6dh28dpm5dsqnc6lvv"

MB_TOKEN=0x4d425346542d663538616430 #MBSFT-f58ad0
deployMysteryBoxSC() {
Expand Down Expand Up @@ -36,57 +36,78 @@ upgradeMysteryBoxSC() {
## eg. setSpecialRole@4d425346542d663538616430@00000000000000000500fc7905a7aaee22daf8952e8faa1ac91e0c4a9ebaa360@45534454526f6c654e4654437265617465@45534454526f6c654e46544275726e@45534454526f6c654e46544164645175616e74697479

###PARAMS

# Cooldown types
## None - 0
## Lifetime - 1
## ResetOnCooldown - 2

#1 Experience points
XP_TYPE=1
XP_REWARD_TOKEN=str:EGLD
XP_VALUE=75
XP_DESCRIPTION=str:ExperiencePoints
XP_CHANCE=4000
DEFAULT_GLOBAL_COOLDOWN=0x00
XP_COOLDOWN_TYPE=0
XP_WINS_PER_COOLDOWN=0x00
XP_COOLDOWN_EPOCHS=0x00
#2 Mystery box
MB_TYPE=2
MB_TOKEN=str:MBSFT-f58ad0
MB_VALUE=1
MB_DESCRIPTION=str:MysteryBox
MB_CHANCE=3999
MB_CHANCE=3998
MB_COOLDOWN_TYPE=0
MB_WINS_PER_COOLDOWN=0x00
MB_COOLDOWN_EPOCHS=0x00
#3 SFT
SFT_TYPE=3
SFT_TOKEN=str:MBSFT-f58ad0
SFT_VALUE=1
SFT_DESCRIPTION=str:SFT
SFT_CHANCE=500
SFT_COOLDOWN_TYPE=0
SFT_WINS_PER_COOLDOWN=0x00
SFT_COOLDOWN_EPOCHS=0x00
#4 PercentValue
PERCENT_TYPE=4
PERCENT_TOKEN=str:EGLD
PERCENT_VALUE=300
PERCENT_DESCRIPTION=str:PercentReward
PERCENT_CHANCE=1000
PERCENT_COOLDOWN_TYPE=0
PERCENT_WINS_PER_COOLDOWN=0x00
PERCENT_COOLDOWN_EPOCHS=0x00
#5 FixedValue
FV_TYPE=5
FV_TOKEN=str:EGLD
FV_VALUE=50000000000000000000
FV_VALUE=400000000000000000000
FV_DESCRIPTION=str:FixedValueReward
FV_CHANCE=1
FV_GLOBAL_COOLDOWN=1
FV_CHANCE=2
FV_COOLDOWN_TYPE=1
FV_WINS_PER_COOLDOWN=0x01
FV_COOLDOWN_EPOCHS=0x00
#6 Custom reward
CUSTOM_TYPE=6
CUSTOM_TOKEN=str:EGLD
CUSTOM_VALUE=1
CUSTOM_DESCRIPTION=str:Combo1
CUSTOM_VALUE=10000000000000000000
CUSTOM_DESCRIPTION=str:BigPrize
CUSTOM_CHANCE=500
CUSTOM_GLOBAL_COOLDOWN=1
CUSTOM_COOLDOWN_TYPE=2
CUSTOM_WINS_PER_COOLDOWN=0x0a ## 10
CUSTOM_COOLDOWN_EPOCHS=0x01
setupMysteryBox() {
mxpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \
--pem=${WALLET_PEM} \
--gas-limit=10000000 \
--gas-limit=100000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--function="setupMysteryBox" \
--arguments $XP_TYPE $XP_REWARD_TOKEN $XP_VALUE $XP_DESCRIPTION $XP_CHANCE $DEFAULT_GLOBAL_COOLDOWN \
$MB_TYPE $MB_TOKEN $MB_VALUE $MB_DESCRIPTION $MB_CHANCE $DEFAULT_GLOBAL_COOLDOWN \
$SFT_TYPE $SFT_TOKEN $SFT_VALUE $SFT_DESCRIPTION $SFT_CHANCE $DEFAULT_GLOBAL_COOLDOWN \
$PERCENT_TYPE $PERCENT_TOKEN $PERCENT_VALUE $PERCENT_DESCRIPTION $PERCENT_CHANCE $DEFAULT_GLOBAL_COOLDOWN \
$FV_TYPE $FV_TOKEN $FV_VALUE $FV_DESCRIPTION $FV_CHANCE $FV_GLOBAL_COOLDOWN \
$CUSTOM_TYPE $CUSTOM_TOKEN $CUSTOM_VALUE $CUSTOM_DESCRIPTION $CUSTOM_CHANCE $CUSTOM_GLOBAL_COOLDOWN \
--arguments $XP_TYPE $XP_REWARD_TOKEN $XP_VALUE $XP_DESCRIPTION $XP_CHANCE $XP_COOLDOWN_TYPE $XP_WINS_PER_COOLDOWN $XP_COOLDOWN_EPOCHS \
$MB_TYPE $MB_TOKEN $MB_VALUE $MB_DESCRIPTION $MB_CHANCE $MB_COOLDOWN_TYPE $MB_WINS_PER_COOLDOWN $MB_COOLDOWN_EPOCHS \
$SFT_TYPE $SFT_TOKEN $SFT_VALUE $SFT_DESCRIPTION $SFT_CHANCE $SFT_COOLDOWN_TYPE $SFT_WINS_PER_COOLDOWN $SFT_COOLDOWN_EPOCHS \
$PERCENT_TYPE $PERCENT_TOKEN $PERCENT_VALUE $PERCENT_DESCRIPTION $PERCENT_CHANCE $PERCENT_COOLDOWN_TYPE $PERCENT_WINS_PER_COOLDOWN $PERCENT_COOLDOWN_EPOCHS \
$FV_TYPE $FV_TOKEN $FV_VALUE $FV_DESCRIPTION $FV_CHANCE $FV_COOLDOWN_TYPE $FV_WINS_PER_COOLDOWN $FV_COOLDOWN_EPOCHS \
$CUSTOM_TYPE $CUSTOM_TOKEN $CUSTOM_VALUE $CUSTOM_DESCRIPTION $CUSTOM_CHANCE $CUSTOM_COOLDOWN_TYPE $CUSTOM_WINS_PER_COOLDOWN $CUSTOM_COOLDOWN_EPOCHS \
--send || return
}

Expand Down
69 changes: 62 additions & 7 deletions contracts/mystery-box/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,95 @@ pub enum RewardType {
ManagedVecItem, NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq, Eq, TypeAbi, Clone,
)]
pub struct Reward<M: ManagedTypeApi> {
pub reward_id: usize,
pub reward_type: RewardType,
pub reward_token_id: EgldOrEsdtTokenIdentifier<M>,
pub value: BigUint<M>,
pub description: ManagedBuffer<M>,
pub percentage_chance: u64,
pub epochs_cooldown: u64,
}

impl<M: ManagedTypeApi> Default for Reward<M> {
fn default() -> Self {
Self {
reward_id: 0usize,
reward_type: RewardType::None,
reward_token_id: EgldOrEsdtTokenIdentifier::egld(),
value: BigUint::zero(),
description: ManagedBuffer::new(),
percentage_chance: 0u64,
epochs_cooldown: 0u64,
}
}
}

impl<M: ManagedTypeApi> Reward<M> {
#[inline]
pub fn new(
reward_id: usize,
reward_type: RewardType,
reward_token_id: EgldOrEsdtTokenIdentifier<M>,
value: BigUint<M>,
description: ManagedBuffer<M>,
percentage_chance: u64,
epochs_cooldown: u64,
) -> Self {
Reward {
reward_id,
reward_type,
reward_token_id,
value,
description,
percentage_chance,
epochs_cooldown,
}
}
}

#[derive(
ManagedVecItem, NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq, Eq, TypeAbi, Clone,
)]
pub enum CooldownType {
None,
Lifetime,
ResetOnCooldown,
}

#[derive(
ManagedVecItem, NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq, Eq, TypeAbi, Clone,
)]
pub struct RewardCooldown {
pub cooldown_type: CooldownType,
pub wins_per_cooldown: u64,
pub cooldown_epochs: u64,
pub remaining_epoch_wins: u64,
pub last_update_epoch: u64,
}

impl Default for RewardCooldown {
fn default() -> Self {
Self {
cooldown_type: CooldownType::Lifetime,
wins_per_cooldown: 0u64,
cooldown_epochs: 0u64,
remaining_epoch_wins: 0u64,
last_update_epoch: 0u64,
}
}
}

impl RewardCooldown {
#[inline]
pub fn new(
cooldown_type: CooldownType,
wins_per_cooldown: u64,
cooldown_epochs: u64,
remaining_epoch_wins: u64,
last_update_epoch: u64,
) -> Self {
RewardCooldown {
cooldown_type,
wins_per_cooldown,
cooldown_epochs,
remaining_epoch_wins,
last_update_epoch,
}
}
}
Expand All @@ -71,9 +122,13 @@ pub trait ConfigModule {
#[storage_mapper("mysteryBoxTokenIdentifier")]
fn mystery_box_token_id(&self) -> SingleValueMapper<TokenIdentifier>;

#[view(getGlobalCooldownEpoch)]
#[storage_mapper("globalCooldownEpoch")]
fn global_cooldown_epoch(&self, reward: &RewardType) -> SingleValueMapper<u64>;
#[view(getLastRewardId)]
#[storage_mapper("lastRewardId")]
fn last_reward_id(&self) -> SingleValueMapper<usize>;

#[view(getRewardCooldown)]
#[storage_mapper("rewardCooldown")]
fn reward_cooldown(&self, reward_id: usize) -> SingleValueMapper<RewardCooldown>;

#[view(getWinningRates)]
#[storage_mapper("winningRates")]
Expand Down
38 changes: 31 additions & 7 deletions contracts/mystery-box/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ pub mod events;
pub mod rewards;
pub mod token_attributes;

use crate::config::SFT_AMOUNT;
use config::{Reward, RewardType, MAX_PERCENTAGE};
use crate::config::{RewardCooldown, SFT_AMOUNT};
use config::{CooldownType, Reward, RewardType, MAX_PERCENTAGE};
use multiversx_sc_modules::only_admin;

#[multiversx_sc::contract]
Expand All @@ -36,10 +36,21 @@ pub trait MysteryBox:
fn setup_mystery_box(
&self,
winning_rates_list: MultiValueEncoded<
MultiValue6<RewardType, EgldOrEsdtTokenIdentifier, BigUint, ManagedBuffer, u64, u64>,
MultiValue8<
RewardType,
EgldOrEsdtTokenIdentifier,
BigUint,
ManagedBuffer,
u64,
CooldownType,
u64,
u64,
>,
>,
) {
self.require_caller_is_admin();
let current_epoch = self.blockchain().get_block_epoch();
let mut reward_id = self.last_reward_id().get();
let mut accumulated_percentage = 0u64;
let mut winning_rates = ManagedVec::new();
for winning_rate in winning_rates_list.into_iter() {
Expand All @@ -49,25 +60,38 @@ pub trait MysteryBox:
value,
description,
percentage_chance,
epochs_cooldown,
cooldown_type,
wins_per_cooldown,
cooldown_epochs,
) = winning_rate.into_tuple();
accumulated_percentage += percentage_chance;
reward_id += 1;
let reward = Reward::new(
reward_id,
reward_type,
reward_token_id,
value,
description,
percentage_chance,
epochs_cooldown,
);
self.check_reward_validity(&reward);
let reward_cooldown = RewardCooldown::new(
cooldown_type,
wins_per_cooldown,
cooldown_epochs,
wins_per_cooldown,
current_epoch,
);
self.check_reward_validity(&reward, &reward_cooldown);
winning_rates.push(reward);

self.reward_cooldown(reward_id).set(reward_cooldown);
}
require!(
accumulated_percentage == MAX_PERCENTAGE,
"The total percentage must be 100%"
);

self.last_reward_id().set(reward_id);
self.winning_rates().set(winning_rates);
self.mystery_box_uris().set_if_empty(ManagedVec::new());
}
Expand Down Expand Up @@ -129,7 +153,7 @@ pub trait MysteryBox:
let mut winning_reward = Reward::default();
while active_cooldown {
winning_reward = self.get_winning_reward(&attributes);
active_cooldown = self.check_global_cooldown(current_epoch, &winning_reward);
active_cooldown = self.check_reward_cooldown(current_epoch, &winning_reward);
}

// We send the mystery box rewards directly to the user
Expand Down
52 changes: 40 additions & 12 deletions contracts/mystery-box/src/rewards.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
multiversx_sc::imports!();
multiversx_sc::derive_imports!();

use crate::config::{self, Reward, RewardType, MAX_PERCENTAGE};
use crate::config::{self, CooldownType, Reward, RewardCooldown, RewardType, MAX_PERCENTAGE};

#[multiversx_sc::module]
pub trait RewardsModule: config::ConfigModule {
Expand Down Expand Up @@ -29,7 +29,19 @@ pub trait RewardsModule: config::ConfigModule {
winning_reward
}

fn check_reward_validity(&self, reward: &Reward<Self::Api>) {
fn check_reward_validity(&self, reward: &Reward<Self::Api>, reward_cooldown: &RewardCooldown) {
if reward_cooldown.cooldown_type == CooldownType::ResetOnCooldown {
require!(
reward_cooldown.wins_per_cooldown > 0 && reward_cooldown.cooldown_epochs > 0,
"Invalid cooldown input for resettable cooldown type"
);
}
if reward_cooldown.cooldown_epochs > 0 {
require!(
reward_cooldown.wins_per_cooldown > 0,
"Wins per cooldown must be greater than 0 for rewards with cooldown"
);
}
match reward.reward_type {
RewardType::ExperiencePoints => {
require!(
Expand Down Expand Up @@ -68,17 +80,33 @@ pub trait RewardsModule: config::ConfigModule {
}
}

fn check_global_cooldown(&self, current_epoch: u64, reward: &Reward<Self::Api>) -> bool {
let global_cooldown_epoch = self.global_cooldown_epoch(&reward.reward_type).get();
fn check_reward_cooldown(&self, current_epoch: u64, reward: &Reward<Self::Api>) -> bool {
let reward_cooldown_mapper = self.reward_cooldown(reward.reward_id);
if reward_cooldown_mapper.is_empty() {
return false;
};

let mut reward_cooldown = reward_cooldown_mapper.get();
if reward_cooldown.cooldown_type == CooldownType::None {
return false;
};

let mut cooldown_check = true;

if reward.epochs_cooldown == 0 {
false
} else if global_cooldown_epoch <= current_epoch {
self.global_cooldown_epoch(&reward.reward_type)
.set(current_epoch + reward.epochs_cooldown);
false
} else {
true
if reward_cooldown.cooldown_type == CooldownType::ResetOnCooldown
&& current_epoch >= reward_cooldown.last_update_epoch + reward_cooldown.cooldown_epochs
{
reward_cooldown.last_update_epoch = current_epoch;
reward_cooldown.remaining_epoch_wins = reward_cooldown.wins_per_cooldown;
}

if reward_cooldown.remaining_epoch_wins > 0 {
reward_cooldown.remaining_epoch_wins -= 1;
cooldown_check = false;
}

reward_cooldown_mapper.set(reward_cooldown);

cooldown_check
}
}
Loading