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

add migration guaranteed tickets functionality #63

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
125 changes: 116 additions & 9 deletions launchpad-guaranteed-tickets/src/guaranteed_tickets_init.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
multiversx_sc::imports!();
multiversx_sc::derive_imports!();

pub const STAKING_GUARANTEED_TICKETS_NO: usize = 1;
pub const MIGRATION_GUARANTEED_TICKETS_NO: usize = 1;

#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq, Eq, TypeAbi, Clone)]
pub struct UserGuaranteedTickets<M: ManagedTypeApi> {
pub address: ManagedAddress<M>,
pub guaranteed_tickets: usize,
}

impl<M: ManagedTypeApi> UserGuaranteedTickets<M> {
pub fn new(address: ManagedAddress<M>, guaranteed_tickets: usize) -> Self {
Self {
address,
guaranteed_tickets,
}
}
}

#[multiversx_sc::module]
pub trait GuaranteedTicketsInitModule:
Expand All @@ -16,47 +35,135 @@ pub trait GuaranteedTicketsInitModule:
let min_confirmed_for_guaranteed_ticket = self.min_confirmed_for_guaranteed_ticket().get();
let mut guranteed_ticket_whitelist = self.users_with_guaranteed_ticket();
let mut total_winning_tickets = self.nr_winning_tickets().get();
let mut total_guaranteed_tickets = self.total_guaranteed_tickets().get();

for multi_arg in address_number_pairs {
let (buyer, nr_tickets) = multi_arg.into_tuple();
self.try_create_tickets(buyer.clone(), nr_tickets);
self.user_total_allocated_tickets(&buyer).set(nr_tickets);

if nr_tickets >= min_confirmed_for_guaranteed_ticket {
require!(
total_winning_tickets > 0,
"Too many users with guaranteed ticket"
);

let _ = guranteed_ticket_whitelist.insert(buyer);
total_winning_tickets -= 1;
let user_guaranteed_tickets =
UserGuaranteedTickets::new(buyer, STAKING_GUARANTEED_TICKETS_NO);
let _ = guranteed_ticket_whitelist.insert(user_guaranteed_tickets);
total_winning_tickets -= STAKING_GUARANTEED_TICKETS_NO;
total_guaranteed_tickets += STAKING_GUARANTEED_TICKETS_NO;
}
}

self.nr_winning_tickets().set(total_winning_tickets);
self.total_guaranteed_tickets()
.set(total_guaranteed_tickets);
}

fn add_more_guaranteed_tickets(&self, addresses: MultiValueEncoded<ManagedAddress>) {
self.require_add_tickets_period();

let mut guranteed_ticket_whitelist = self.users_with_guaranteed_ticket();
let mut total_winning_tickets = self.nr_winning_tickets().get();
let mut total_guaranteed_tickets = self.total_guaranteed_tickets().get();

for user in addresses {
let mut user_new_guaranteed_tickets = MIGRATION_GUARANTEED_TICKETS_NO;
let user_initial_guaranteed_tickets =
UserGuaranteedTickets::new(user.clone(), STAKING_GUARANTEED_TICKETS_NO);
if guranteed_ticket_whitelist.swap_remove(&user_initial_guaranteed_tickets) {
user_new_guaranteed_tickets += 1;
}
let user_ticket_range = self.ticket_range_for_address(&user).get();
let user_total_tickets_no = user_ticket_range.last_id - user_ticket_range.first_id + 1;

require!(
total_winning_tickets > 0,
"Too many users with guaranteed ticket"
);
require!(
user_total_tickets_no >= user_new_guaranteed_tickets,
"The guaranteed tickets number is bigger than the user's total tickets"
);

let new_user_guaranteed_tickets =
UserGuaranteedTickets::new(user, user_new_guaranteed_tickets);

let _ = guranteed_ticket_whitelist.insert(new_user_guaranteed_tickets);
total_winning_tickets -= MIGRATION_GUARANTEED_TICKETS_NO;
total_guaranteed_tickets += MIGRATION_GUARANTEED_TICKETS_NO;
}

self.nr_winning_tickets().set(total_winning_tickets);
self.total_guaranteed_tickets()
.set(total_guaranteed_tickets);
}

fn clear_users_with_guaranteed_ticket_after_blacklist(
&self,
users: &ManagedVec<ManagedAddress>,
) {
let mut whitelist = self.users_with_guaranteed_ticket();
let mut nr_users_removed = 0;
let mut nr_tickets_removed = 0;
for user in users {
let was_whitelisted = whitelist.swap_remove(&user);
if was_whitelisted {
nr_users_removed += 1;
let user_staking_guaranteed_tickets =
UserGuaranteedTickets::new(user.clone(), STAKING_GUARANTEED_TICKETS_NO);
let user_migration_guaranteed_tickets = UserGuaranteedTickets::new(
user,
STAKING_GUARANTEED_TICKETS_NO + MIGRATION_GUARANTEED_TICKETS_NO,
);
if whitelist.swap_remove(&user_staking_guaranteed_tickets) {
nr_tickets_removed += user_staking_guaranteed_tickets.guaranteed_tickets;
}
if whitelist.swap_remove(&user_migration_guaranteed_tickets) {
nr_tickets_removed += user_migration_guaranteed_tickets.guaranteed_tickets;
}
}

if nr_users_removed > 0 {
if nr_tickets_removed > 0 {
self.nr_winning_tickets()
.update(|nr_winning| *nr_winning += nr_users_removed);
.update(|nr_winning| *nr_winning += nr_tickets_removed);
}
}

fn get_user_guaranteed_tickets_no(&self, address: ManagedAddress) -> usize {
let mut user_guaranteed_tickets = UserGuaranteedTickets {
address,
guaranteed_tickets: STAKING_GUARANTEED_TICKETS_NO,
};
let mut user_guaranteed_tickets_no = 0;
if self
.users_with_guaranteed_ticket()
.contains(&user_guaranteed_tickets)
{
user_guaranteed_tickets_no = user_guaranteed_tickets.guaranteed_tickets;
}
user_guaranteed_tickets.guaranteed_tickets =
STAKING_GUARANTEED_TICKETS_NO + MIGRATION_GUARANTEED_TICKETS_NO;
if self
.users_with_guaranteed_ticket()
.contains(&user_guaranteed_tickets)
{
require!(
user_guaranteed_tickets_no == 0,
"Multiple guaranteed tickets entries for the user"
);
user_guaranteed_tickets_no = user_guaranteed_tickets.guaranteed_tickets;
}

user_guaranteed_tickets_no
}

#[storage_mapper("minConfirmedForGuaranteedTicket")]
fn min_confirmed_for_guaranteed_ticket(&self) -> SingleValueMapper<usize>;

#[storage_mapper("usersWithGuaranteedTicket")]
fn users_with_guaranteed_ticket(&self) -> UnorderedSetMapper<ManagedAddress>;
fn users_with_guaranteed_ticket(&self) -> UnorderedSetMapper<UserGuaranteedTickets<Self::Api>>;

#[storage_mapper("userTotalAllocatedTickets")]
fn user_total_allocated_tickets(&self, address: &ManagedAddress) -> SingleValueMapper<usize>;

#[storage_mapper("totalGuaranteedTickets")]
fn total_guaranteed_tickets(&self) -> SingleValueMapper<usize>;
}
60 changes: 43 additions & 17 deletions launchpad-guaranteed-tickets/src/guranteed_ticket_winners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ pub trait GuaranteedTicketWinnersModule:
&self,
op: &mut GuaranteedTicketsSelectionOperation<Self::Api>,
) -> OperationCompletionStatus {
let min_confirmed_for_guaranteed_ticket = self.min_confirmed_for_guaranteed_ticket().get();
let mut users_whitelist = self.users_with_guaranteed_ticket();
let mut users_left = users_whitelist.len();

Expand All @@ -55,23 +54,42 @@ pub trait GuaranteedTicketWinnersModule:
return STOP_OP;
}

let current_user = users_whitelist.get_by_index(VEC_MAPPER_START_INDEX);
let _ = users_whitelist.swap_remove(&current_user);
let user_guaranteed_tickets = users_whitelist.get_by_index(VEC_MAPPER_START_INDEX);
let _ = users_whitelist.swap_remove(&user_guaranteed_tickets);
users_left -= 1;

let user_confirmed_tickets = self.nr_confirmed_tickets(&current_user).get();
if user_confirmed_tickets >= min_confirmed_for_guaranteed_ticket {
let ticket_range = self.ticket_range_for_address(&current_user).get();
if !self.has_any_winning_tickets(&ticket_range) {
self.ticket_status(ticket_range.first_id)
.set(WINNING_TICKET);

op.total_additional_winning_tickets += 1;
} else {
op.leftover_tickets += 1;
let user_confirmed_tickets = self
.nr_confirmed_tickets(&user_guaranteed_tickets.address)
.get();
let user_total_allocated_tickets_no = self
.user_total_allocated_tickets(&user_guaranteed_tickets.address)
.get();
if user_confirmed_tickets == user_total_allocated_tickets_no {
let ticket_range = self
.ticket_range_for_address(&user_guaranteed_tickets.address)
.get();
// We keep this function to determine in advance the number of winning tickets the user has
let mut remaining_winning_tickets = self.remaining_user_winning_tickets_no(
&ticket_range,
user_guaranteed_tickets.guaranteed_tickets,
);
op.leftover_tickets +=
user_guaranteed_tickets.guaranteed_tickets - remaining_winning_tickets;
if remaining_winning_tickets > 0 {
for ticket_id in ticket_range.first_id..=ticket_range.last_id {
if remaining_winning_tickets == 0 {
break;
}
let ticket_status = self.ticket_status(ticket_id).get();
if ticket_status != WINNING_TICKET {
self.ticket_status(ticket_id).set(WINNING_TICKET);
op.total_additional_winning_tickets += 1;
remaining_winning_tickets -= 1;
}
}
}
} else {
op.leftover_tickets += 1;
op.leftover_tickets += user_guaranteed_tickets.guaranteed_tickets;
}

CONTINUE_OP
Expand Down Expand Up @@ -110,15 +128,23 @@ pub trait GuaranteedTicketWinnersModule:
})
}

fn has_any_winning_tickets(&self, ticket_range: &TicketRange) -> bool {
fn remaining_user_winning_tickets_no(
&self,
ticket_range: &TicketRange,
guaranteed_tickets: usize,
) -> usize {
let mut remaining_winning_tickets = guaranteed_tickets;
for ticket_id in ticket_range.first_id..=ticket_range.last_id {
if remaining_winning_tickets == 0 {
return 0;
}
let ticket_status = self.ticket_status(ticket_id).get();
if ticket_status == WINNING_TICKET {
return true;
remaining_winning_tickets -= 1;
}
}

false
remaining_winning_tickets
}

fn try_select_winning_ticket(
Expand Down
24 changes: 23 additions & 1 deletion launchpad-guaranteed-tickets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::guranteed_ticket_winners::GuaranteedTicketsSelectionOperation;
pub mod guaranteed_tickets_init;
pub mod guranteed_ticket_winners;

pub type UserTicketsStatus = MultiValue3<usize, usize, usize>;

#[multiversx_sc::contract]
pub trait LaunchpadGuaranteedTickets:
launchpad_common::LaunchpadMain
Expand Down Expand Up @@ -69,12 +71,18 @@ pub trait LaunchpadGuaranteedTickets:
self.add_tickets_with_guaranteed_winners(address_number_pairs);
}

#[only_owner]
#[endpoint(addMoreGuaranteedTickets)]
fn add_more_guaranteed_tickets_endpoint(&self, addresses: MultiValueEncoded<ManagedAddress>) {
self.add_more_guaranteed_tickets(addresses);
}

#[only_owner]
#[payable("*")]
#[endpoint(depositLaunchpadTokens)]
fn deposit_launchpad_tokens_endpoint(&self) {
let base_selection_winning_tickets = self.nr_winning_tickets().get();
let reserved_tickets = self.users_with_guaranteed_ticket().len();
let reserved_tickets = self.total_guaranteed_tickets().get();
let total_tickets = base_selection_winning_tickets + reserved_tickets;

self.deposit_launchpad_tokens(total_tickets);
Expand Down Expand Up @@ -145,4 +153,18 @@ pub trait LaunchpadGuaranteedTickets:
fn claim_ticket_payment_endpoint(&self) {
self.claim_ticket_payment();
}

#[view(getUserTicketsStatus)]
fn user_tickets_status(&self, address: ManagedAddress) -> UserTicketsStatus {
let user_total_allocated_tickets_no = self.user_total_allocated_tickets(&address).get();
let user_confirmed_tickets_no = self.nr_confirmed_tickets(&address).get();
let user_guaranteed_tickets_no = self.get_user_guaranteed_tickets_no(address);

(
user_total_allocated_tickets_no,
user_confirmed_tickets_no,
user_guaranteed_tickets_no,
)
.into()
}
}
27 changes: 19 additions & 8 deletions launchpad-guaranteed-tickets/tests/guaranteed_tickets_setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use launchpad_common::{
winner_selection::WinnerSelectionModule,
};
use launchpad_guaranteed_tickets::{
guaranteed_tickets_init::GuaranteedTicketsInitModule, LaunchpadGuaranteedTickets,
guaranteed_tickets_init::{
GuaranteedTicketsInitModule, UserGuaranteedTickets, STAKING_GUARANTEED_TICKETS_NO,
},
LaunchpadGuaranteedTickets,
};
use multiversx_sc_scenario::{
managed_address, managed_biguint, managed_token_id, rust_biguint,
Expand Down Expand Up @@ -44,11 +47,11 @@ impl<LaunchpadBuilder> LaunchpadSetup<LaunchpadBuilder>
where
LaunchpadBuilder: 'static + Copy + Fn() -> launchpad_guaranteed_tickets::ContractObj<DebugApi>,
{
pub fn new(lp_builder: LaunchpadBuilder) -> Self {
pub fn new(nr_winning_tickets: usize, lp_builder: LaunchpadBuilder) -> Self {
let rust_zero = rust_biguint!(0u64);
let user_balance = rust_biguint!(TICKET_COST * MAX_TIER_TICKETS as u64);
let total_launchpad_tokens =
rust_biguint!(LAUNCHPAD_TOKENS_PER_TICKET * NR_WINNING_TICKETS as u64);
rust_biguint!(LAUNCHPAD_TOKENS_PER_TICKET * nr_winning_tickets as u64);

let mut b_mock = BlockchainStateWrapper::new();
let owner_address = b_mock.create_user_account(&rust_zero);
Expand Down Expand Up @@ -76,7 +79,7 @@ where
managed_biguint!(LAUNCHPAD_TOKENS_PER_TICKET),
EgldOrEsdtTokenIdentifier::egld(),
managed_biguint!(TICKET_COST),
NR_WINNING_TICKETS,
nr_winning_tickets,
CONFIRM_START_BLOCK,
WINNER_SELECTION_START_BLOCK,
CLAIM_START_BLOCK,
Expand All @@ -97,11 +100,15 @@ where
sc.add_tickets_endpoint(args);

// 1 ticket for the max tier gets removed
assert_eq!(sc.nr_winning_tickets().get(), NR_WINNING_TICKETS - 1);
assert_eq!(sc.nr_winning_tickets().get(), nr_winning_tickets - 1);
assert_eq!(sc.users_with_guaranteed_ticket().len(), 1);
let user_guaranteed_tickets = UserGuaranteedTickets::new(
managed_address!(participants.last().unwrap()),
STAKING_GUARANTEED_TICKETS_NO,
);
assert!(sc
.users_with_guaranteed_ticket()
.contains(&managed_address!(participants.last().unwrap())));
.contains(&user_guaranteed_tickets));
})
.assert_ok();

Expand Down Expand Up @@ -152,13 +159,17 @@ where
)
}

pub fn select_base_winners_mock(&mut self, nr_whales: usize) -> TxResult {
pub fn select_base_winners_mock(
&mut self,
nr_winning_tickets: usize,
guaranteed_tickets: usize,
) -> TxResult {
self.b_mock.execute_tx(
&self.owner_address,
&self.lp_wrapper,
&rust_biguint!(0),
|sc| {
let base_winning = NR_WINNING_TICKETS - nr_whales;
let base_winning = nr_winning_tickets - guaranteed_tickets;
for ticket_id in 1..=base_winning {
sc.ticket_status(ticket_id).set(WINNING_TICKET);
}
Expand Down
Loading