From 47f0ea977fd3c5c9788ae35b28d736fb8388cb8a Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Sat, 6 Jan 2024 13:19:01 +0200 Subject: [PATCH] Added vesting functionality --- launchpad-guaranteed-tickets/src/lib.rs | 61 ++++++++- .../src/token_release.rs | 126 ++++++++++++++++++ launchpad-guaranteed-tickets/wasm/src/lib.rs | 7 +- 3 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 launchpad-guaranteed-tickets/src/token_release.rs diff --git a/launchpad-guaranteed-tickets/src/lib.rs b/launchpad-guaranteed-tickets/src/lib.rs index 3a0f4a4..323ec0a 100644 --- a/launchpad-guaranteed-tickets/src/lib.rs +++ b/launchpad-guaranteed-tickets/src/lib.rs @@ -3,12 +3,13 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use launchpad_common::launch_stage::Flags; +use launchpad_common::{launch_stage::Flags, tickets::WINNING_TICKET}; use crate::guranteed_ticket_winners::GuaranteedTicketsSelectionOperation; pub mod guaranteed_tickets_init; pub mod guranteed_ticket_winners; +pub mod token_release; pub type UserTicketsStatus = MultiValue5; @@ -27,6 +28,7 @@ pub trait LaunchpadGuaranteedTickets: + launchpad_common::user_interactions::UserInteractionsModule + guaranteed_tickets_init::GuaranteedTicketsInitModule + guranteed_ticket_winners::GuaranteedTicketWinnersModule + + token_release::TokenReleaseModule { #[allow(clippy::too_many_arguments)] #[init] @@ -149,7 +151,62 @@ pub trait LaunchpadGuaranteedTickets: #[endpoint(claimLaunchpadTokens)] fn claim_launchpad_tokens_endpoint(&self) { - self.claim_launchpad_tokens(Self::default_send_launchpad_tokens_fn); + let caller = self.blockchain().get_caller(); + let user_results_processed = self.claim_list().contains(&caller); + if !user_results_processed { + self.compute_launchpad_results(&caller); + }; + + let claimable_tokens = self.compute_claimable_tokens(&caller); + if claimable_tokens > 0 { + let launchpad_token_id = self.launchpad_token_id().get(); + self.send() + .direct_esdt(&caller, &launchpad_token_id, 0, &claimable_tokens); + self.user_claimed_balance(&caller) + .update(|balance| *balance += claimable_tokens); + } + } + + fn compute_launchpad_results(&self, caller: &ManagedAddress) { + self.require_claim_period(); + + let ticket_range = self.try_get_ticket_range(caller); + let nr_confirmed_tickets = self.nr_confirmed_tickets(caller).get(); + let mut nr_redeemable_tickets = 0; + + for ticket_id in ticket_range.first_id..=ticket_range.last_id { + let ticket_status = self.ticket_status(ticket_id).get(); + if ticket_status == WINNING_TICKET { + self.ticket_status(ticket_id).clear(); + + nr_redeemable_tickets += 1; + } + + self.ticket_pos_to_id(ticket_id).clear(); + } + + self.nr_confirmed_tickets(caller).clear(); + self.ticket_range_for_address(caller).clear(); + self.ticket_batch(ticket_range.first_id).clear(); + + if nr_redeemable_tickets > 0 { + self.nr_winning_tickets() + .update(|nr_winning_tickets| *nr_winning_tickets -= nr_redeemable_tickets); + } + + self.claim_list().add(caller); + + let nr_tickets_to_refund = nr_confirmed_tickets - nr_redeemable_tickets; + self.refund_ticket_payment(caller, nr_tickets_to_refund); + + if nr_redeemable_tickets > 0 { + let tokens_per_winning_ticket = self.launchpad_tokens_per_winning_ticket().get(); + let launchpad_tokens_amount_won = + BigUint::from(nr_redeemable_tickets as u32) * tokens_per_winning_ticket; + + self.user_total_claimable_balance(caller) + .set(launchpad_tokens_amount_won); + } } #[only_owner] diff --git a/launchpad-guaranteed-tickets/src/token_release.rs b/launchpad-guaranteed-tickets/src/token_release.rs new file mode 100644 index 0000000..ae84700 --- /dev/null +++ b/launchpad-guaranteed-tickets/src/token_release.rs @@ -0,0 +1,126 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use launchpad_common::config; + +pub const MAX_PERCENTAGE: u64 = 10_000; + +#[derive(TopEncode, TopDecode, TypeAbi)] +pub struct UnlockSchedule { + claim_start_epoch: u64, + initial_release_percentage: u64, + vesting_release_times: u64, + vesting_release_percentage: u64, + vesting_release_period: u64, +} + +impl UnlockSchedule { + pub fn new( + claim_start_epoch: u64, + initial_release_percentage: u64, + vesting_release_times: u64, + vesting_release_percentage: u64, + vesting_release_period: u64, + ) -> Self { + UnlockSchedule { + claim_start_epoch, + initial_release_percentage, + vesting_release_times, + vesting_release_percentage, + vesting_release_period, + } + } +} + +#[multiversx_sc::module] +pub trait TokenReleaseModule: config::ConfigModule { + #[only_owner] + #[endpoint(setUnlockSchedule)] + fn set_unlock_schedule( + &self, + claim_start_epoch: u64, + initial_release_percentage: u64, + vesting_release_times: u64, + vesting_release_percentage: u64, + vesting_release_period: u64, + ) { + let configuration = self.configuration(); + require!( + !configuration.is_empty(), + "Timeline configuration is not set" + ); + let confirmation_period_start_block = configuration.get().confirmation_period_start_block; + + let current_block = self.blockchain().get_block_nonce(); + let current_epoch = self.blockchain().get_block_epoch(); + require!( + current_block < confirmation_period_start_block || self.unlock_schedule().is_empty(), + "Can't change the unlock schedule" + ); + require!( + claim_start_epoch >= current_epoch, + "Wrong claim start epoch" + ); + require!( + vesting_release_period > 0, + "Wrong vesting release recurrency" + ); + + let unlock_percentage = + initial_release_percentage + vesting_release_times * vesting_release_percentage; + + require!( + unlock_percentage == MAX_PERCENTAGE, + "Unlock percentage is not 100%" + ); + + let unlock_schedule = UnlockSchedule::new( + claim_start_epoch, + initial_release_percentage, + vesting_release_times, + vesting_release_percentage, + vesting_release_period, + ); + + self.unlock_schedule().set(unlock_schedule); + } + + #[view(getClaimableTokens)] + fn compute_claimable_tokens(&self, address: &ManagedAddress) -> BigUint { + let user_total_claimable_balance = self.user_total_claimable_balance(address).get(); + let user_claimed_balance = self.user_claimed_balance(address).get(); + if user_total_claimable_balance == user_claimed_balance { + return BigUint::zero(); + } + let unlock_schedule_mapper = self.unlock_schedule(); + if unlock_schedule_mapper.is_empty() { + return BigUint::zero(); + } + let unlock_schedule = unlock_schedule_mapper.get(); + let current_epoch = self.blockchain().get_block_epoch(); + if unlock_schedule.claim_start_epoch > current_epoch { + return BigUint::zero(); + } + + let epochs_passed = current_epoch - unlock_schedule.claim_start_epoch; + let mut claimable_periods = epochs_passed / unlock_schedule.vesting_release_period; + if claimable_periods > unlock_schedule.vesting_release_times { + claimable_periods = unlock_schedule.vesting_release_times; + } + let claimable_percentage = unlock_schedule.initial_release_percentage + + unlock_schedule.vesting_release_percentage * claimable_periods; + let current_claimable_tokens = + &user_total_claimable_balance * claimable_percentage / MAX_PERCENTAGE; + + current_claimable_tokens - user_claimed_balance + } + + #[storage_mapper("userTotalClaimableBalance")] + fn user_total_claimable_balance(&self, address: &ManagedAddress) -> SingleValueMapper; + + #[storage_mapper("userClaimedBalance")] + fn user_claimed_balance(&self, address: &ManagedAddress) -> SingleValueMapper; + + #[storage_mapper("unlockSchedule")] + fn unlock_schedule(&self) -> SingleValueMapper; +} diff --git a/launchpad-guaranteed-tickets/wasm/src/lib.rs b/launchpad-guaranteed-tickets/wasm/src/lib.rs index b43e809..faea531 100644 --- a/launchpad-guaranteed-tickets/wasm/src/lib.rs +++ b/launchpad-guaranteed-tickets/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 32 +// Endpoints: 35 // Async Callback (empty): 1 -// Total number of exported functions: 34 +// Total number of exported functions: 37 #![no_std] #![feature(lang_items)] @@ -21,6 +21,7 @@ multiversx_sc_wasm_adapter::endpoints! { addTickets depositLaunchpadTokens addUsersToBlacklist + removeGuaranteedUsersFromBlacklist distributeGuaranteedTickets claimLaunchpadTokens claimTicketPayment @@ -50,6 +51,8 @@ multiversx_sc_wasm_adapter::endpoints! { isUserBlacklisted confirmTickets hasUserClaimedTokens + setUnlockSchedule + getClaimableTokens ) }