From 308b945dd09e932a609cfb187fbc8258bb9ef32c Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Wed, 8 May 2024 09:56:39 +0300 Subject: [PATCH 1/5] changes --- liquid-staking/Cargo.toml | 2 + liquid-staking/src/config.rs | 12 +++-- liquid-staking/src/delegation.rs | 87 ++++++++++++++++++-------------- liquid-staking/src/events.rs | 10 ++-- liquid-staking/src/lib.rs | 15 +++++- 5 files changed, 77 insertions(+), 49 deletions(-) diff --git a/liquid-staking/Cargo.toml b/liquid-staking/Cargo.toml index 69114cd..8585289 100644 --- a/liquid-staking/Cargo.toml +++ b/liquid-staking/Cargo.toml @@ -7,6 +7,7 @@ publish = false [lib] path = "src/lib.rs" + [dependencies.itertools] version = "0.10.1" default-features = false @@ -19,5 +20,6 @@ version = "=0.48.0" [dependencies.delegation-mock] path = "../delegation-mock" + [dev-dependencies.multiversx-sc-scenario] version = "=0.48.0" diff --git a/liquid-staking/src/config.rs b/liquid-staking/src/config.rs index 708843a..097260e 100644 --- a/liquid-staking/src/config.rs +++ b/liquid-staking/src/config.rs @@ -1,19 +1,21 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use crate::liquidity_pool::State; +use delegation_mock::Epoch; -pub const MAX_PERCENTAGE: u64 = 100_000; -pub const UNBOND_PERIOD: u64 = 10; +use crate::{liquidity_pool::State, Percent}; + +pub const MAX_PERCENTAGE: Percent = 100_000; +pub const UNBOND_PERIOD: Epoch = 10; #[derive( TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, Clone, PartialEq, Eq, Debug, )] pub struct UnstakeTokenAttributes { pub delegation_contract: ManagedAddress, - pub unstake_epoch: u64, + pub unstake_epoch: Epoch, pub unstake_amount: BigUint, - pub unbond_epoch: u64, + pub unbond_epoch: Epoch, } #[multiversx_sc::module] diff --git a/liquid-staking/src/delegation.rs b/liquid-staking/src/delegation.rs index 67969d3..e6aefeb 100644 --- a/liquid-staking/src/delegation.rs +++ b/liquid-staking/src/delegation.rs @@ -1,3 +1,5 @@ +use delegation_mock::Epoch; + use crate::errors::{ ERROR_ALREADY_WHITELISTED, ERROR_BAD_DELEGATION_ADDRESS, ERROR_CLAIM_EPOCH, ERROR_CLAIM_START, ERROR_DELEGATION_CAP, ERROR_FIRST_DELEGATION_NODE, ERROR_NOT_WHITELISTED, @@ -20,8 +22,8 @@ pub enum ClaimStatusType { #[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq, Eq, TypeAbi, Clone)] pub struct ClaimStatus { pub status: ClaimStatusType, - pub last_claim_epoch: u64, - pub last_claim_block: u64, + pub last_claim_epoch: Epoch, + pub last_claim_block: Epoch, pub current_node: u32, pub starting_token_reserve: BigUint, } @@ -85,7 +87,7 @@ pub trait DelegationModule: ); require!( - delegation_contract_cap >= total_staked, + total_staked <= delegation_contract_cap, ERROR_DELEGATION_CAP ); @@ -138,7 +140,7 @@ pub trait DelegationModule: ERROR_ONLY_DELEGATION_ADMIN ); require!( - delegation_contract_cap >= total_staked, + total_staked <= delegation_contract_cap, ERROR_DELEGATION_CAP ); @@ -159,24 +161,27 @@ pub trait DelegationModule: let mut delegation_addresses_mapper = self.delegation_addresses_list(); if delegation_addresses_mapper.is_empty() { delegation_addresses_mapper.push_front(contract_address); - } else { - let mut check_if_added = false; - for delegation_address_element in delegation_addresses_mapper.iter() { - let node_id = delegation_address_element.get_node_id(); - let delegation_address = delegation_address_element.into_value(); - let delegation_contract_data = - self.delegation_contract_data(&delegation_address).get(); - if apy >= delegation_contract_data.apy { - self.delegation_addresses_list() - .push_before_node_id(node_id, contract_address.clone()); - check_if_added = true; - break; - } - } - if !check_if_added { - delegation_addresses_mapper.push_back(contract_address); + + return; + } + + let mut added = false; + for delegation_address_element in delegation_addresses_mapper.iter() { + let node_id = delegation_address_element.get_node_id(); + let delegation_address = delegation_address_element.into_value(); + let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); + if apy >= delegation_contract_data.apy { + self.delegation_addresses_list() + .push_before_node_id(node_id, contract_address.clone()); + added = true; + + break; } } + + if !added { + delegation_addresses_mapper.push_back(contract_address); + } } fn remove_delegation_address_from_list(&self, contract_address: &ManagedAddress) { @@ -185,6 +190,7 @@ pub trait DelegationModule: let delegation_address = delegation_address_element.into_value(); if contract_address == &delegation_address { self.delegation_addresses_list().remove_node_by_id(node_id); + break; } } @@ -206,7 +212,6 @@ pub trait DelegationModule: ); let delegation_addresses_mapper = self.delegation_addresses_list(); - for delegation_address_element in delegation_addresses_mapper.iter() { let delegation_address = delegation_address_element.into_value(); let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); @@ -217,6 +222,7 @@ pub trait DelegationModule: return delegation_address; } } + sc_panic!(ERROR_BAD_DELEGATION_ADDRESS); } @@ -230,7 +236,6 @@ pub trait DelegationModule: ); let delegation_addresses_mapper = self.delegation_addresses_list(); - for delegation_address_element in delegation_addresses_mapper.iter() { let delegation_address = delegation_address_element.into_value(); let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); @@ -239,6 +244,7 @@ pub trait DelegationModule: return delegation_address; } } + sc_panic!(ERROR_BAD_DELEGATION_ADDRESS); } @@ -246,7 +252,7 @@ pub trait DelegationModule: &self, current_claim_status: &ClaimStatus, old_claim_status: ClaimStatus, - current_epoch: u64, + current_epoch: Epoch, ) { require!( current_claim_status.status == ClaimStatusType::None @@ -267,24 +273,27 @@ pub trait DelegationModule: fn prepare_claim_operation( &self, current_claim_status: &mut ClaimStatus, - current_epoch: u64, + current_epoch: Epoch, ) { - if current_claim_status.status == ClaimStatusType::None { - let delegation_addresses_mapper = self.delegation_addresses_list(); - require!( - delegation_addresses_mapper.front().unwrap().get_node_id() != 0, - ERROR_FIRST_DELEGATION_NODE - ); - current_claim_status.status = ClaimStatusType::Pending; - current_claim_status.last_claim_epoch = current_epoch; - current_claim_status.current_node = - delegation_addresses_mapper.front().unwrap().get_node_id(); - let current_total_withdrawn_egld = self.total_withdrawn_egld().get(); - current_claim_status.starting_token_reserve = self - .blockchain() - .get_sc_balance(&EgldOrEsdtTokenIdentifier::egld(), 0) - - current_total_withdrawn_egld; + if current_claim_status.status != ClaimStatusType::None { + return; } + + let delegation_addresses_mapper = self.delegation_addresses_list(); + require!( + delegation_addresses_mapper.front().unwrap().get_node_id() != 0, + ERROR_FIRST_DELEGATION_NODE + ); + current_claim_status.status = ClaimStatusType::Pending; + current_claim_status.last_claim_epoch = current_epoch; + current_claim_status.current_node = + delegation_addresses_mapper.front().unwrap().get_node_id(); + + let current_total_withdrawn_egld = self.total_withdrawn_egld().get(); + let egld_balance = self + .blockchain() + .get_sc_balance(&EgldOrEsdtTokenIdentifier::egld(), 0); + current_claim_status.starting_token_reserve = egld_balance - current_total_withdrawn_egld; } #[view(getDelegationStatus)] diff --git a/liquid-staking/src/events.rs b/liquid-staking/src/events.rs index c03609e..ea0dd5c 100644 --- a/liquid-staking/src/events.rs +++ b/liquid-staking/src/events.rs @@ -1,3 +1,5 @@ +use delegation_mock::Epoch; + use crate::contexts::base::StorageCache; multiversx_sc::imports!(); @@ -12,7 +14,7 @@ pub struct AddLiquidityEvent { virtual_egld_reserve: BigUint, rewards_reserve: BigUint, block: u64, - epoch: u64, + epoch: Epoch, timestamp: u64, } @@ -27,7 +29,7 @@ pub struct RemoveLiquidityEvent { virtual_egld_reserve: BigUint, rewards_reserve: BigUint, block: u64, - epoch: u64, + epoch: Epoch, timestamp: u64, } @@ -94,7 +96,7 @@ pub trait EventsModule: &self, #[indexed] ls_token: &TokenIdentifier, #[indexed] caller: &ManagedAddress, - #[indexed] epoch: u64, + #[indexed] epoch: Epoch, add_liquidity_event: &AddLiquidityEvent, ); @@ -103,7 +105,7 @@ pub trait EventsModule: &self, #[indexed] ls_token: &TokenIdentifier, #[indexed] caller: &ManagedAddress, - #[indexed] epoch: u64, + #[indexed] epoch: Epoch, remove_liquidity_event: &RemoveLiquidityEvent, ); } diff --git a/liquid-staking/src/lib.rs b/liquid-staking/src/lib.rs index b9f3246..a1eb888 100644 --- a/liquid-staking/src/lib.rs +++ b/liquid-staking/src/lib.rs @@ -14,6 +14,9 @@ pub const MIN_EGLD_TO_DELEGATE: u64 = 1_000_000_000_000_000_000; pub const RECOMPUTE_BLOCK_OFFSET: u64 = 10; pub const MAX_DELEGATION_ADDRESSES: usize = 50; +pub type Epoch = u64; +pub type Percent = u64; + pub mod config; mod contexts; pub mod delegation; @@ -75,6 +78,8 @@ pub trait LiquidStaking: let delegation_contract = self.get_delegation_contract_for_delegate(&payment); let gas_for_async_call = self.get_gas_for_async_call(); + drop(storage_cache); + self.delegation_proxy_obj() .contract(delegation_contract.clone()) .delegate() @@ -155,6 +160,8 @@ pub trait LiquidStaking: let delegation_contract = self.get_delegation_contract_for_undelegate(&egld_to_unstake); let gas_for_async_call = self.get_gas_for_async_call(); + drop(storage_cache); + self.delegation_proxy_obj() .contract(delegation_contract.clone()) .undelegate(egld_to_unstake.clone()) @@ -269,8 +276,12 @@ pub trait LiquidStaking: self.unstake_token_supply() .update(|x| *x -= &unstake_amount); self.burn_unstake_tokens(payment.token_nonce); - self.send().direct_egld(&caller, &unstake_amount) + self.send().direct_egld(&caller, &unstake_amount); + + drop(storage_cache); } else { + drop(storage_cache); + let gas_for_async_call = self.get_gas_for_async_call(); self.delegation_proxy_obj() .contract(delegation_contract.clone()) @@ -448,6 +459,8 @@ pub trait LiquidStaking: let delegation_contract = self.get_delegation_contract_for_delegate(&rewards_reserve); let gas_for_async_call = self.get_gas_for_async_call(); + drop(storage_cache); + self.delegation_proxy_obj() .contract(delegation_contract.clone()) .delegate() From 77769699ab109c06ca3fb2408f84881f9da5d19b Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Wed, 8 May 2024 09:58:24 +0300 Subject: [PATCH 2/5] remove useless drop --- liquid-staking/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/liquid-staking/src/lib.rs b/liquid-staking/src/lib.rs index a1eb888..a199fdd 100644 --- a/liquid-staking/src/lib.rs +++ b/liquid-staking/src/lib.rs @@ -277,8 +277,6 @@ pub trait LiquidStaking: .update(|x| *x -= &unstake_amount); self.burn_unstake_tokens(payment.token_nonce); self.send().direct_egld(&caller, &unstake_amount); - - drop(storage_cache); } else { drop(storage_cache); From 74d0ad9699468e5d1c92b0eb2116bccaa656c4b8 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Wed, 8 May 2024 10:01:30 +0300 Subject: [PATCH 3/5] remove drop --- liquid-staking/src/lib.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/liquid-staking/src/lib.rs b/liquid-staking/src/lib.rs index a199fdd..e2368eb 100644 --- a/liquid-staking/src/lib.rs +++ b/liquid-staking/src/lib.rs @@ -78,8 +78,6 @@ pub trait LiquidStaking: let delegation_contract = self.get_delegation_contract_for_delegate(&payment); let gas_for_async_call = self.get_gas_for_async_call(); - drop(storage_cache); - self.delegation_proxy_obj() .contract(delegation_contract.clone()) .delegate() @@ -160,8 +158,6 @@ pub trait LiquidStaking: let delegation_contract = self.get_delegation_contract_for_undelegate(&egld_to_unstake); let gas_for_async_call = self.get_gas_for_async_call(); - drop(storage_cache); - self.delegation_proxy_obj() .contract(delegation_contract.clone()) .undelegate(egld_to_unstake.clone()) @@ -278,8 +274,6 @@ pub trait LiquidStaking: self.burn_unstake_tokens(payment.token_nonce); self.send().direct_egld(&caller, &unstake_amount); } else { - drop(storage_cache); - let gas_for_async_call = self.get_gas_for_async_call(); self.delegation_proxy_obj() .contract(delegation_contract.clone()) @@ -457,8 +451,6 @@ pub trait LiquidStaking: let delegation_contract = self.get_delegation_contract_for_delegate(&rewards_reserve); let gas_for_async_call = self.get_gas_for_async_call(); - drop(storage_cache); - self.delegation_proxy_obj() .contract(delegation_contract.clone()) .delegate() From d5c52e5a6b206f541b06069bed161d1a5d4afd4f Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Wed, 8 May 2024 10:54:12 +0300 Subject: [PATCH 4/5] split into modules --- liquid-staking/src/lib.rs | 484 +----------------- .../src/user_actions/add_liquidity.rs | 83 +++ .../src/user_actions/claim_rewards.rs | 82 +++ liquid-staking/src/user_actions/common.rs | 44 ++ .../src/user_actions/delegate_rewards.rs | 77 +++ liquid-staking/src/user_actions/mod.rs | 7 + .../user_actions/recompute_token_reserve.rs | 61 +++ .../src/user_actions/remove_liquidity.rs | 126 +++++ liquid-staking/src/user_actions/unbond.rs | 121 +++++ .../tests/contract_interactions/mod.rs | 8 +- liquid-staking/wasm/src/lib.rs | 19 +- 11 files changed, 629 insertions(+), 483 deletions(-) create mode 100644 liquid-staking/src/user_actions/add_liquidity.rs create mode 100644 liquid-staking/src/user_actions/claim_rewards.rs create mode 100644 liquid-staking/src/user_actions/common.rs create mode 100644 liquid-staking/src/user_actions/delegate_rewards.rs create mode 100644 liquid-staking/src/user_actions/mod.rs create mode 100644 liquid-staking/src/user_actions/recompute_token_reserve.rs create mode 100644 liquid-staking/src/user_actions/remove_liquidity.rs create mode 100644 liquid-staking/src/user_actions/unbond.rs diff --git a/liquid-staking/src/lib.rs b/liquid-staking/src/lib.rs index e2368eb..83668ab 100644 --- a/liquid-staking/src/lib.rs +++ b/liquid-staking/src/lib.rs @@ -3,15 +3,6 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use multiversx_sc::types::OperationCompletionStatus; -use multiversx_sc_modules::ongoing_operation::{ - CONTINUE_OP, DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, STOP_OP, -}; -pub const DEFAULT_GAS_TO_CLAIM_REWARDS: u64 = 6_000_000; -pub const MIN_GAS_FOR_ASYNC_CALL: u64 = 12_000_000; -pub const MIN_GAS_FOR_CALLBACK: u64 = 12_000_000; -pub const MIN_EGLD_TO_DELEGATE: u64 = 1_000_000_000_000_000_000; -pub const RECOMPUTE_BLOCK_OFFSET: u64 = 10; pub const MAX_DELEGATION_ADDRESSES: usize = 50; pub type Epoch = u64; @@ -24,13 +15,13 @@ pub mod delegation_proxy; pub mod errors; mod events; mod liquidity_pool; +pub mod user_actions; use crate::{ delegation::{ClaimStatus, ClaimStatusType}, errors::*, }; -use config::{UnstakeTokenAttributes, UNBOND_PERIOD}; use contexts::base::*; use liquidity_pool::State; @@ -42,6 +33,13 @@ pub trait LiquidStaking: + delegation::DelegationModule + multiversx_sc_modules::ongoing_operation::OngoingOperationModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + user_actions::common::CommonModule + + user_actions::add_liquidity::AddLiquidityModule + + user_actions::remove_liquidity::RemoveLiquidityModule + + user_actions::unbond::UnbondModule + + user_actions::claim_rewards::ClaimRewardsModule + + user_actions::delegate_rewards::DelegateRewardsModule + + user_actions::recompute_token_reserve::RecomputeTokenReserveModule { #[init] fn init(&self) { @@ -58,469 +56,9 @@ pub trait LiquidStaking: starting_token_reserve: BigUint::zero(), }; - self.delegation_claim_status().set_if_empty(claim_status); - } - - #[payable("EGLD")] - #[endpoint(addLiquidity)] - fn add_liquidity(&self) { - self.blockchain().check_caller_is_user_account(); - let storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - - let payment = self.call_value().egld_value().clone_value(); - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!(payment >= MIN_EGLD_TO_DELEGATE, ERROR_BAD_PAYMENT_AMOUNT); - - let delegation_contract = self.get_delegation_contract_for_delegate(&payment); - let gas_for_async_call = self.get_gas_for_async_call(); - - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .delegate() - .with_gas_limit(gas_for_async_call) - .with_egld_transfer(payment.clone()) - .async_call() - .with_callback(LiquidStaking::callbacks(self).add_liquidity_callback( - caller, - delegation_contract, - payment, - )) - .call_and_exit() - } - - #[callback] - fn add_liquidity_callback( - &self, - caller: ManagedAddress, - delegation_contract: ManagedAddress, - staked_tokens: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - match result { - ManagedAsyncCallResult::Ok(()) => { - let mut storage_cache = StorageCache::new(self); - self.delegation_contract_data(&delegation_contract) - .update(|contract_data| { - contract_data.total_staked_from_ls_contract += &staked_tokens; - }); - - let ls_token_amount = self.pool_add_liquidity(&staked_tokens, &mut storage_cache); - let user_payment = self.mint_ls_token(ls_token_amount); - self.send().direct_esdt( - &caller, - &user_payment.token_identifier, - user_payment.token_nonce, - &user_payment.amount, - ); - - self.emit_add_liquidity_event(&storage_cache, &caller, user_payment.amount); - } - ManagedAsyncCallResult::Err(_) => { - self.send().direct_egld(&caller, &staked_tokens); - self.move_delegation_contract_to_back(delegation_contract); - } - } - } - - #[payable("*")] - #[endpoint(removeLiquidity)] - fn remove_liquidity(&self) { - self.blockchain().check_caller_is_user_account(); - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt(); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - storage_cache.ls_token_id.is_valid_esdt_identifier(), - ERROR_LS_TOKEN_NOT_ISSUED - ); - require!( - payment.token_identifier == storage_cache.ls_token_id, - ERROR_BAD_PAYMENT_TOKEN - ); - require!(payment.amount > 0, ERROR_BAD_PAYMENT_AMOUNT); - - let egld_to_unstake = self.pool_remove_liquidity(&payment.amount, &mut storage_cache); - require!( - egld_to_unstake >= MIN_EGLD_TO_DELEGATE, - ERROR_INSUFFICIENT_UNSTAKE_AMOUNT - ); - self.burn_ls_token(&payment.amount); - - let delegation_contract = self.get_delegation_contract_for_undelegate(&egld_to_unstake); - let gas_for_async_call = self.get_gas_for_async_call(); - - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .undelegate(egld_to_unstake.clone()) - .with_gas_limit(gas_for_async_call) - .async_call() - .with_callback(LiquidStaking::callbacks(self).remove_liquidity_callback( - caller, - delegation_contract, - egld_to_unstake, - payment.amount, - )) - .call_and_exit() - } - - #[callback] - fn remove_liquidity_callback( - &self, - caller: ManagedAddress, - delegation_contract: ManagedAddress, - egld_to_unstake: BigUint, - ls_tokens_to_be_burned: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - let mut storage_cache = StorageCache::new(self); - match result { - ManagedAsyncCallResult::Ok(()) => { - let current_epoch = self.blockchain().get_block_epoch(); - let unbond_epoch = current_epoch + UNBOND_PERIOD; - - self.delegation_contract_data(&delegation_contract) - .update(|contract_data| { - contract_data.total_staked_from_ls_contract -= &egld_to_unstake; - contract_data.total_unstaked_from_ls_contract += &egld_to_unstake; - }); - self.unstake_token_supply() - .update(|x| *x += &egld_to_unstake); - - let virtual_position = UnstakeTokenAttributes { - delegation_contract, - unstake_epoch: current_epoch, - unstake_amount: egld_to_unstake, - unbond_epoch, - }; - - let user_payment = self.mint_unstake_tokens(&virtual_position); - self.send().direct_esdt( - &caller, - &user_payment.token_identifier, - user_payment.token_nonce, - &user_payment.amount, - ); - - self.emit_remove_liquidity_event( - &storage_cache, - ls_tokens_to_be_burned, - user_payment.amount, - ); - } - ManagedAsyncCallResult::Err(_) => { - let ls_token_amount = self.pool_add_liquidity(&egld_to_unstake, &mut storage_cache); - let user_payment = self.mint_ls_token(ls_token_amount); - self.send().direct_esdt( - &caller, - &user_payment.token_identifier, - user_payment.token_nonce, - &user_payment.amount, - ); - self.move_delegation_contract_to_back(delegation_contract); - } - } - } - - #[payable("*")] - #[endpoint(unbondTokens)] - fn unbond_tokens(&self) { - self.blockchain().check_caller_is_user_account(); - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt(); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - payment.token_identifier == self.unstake_token().get_token_id(), - ERROR_BAD_PAYMENT_TOKEN - ); - require!(payment.amount > 0, ERROR_BAD_PAYMENT_AMOUNT); - - let unstake_token_attributes: UnstakeTokenAttributes = self - .unstake_token() - .get_token_attributes(payment.token_nonce); - - let current_epoch = self.blockchain().get_block_epoch(); - require!( - current_epoch >= unstake_token_attributes.unbond_epoch, - ERROR_UNSTAKE_PERIOD_NOT_PASSED - ); - - let delegation_contract = unstake_token_attributes.delegation_contract; - let unstake_amount = unstake_token_attributes.unstake_amount; - let delegation_contract_mapper = self.delegation_contract_data(&delegation_contract); - let delegation_contract_data = delegation_contract_mapper.get(); - if delegation_contract_data.total_unbonded_from_ls_contract >= unstake_amount { - delegation_contract_mapper.update(|contract_data| { - contract_data.total_unstaked_from_ls_contract -= &unstake_amount; - contract_data.total_unbonded_from_ls_contract -= &unstake_amount - }); - - storage_cache.total_withdrawn_egld -= &unstake_amount; - self.unstake_token_supply() - .update(|x| *x -= &unstake_amount); - self.burn_unstake_tokens(payment.token_nonce); - self.send().direct_egld(&caller, &unstake_amount); - } else { - let gas_for_async_call = self.get_gas_for_async_call(); - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .withdraw() - .with_gas_limit(gas_for_async_call) - .async_call() - .with_callback(LiquidStaking::callbacks(self).withdraw_tokens_callback( - caller, - delegation_contract, - payment.token_nonce, - unstake_amount, - )) - .call_and_exit(); - } - } - - #[callback] - fn withdraw_tokens_callback( - &self, - caller: ManagedAddress, - delegation_contract: ManagedAddress, - unstake_token_nonce: u64, - unstake_token_amount: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - match result { - ManagedAsyncCallResult::Ok(()) => { - let withdraw_amount = self.call_value().egld_value().clone_value(); - let mut storage_cache = StorageCache::new(self); - let delegation_contract_mapper = - self.delegation_contract_data(&delegation_contract); - if withdraw_amount > 0u64 { - delegation_contract_mapper.update(|contract_data| { - contract_data.total_unbonded_from_ls_contract += &withdraw_amount - }); - storage_cache.total_withdrawn_egld += &withdraw_amount; - } - let delegation_contract_data = delegation_contract_mapper.get(); - if delegation_contract_data.total_unbonded_from_ls_contract >= unstake_token_amount - { - delegation_contract_mapper.update(|contract_data| { - contract_data.total_unstaked_from_ls_contract -= &unstake_token_amount; - contract_data.total_unbonded_from_ls_contract -= &unstake_token_amount; - }); - storage_cache.total_withdrawn_egld -= &unstake_token_amount; - self.unstake_token_supply() - .update(|x| *x -= &unstake_token_amount); - self.burn_unstake_tokens(unstake_token_nonce); - self.send().direct_egld(&caller, &unstake_token_amount); - } else { - self.send_back_unbond_nft(&caller, unstake_token_nonce); - } - } - ManagedAsyncCallResult::Err(_) => { - self.send_back_unbond_nft(&caller, unstake_token_nonce); - } - } - } - - #[endpoint(claimRewards)] - fn claim_rewards(&self) { - let storage_cache = StorageCache::new(self); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - - let delegation_addresses_mapper = self.delegation_addresses_list(); - require!( - !delegation_addresses_mapper.is_empty(), - ERROR_NO_DELEGATION_CONTRACTS - ); - let claim_status_mapper = self.delegation_claim_status(); - let old_claim_status = claim_status_mapper.get(); - let current_epoch = self.blockchain().get_block_epoch(); - let mut current_claim_status = self.load_operation::>(); - - self.check_claim_operation(¤t_claim_status, old_claim_status, current_epoch); - self.prepare_claim_operation(&mut current_claim_status, current_epoch); - - let run_result = self.run_while_it_has_gas(DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, || { - let delegation_address_node = delegation_addresses_mapper - .get_node_by_id(current_claim_status.current_node) - .unwrap(); - let next_node = delegation_address_node.get_next_node_id(); - let delegation_address = delegation_address_node.into_value(); - - self.delegation_proxy_obj() - .contract(delegation_address) - .claim_rewards() - .with_gas_limit(DEFAULT_GAS_TO_CLAIM_REWARDS) - .transfer_execute(); - - if next_node == 0 { - claim_status_mapper.set(current_claim_status.clone()); - return STOP_OP; - } else { - current_claim_status.current_node = next_node; - } - - CONTINUE_OP - }); - - match run_result { - OperationCompletionStatus::InterruptedBeforeOutOfGas => { - self.save_progress(¤t_claim_status); - } - OperationCompletionStatus::Completed => { - claim_status_mapper.update(|claim_status| { - claim_status.status = ClaimStatusType::Finished; - claim_status.last_claim_block = self.blockchain().get_block_nonce(); - }); - } - }; - } - - #[endpoint(recomputeTokenReserve)] - fn recompute_token_reserve(&self) { - let mut storage_cache = StorageCache::new(self); - let claim_status_mapper = self.delegation_claim_status(); - let mut claim_status = claim_status_mapper.get(); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - claim_status.status == ClaimStatusType::Finished, - ERROR_RECOMPUTE_RESERVES - ); - - let current_block = self.blockchain().get_block_nonce(); - require!( - current_block >= claim_status.last_claim_block + RECOMPUTE_BLOCK_OFFSET, - ERROR_RECOMPUTE_TOO_SOON - ); - - let current_egld_balance = self - .blockchain() - .get_sc_balance(&EgldOrEsdtTokenIdentifier::egld(), 0); - if current_egld_balance - > &storage_cache.total_withdrawn_egld + &claim_status.starting_token_reserve - { - let rewards = ¤t_egld_balance - - &storage_cache.total_withdrawn_egld - - &claim_status.starting_token_reserve; - storage_cache.rewards_reserve += rewards; - } - - if storage_cache.rewards_reserve >= MIN_EGLD_TO_DELEGATE { - claim_status.status = ClaimStatusType::Delegable; - } else { - claim_status.status = ClaimStatusType::Insufficient; - } - - claim_status_mapper.set(claim_status); - } - - #[endpoint(delegateRewards)] - fn delegate_rewards(&self) { - let mut storage_cache = StorageCache::new(self); - let claim_status = self.delegation_claim_status().get(); - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - claim_status.status == ClaimStatusType::Delegable, - ERROR_CLAIM_REDELEGATE - ); - - let rewards_reserve = storage_cache.rewards_reserve.clone(); - storage_cache.rewards_reserve = BigUint::zero(); - let delegation_contract = self.get_delegation_contract_for_delegate(&rewards_reserve); - let gas_for_async_call = self.get_gas_for_async_call(); - - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .delegate() - .with_gas_limit(gas_for_async_call) - .with_egld_transfer(rewards_reserve.clone()) - .async_call() - .with_callback( - LiquidStaking::callbacks(self) - .delegate_rewards_callback(delegation_contract, rewards_reserve), - ) - .call_and_exit() - } - - #[callback] - fn delegate_rewards_callback( - &self, - delegation_contract: ManagedAddress, - staked_tokens: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - let mut storage_cache = StorageCache::new(self); - match result { - ManagedAsyncCallResult::Ok(()) => { - self.delegation_contract_data(&delegation_contract) - .update(|contract_data| { - contract_data.total_staked_from_ls_contract += &staked_tokens; - }); - - self.delegation_claim_status() - .update(|claim_status| claim_status.status = ClaimStatusType::Redelegated); - - storage_cache.virtual_egld_reserve += &staked_tokens; - let sc_address = self.blockchain().get_sc_address(); - self.emit_add_liquidity_event(&storage_cache, &sc_address, BigUint::zero()); - } - ManagedAsyncCallResult::Err(_) => { - storage_cache.rewards_reserve = staked_tokens; - self.move_delegation_contract_to_back(delegation_contract); - } - } - } - - fn get_gas_for_async_call(&self) -> u64 { - let gas_left = self.blockchain().get_gas_left(); - require!( - gas_left > MIN_GAS_FOR_ASYNC_CALL + MIN_GAS_FOR_CALLBACK, - ERROR_INSUFFICIENT_GAS - ); - gas_left - MIN_GAS_FOR_CALLBACK - } - - fn send_back_unbond_nft(&self, caller: &ManagedAddress, unstake_token_nonce: u64) { - let unstake_token_id = self.unstake_token().get_token_id(); - self.send().direct_esdt( - caller, - &unstake_token_id, - unstake_token_nonce, - &BigUint::from(1u64), - ) - } - - // views - #[view(getLsValueForPosition)] - fn get_ls_value_for_position(&self, ls_token_amount: BigUint) -> BigUint { - let storage_cache = StorageCache::new(self); - self.get_egld_amount(&ls_token_amount, &storage_cache) + self.delegation_claim_status().set(claim_status); } - // proxy - - #[proxy] - fn delegation_proxy_obj(&self) -> delegation_proxy::Proxy; + #[upgrade] + fn upgrade(&self) {} } diff --git a/liquid-staking/src/user_actions/add_liquidity.rs b/liquid-staking/src/user_actions/add_liquidity.rs new file mode 100644 index 0000000..e92e11a --- /dev/null +++ b/liquid-staking/src/user_actions/add_liquidity.rs @@ -0,0 +1,83 @@ +use crate::{ + delegation_proxy::ProxyTrait as _, user_actions::common::MIN_EGLD_TO_DELEGATE, StorageCache, + ERROR_BAD_PAYMENT_AMOUNT, ERROR_NOT_ACTIVE, +}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait AddLiquidityModule: + crate::config::ConfigModule + + crate::events::EventsModule + + crate::delegation::DelegationModule + + crate::liquidity_pool::LiquidityPoolModule + + multiversx_sc_modules::ongoing_operation::OngoingOperationModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + super::common::CommonModule +{ + #[payable("EGLD")] + #[endpoint(addLiquidity)] + fn add_liquidity(&self) { + self.blockchain().check_caller_is_user_account(); + + let storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); + + let payment = self.call_value().egld_value().clone_value(); + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + require!(payment >= MIN_EGLD_TO_DELEGATE, ERROR_BAD_PAYMENT_AMOUNT); + + let delegation_contract = self.get_delegation_contract_for_delegate(&payment); + let gas_for_async_call = self.get_gas_for_async_call(); + + self.delegation_proxy_obj() + .contract(delegation_contract.clone()) + .delegate() + .with_gas_limit(gas_for_async_call) + .with_egld_transfer(payment.clone()) + .async_call() + .with_callback(AddLiquidityModule::callbacks(self).add_liquidity_callback( + caller, + delegation_contract, + payment, + )) + .call_and_exit() + } + + #[callback] + fn add_liquidity_callback( + &self, + caller: ManagedAddress, + delegation_contract: ManagedAddress, + staked_tokens: BigUint, + #[call_result] result: ManagedAsyncCallResult<()>, + ) { + match result { + ManagedAsyncCallResult::Ok(()) => { + let mut storage_cache = StorageCache::new(self); + self.delegation_contract_data(&delegation_contract) + .update(|contract_data| { + contract_data.total_staked_from_ls_contract += &staked_tokens; + }); + + let ls_token_amount = self.pool_add_liquidity(&staked_tokens, &mut storage_cache); + let user_payment = self.mint_ls_token(ls_token_amount); + self.send().direct_esdt( + &caller, + &user_payment.token_identifier, + user_payment.token_nonce, + &user_payment.amount, + ); + + self.emit_add_liquidity_event(&storage_cache, &caller, user_payment.amount); + } + ManagedAsyncCallResult::Err(_) => { + self.send().direct_egld(&caller, &staked_tokens); + self.move_delegation_contract_to_back(delegation_contract); + } + } + } +} diff --git a/liquid-staking/src/user_actions/claim_rewards.rs b/liquid-staking/src/user_actions/claim_rewards.rs new file mode 100644 index 0000000..b687adc --- /dev/null +++ b/liquid-staking/src/user_actions/claim_rewards.rs @@ -0,0 +1,82 @@ +use multiversx_sc_modules::ongoing_operation::{ + CONTINUE_OP, DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, STOP_OP, +}; + +use crate::{ + delegation::{ClaimStatus, ClaimStatusType}, + delegation_proxy::ProxyTrait as _, + StorageCache, ERROR_NOT_ACTIVE, ERROR_NO_DELEGATION_CONTRACTS, +}; + +multiversx_sc::imports!(); + +pub const DEFAULT_GAS_TO_CLAIM_REWARDS: u64 = 6_000_000; + +#[multiversx_sc::module] +pub trait ClaimRewardsModule: + crate::config::ConfigModule + + crate::events::EventsModule + + crate::delegation::DelegationModule + + crate::liquidity_pool::LiquidityPoolModule + + multiversx_sc_modules::ongoing_operation::OngoingOperationModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + super::common::CommonModule +{ + #[endpoint(claimRewards)] + fn claim_rewards(&self) { + let storage_cache = StorageCache::new(self); + + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + + let delegation_addresses_mapper = self.delegation_addresses_list(); + require!( + !delegation_addresses_mapper.is_empty(), + ERROR_NO_DELEGATION_CONTRACTS + ); + let claim_status_mapper = self.delegation_claim_status(); + let old_claim_status = claim_status_mapper.get(); + let current_epoch = self.blockchain().get_block_epoch(); + let mut current_claim_status = self.load_operation::>(); + + self.check_claim_operation(¤t_claim_status, old_claim_status, current_epoch); + self.prepare_claim_operation(&mut current_claim_status, current_epoch); + + let run_result = self.run_while_it_has_gas(DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, || { + let delegation_address_node = delegation_addresses_mapper + .get_node_by_id(current_claim_status.current_node) + .unwrap(); + let next_node = delegation_address_node.get_next_node_id(); + let delegation_address = delegation_address_node.into_value(); + + self.delegation_proxy_obj() + .contract(delegation_address) + .claim_rewards() + .with_gas_limit(DEFAULT_GAS_TO_CLAIM_REWARDS) + .transfer_execute(); + + if next_node == 0 { + claim_status_mapper.set(current_claim_status.clone()); + return STOP_OP; + } else { + current_claim_status.current_node = next_node; + } + + CONTINUE_OP + }); + + match run_result { + OperationCompletionStatus::InterruptedBeforeOutOfGas => { + self.save_progress(¤t_claim_status); + } + OperationCompletionStatus::Completed => { + claim_status_mapper.update(|claim_status| { + claim_status.status = ClaimStatusType::Finished; + claim_status.last_claim_block = self.blockchain().get_block_nonce(); + }); + } + }; + } +} diff --git a/liquid-staking/src/user_actions/common.rs b/liquid-staking/src/user_actions/common.rs new file mode 100644 index 0000000..2fa401c --- /dev/null +++ b/liquid-staking/src/user_actions/common.rs @@ -0,0 +1,44 @@ +use crate::{delegation_proxy, StorageCache, ERROR_INSUFFICIENT_GAS}; + +multiversx_sc::imports!(); + +pub const MIN_GAS_FOR_CALLBACK: u64 = 12_000_000; +pub const MIN_GAS_FOR_ASYNC_CALL: u64 = 12_000_000; +pub const MIN_EGLD_TO_DELEGATE: u64 = 1_000_000_000_000_000_000; + +#[multiversx_sc::module] +pub trait CommonModule: + crate::config::ConfigModule + + crate::liquidity_pool::LiquidityPoolModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule +{ + // views + #[view(getLsValueForPosition)] + fn get_ls_value_for_position(&self, ls_token_amount: BigUint) -> BigUint { + let storage_cache = StorageCache::new(self); + self.get_egld_amount(&ls_token_amount, &storage_cache) + } + + fn get_gas_for_async_call(&self) -> u64 { + let gas_left = self.blockchain().get_gas_left(); + require!( + gas_left > MIN_GAS_FOR_ASYNC_CALL + MIN_GAS_FOR_CALLBACK, + ERROR_INSUFFICIENT_GAS + ); + + gas_left - MIN_GAS_FOR_CALLBACK + } + + fn send_back_unbond_nft(&self, caller: &ManagedAddress, unstake_token_nonce: u64) { + let unstake_token_id = self.unstake_token().get_token_id(); + self.send().direct_esdt( + caller, + &unstake_token_id, + unstake_token_nonce, + &BigUint::from(1u64), + ) + } + + #[proxy] + fn delegation_proxy_obj(&self) -> delegation_proxy::Proxy; +} diff --git a/liquid-staking/src/user_actions/delegate_rewards.rs b/liquid-staking/src/user_actions/delegate_rewards.rs new file mode 100644 index 0000000..f992e90 --- /dev/null +++ b/liquid-staking/src/user_actions/delegate_rewards.rs @@ -0,0 +1,77 @@ +use crate::{ + delegation::ClaimStatusType, delegation_proxy::ProxyTrait as _, StorageCache, + ERROR_CLAIM_REDELEGATE, ERROR_NOT_ACTIVE, +}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait DelegateRewardsModule: + crate::config::ConfigModule + + crate::events::EventsModule + + crate::delegation::DelegationModule + + crate::liquidity_pool::LiquidityPoolModule + + multiversx_sc_modules::ongoing_operation::OngoingOperationModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + super::common::CommonModule +{ + #[endpoint(delegateRewards)] + fn delegate_rewards(&self) { + let mut storage_cache = StorageCache::new(self); + let claim_status = self.delegation_claim_status().get(); + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + require!( + claim_status.status == ClaimStatusType::Delegable, + ERROR_CLAIM_REDELEGATE + ); + + let rewards_reserve = storage_cache.rewards_reserve.clone(); + storage_cache.rewards_reserve = BigUint::zero(); + let delegation_contract = self.get_delegation_contract_for_delegate(&rewards_reserve); + let gas_for_async_call = self.get_gas_for_async_call(); + + self.delegation_proxy_obj() + .contract(delegation_contract.clone()) + .delegate() + .with_gas_limit(gas_for_async_call) + .with_egld_transfer(rewards_reserve.clone()) + .async_call() + .with_callback( + DelegateRewardsModule::callbacks(self) + .delegate_rewards_callback(delegation_contract, rewards_reserve), + ) + .call_and_exit() + } + + #[callback] + fn delegate_rewards_callback( + &self, + delegation_contract: ManagedAddress, + staked_tokens: BigUint, + #[call_result] result: ManagedAsyncCallResult<()>, + ) { + let mut storage_cache = StorageCache::new(self); + match result { + ManagedAsyncCallResult::Ok(()) => { + self.delegation_contract_data(&delegation_contract) + .update(|contract_data| { + contract_data.total_staked_from_ls_contract += &staked_tokens; + }); + + self.delegation_claim_status() + .update(|claim_status| claim_status.status = ClaimStatusType::Redelegated); + + storage_cache.virtual_egld_reserve += &staked_tokens; + let sc_address = self.blockchain().get_sc_address(); + self.emit_add_liquidity_event(&storage_cache, &sc_address, BigUint::zero()); + } + ManagedAsyncCallResult::Err(_) => { + storage_cache.rewards_reserve = staked_tokens; + self.move_delegation_contract_to_back(delegation_contract); + } + } + } +} diff --git a/liquid-staking/src/user_actions/mod.rs b/liquid-staking/src/user_actions/mod.rs new file mode 100644 index 0000000..cf1630d --- /dev/null +++ b/liquid-staking/src/user_actions/mod.rs @@ -0,0 +1,7 @@ +pub mod add_liquidity; +pub mod claim_rewards; +pub mod common; +pub mod delegate_rewards; +pub mod recompute_token_reserve; +pub mod remove_liquidity; +pub mod unbond; diff --git a/liquid-staking/src/user_actions/recompute_token_reserve.rs b/liquid-staking/src/user_actions/recompute_token_reserve.rs new file mode 100644 index 0000000..cd1e4bb --- /dev/null +++ b/liquid-staking/src/user_actions/recompute_token_reserve.rs @@ -0,0 +1,61 @@ +use crate::{ + delegation::ClaimStatusType, user_actions::common::MIN_EGLD_TO_DELEGATE, StorageCache, + ERROR_NOT_ACTIVE, ERROR_RECOMPUTE_RESERVES, ERROR_RECOMPUTE_TOO_SOON, +}; + +multiversx_sc::imports!(); + +pub const RECOMPUTE_BLOCK_OFFSET: u64 = 10; + +#[multiversx_sc::module] +pub trait RecomputeTokenReserveModule: + crate::config::ConfigModule + + crate::events::EventsModule + + crate::delegation::DelegationModule + + crate::liquidity_pool::LiquidityPoolModule + + multiversx_sc_modules::ongoing_operation::OngoingOperationModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + super::common::CommonModule +{ + #[endpoint(recomputeTokenReserve)] + fn recompute_token_reserve(&self) { + let mut storage_cache = StorageCache::new(self); + let claim_status_mapper = self.delegation_claim_status(); + let mut claim_status = claim_status_mapper.get(); + + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + require!( + claim_status.status == ClaimStatusType::Finished, + ERROR_RECOMPUTE_RESERVES + ); + + let current_block = self.blockchain().get_block_nonce(); + require!( + current_block >= claim_status.last_claim_block + RECOMPUTE_BLOCK_OFFSET, + ERROR_RECOMPUTE_TOO_SOON + ); + + let current_egld_balance = self + .blockchain() + .get_sc_balance(&EgldOrEsdtTokenIdentifier::egld(), 0); + if current_egld_balance + > &storage_cache.total_withdrawn_egld + &claim_status.starting_token_reserve + { + let rewards = ¤t_egld_balance + - &storage_cache.total_withdrawn_egld + - &claim_status.starting_token_reserve; + storage_cache.rewards_reserve += rewards; + } + + if storage_cache.rewards_reserve >= MIN_EGLD_TO_DELEGATE { + claim_status.status = ClaimStatusType::Delegable; + } else { + claim_status.status = ClaimStatusType::Insufficient; + } + + claim_status_mapper.set(claim_status); + } +} diff --git a/liquid-staking/src/user_actions/remove_liquidity.rs b/liquid-staking/src/user_actions/remove_liquidity.rs new file mode 100644 index 0000000..1f8bb49 --- /dev/null +++ b/liquid-staking/src/user_actions/remove_liquidity.rs @@ -0,0 +1,126 @@ +use crate::{ + config::{UnstakeTokenAttributes, UNBOND_PERIOD}, + delegation_proxy::ProxyTrait as _, + user_actions::common::MIN_EGLD_TO_DELEGATE, + StorageCache, ERROR_BAD_PAYMENT_AMOUNT, ERROR_BAD_PAYMENT_TOKEN, + ERROR_INSUFFICIENT_UNSTAKE_AMOUNT, ERROR_LS_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, +}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait RemoveLiquidityModule: + crate::config::ConfigModule + + crate::events::EventsModule + + crate::delegation::DelegationModule + + crate::liquidity_pool::LiquidityPoolModule + + multiversx_sc_modules::ongoing_operation::OngoingOperationModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + super::common::CommonModule +{ + #[payable("*")] + #[endpoint(removeLiquidity)] + fn remove_liquidity(&self) { + self.blockchain().check_caller_is_user_account(); + let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); + let payment = self.call_value().single_esdt(); + + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + require!( + storage_cache.ls_token_id.is_valid_esdt_identifier(), + ERROR_LS_TOKEN_NOT_ISSUED + ); + require!( + payment.token_identifier == storage_cache.ls_token_id, + ERROR_BAD_PAYMENT_TOKEN + ); + require!(payment.amount > 0, ERROR_BAD_PAYMENT_AMOUNT); + + let egld_to_unstake = self.pool_remove_liquidity(&payment.amount, &mut storage_cache); + require!( + egld_to_unstake >= MIN_EGLD_TO_DELEGATE, + ERROR_INSUFFICIENT_UNSTAKE_AMOUNT + ); + self.burn_ls_token(&payment.amount); + + let delegation_contract = self.get_delegation_contract_for_undelegate(&egld_to_unstake); + let gas_for_async_call = self.get_gas_for_async_call(); + + self.delegation_proxy_obj() + .contract(delegation_contract.clone()) + .undelegate(egld_to_unstake.clone()) + .with_gas_limit(gas_for_async_call) + .async_call() + .with_callback( + RemoveLiquidityModule::callbacks(self).remove_liquidity_callback( + caller, + delegation_contract, + egld_to_unstake, + payment.amount, + ), + ) + .call_and_exit() + } + + #[callback] + fn remove_liquidity_callback( + &self, + caller: ManagedAddress, + delegation_contract: ManagedAddress, + egld_to_unstake: BigUint, + ls_tokens_to_be_burned: BigUint, + #[call_result] result: ManagedAsyncCallResult<()>, + ) { + let mut storage_cache = StorageCache::new(self); + match result { + ManagedAsyncCallResult::Ok(()) => { + let current_epoch = self.blockchain().get_block_epoch(); + let unbond_epoch = current_epoch + UNBOND_PERIOD; + + self.delegation_contract_data(&delegation_contract) + .update(|contract_data| { + contract_data.total_staked_from_ls_contract -= &egld_to_unstake; + contract_data.total_unstaked_from_ls_contract += &egld_to_unstake; + }); + self.unstake_token_supply() + .update(|x| *x += &egld_to_unstake); + + let virtual_position = UnstakeTokenAttributes { + delegation_contract, + unstake_epoch: current_epoch, + unstake_amount: egld_to_unstake, + unbond_epoch, + }; + + let user_payment = self.mint_unstake_tokens(&virtual_position); + self.send().direct_esdt( + &caller, + &user_payment.token_identifier, + user_payment.token_nonce, + &user_payment.amount, + ); + + self.emit_remove_liquidity_event( + &storage_cache, + ls_tokens_to_be_burned, + user_payment.amount, + ); + } + ManagedAsyncCallResult::Err(_) => { + let ls_token_amount = self.pool_add_liquidity(&egld_to_unstake, &mut storage_cache); + let user_payment = self.mint_ls_token(ls_token_amount); + self.send().direct_esdt( + &caller, + &user_payment.token_identifier, + user_payment.token_nonce, + &user_payment.amount, + ); + self.move_delegation_contract_to_back(delegation_contract); + } + } + } +} diff --git a/liquid-staking/src/user_actions/unbond.rs b/liquid-staking/src/user_actions/unbond.rs new file mode 100644 index 0000000..090b72f --- /dev/null +++ b/liquid-staking/src/user_actions/unbond.rs @@ -0,0 +1,121 @@ +use crate::{ + config::UnstakeTokenAttributes, delegation_proxy::ProxyTrait as _, StorageCache, + ERROR_BAD_PAYMENT_AMOUNT, ERROR_BAD_PAYMENT_TOKEN, ERROR_NOT_ACTIVE, + ERROR_UNSTAKE_PERIOD_NOT_PASSED, +}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait UnbondModule: + crate::config::ConfigModule + + crate::events::EventsModule + + crate::delegation::DelegationModule + + crate::liquidity_pool::LiquidityPoolModule + + multiversx_sc_modules::ongoing_operation::OngoingOperationModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + super::common::CommonModule +{ + #[payable("*")] + #[endpoint(unbondTokens)] + fn unbond_tokens(&self) { + self.blockchain().check_caller_is_user_account(); + let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); + let payment = self.call_value().single_esdt(); + + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + require!( + payment.token_identifier == self.unstake_token().get_token_id(), + ERROR_BAD_PAYMENT_TOKEN + ); + require!(payment.amount > 0, ERROR_BAD_PAYMENT_AMOUNT); + + let unstake_token_attributes: UnstakeTokenAttributes = self + .unstake_token() + .get_token_attributes(payment.token_nonce); + + let current_epoch = self.blockchain().get_block_epoch(); + require!( + current_epoch >= unstake_token_attributes.unbond_epoch, + ERROR_UNSTAKE_PERIOD_NOT_PASSED + ); + + let delegation_contract = unstake_token_attributes.delegation_contract; + let unstake_amount = unstake_token_attributes.unstake_amount; + let delegation_contract_mapper = self.delegation_contract_data(&delegation_contract); + let delegation_contract_data = delegation_contract_mapper.get(); + if delegation_contract_data.total_unbonded_from_ls_contract >= unstake_amount { + delegation_contract_mapper.update(|contract_data| { + contract_data.total_unstaked_from_ls_contract -= &unstake_amount; + contract_data.total_unbonded_from_ls_contract -= &unstake_amount + }); + + storage_cache.total_withdrawn_egld -= &unstake_amount; + self.unstake_token_supply() + .update(|x| *x -= &unstake_amount); + self.burn_unstake_tokens(payment.token_nonce); + self.send().direct_egld(&caller, &unstake_amount); + } else { + let gas_for_async_call = self.get_gas_for_async_call(); + self.delegation_proxy_obj() + .contract(delegation_contract.clone()) + .withdraw() + .with_gas_limit(gas_for_async_call) + .async_call() + .with_callback(UnbondModule::callbacks(self).withdraw_tokens_callback( + caller, + delegation_contract, + payment.token_nonce, + unstake_amount, + )) + .call_and_exit(); + } + } + + #[callback] + fn withdraw_tokens_callback( + &self, + caller: ManagedAddress, + delegation_contract: ManagedAddress, + unstake_token_nonce: u64, + unstake_token_amount: BigUint, + #[call_result] result: ManagedAsyncCallResult<()>, + ) { + match result { + ManagedAsyncCallResult::Ok(()) => { + let withdraw_amount = self.call_value().egld_value().clone_value(); + let mut storage_cache = StorageCache::new(self); + let delegation_contract_mapper = + self.delegation_contract_data(&delegation_contract); + if withdraw_amount > 0u64 { + delegation_contract_mapper.update(|contract_data| { + contract_data.total_unbonded_from_ls_contract += &withdraw_amount + }); + storage_cache.total_withdrawn_egld += &withdraw_amount; + } + let delegation_contract_data = delegation_contract_mapper.get(); + if delegation_contract_data.total_unbonded_from_ls_contract >= unstake_token_amount + { + delegation_contract_mapper.update(|contract_data| { + contract_data.total_unstaked_from_ls_contract -= &unstake_token_amount; + contract_data.total_unbonded_from_ls_contract -= &unstake_token_amount; + }); + storage_cache.total_withdrawn_egld -= &unstake_token_amount; + self.unstake_token_supply() + .update(|x| *x -= &unstake_token_amount); + self.burn_unstake_tokens(unstake_token_nonce); + self.send().direct_egld(&caller, &unstake_token_amount); + } else { + self.send_back_unbond_nft(&caller, unstake_token_nonce); + } + } + ManagedAsyncCallResult::Err(_) => { + self.send_back_unbond_nft(&caller, unstake_token_nonce); + } + } + } +} diff --git a/liquid-staking/tests/contract_interactions/mod.rs b/liquid-staking/tests/contract_interactions/mod.rs index 6ce6702..9861e8a 100644 --- a/liquid-staking/tests/contract_interactions/mod.rs +++ b/liquid-staking/tests/contract_interactions/mod.rs @@ -1,6 +1,12 @@ use crate::contract_setup::LiquidStakingContractSetup; use liquid_staking::config::{ConfigModule, UnstakeTokenAttributes}; -use liquid_staking::LiquidStaking; +use liquid_staking::user_actions::add_liquidity::AddLiquidityModule; +use liquid_staking::user_actions::claim_rewards::ClaimRewardsModule; +use liquid_staking::user_actions::common::CommonModule; +use liquid_staking::user_actions::delegate_rewards::DelegateRewardsModule; +use liquid_staking::user_actions::recompute_token_reserve::RecomputeTokenReserveModule; +use liquid_staking::user_actions::remove_liquidity::RemoveLiquidityModule; +use liquid_staking::user_actions::unbond::UnbondModule; use multiversx_sc::types::Address; use multiversx_sc_scenario::{managed_address, num_bigint, rust_biguint, DebugApi}; diff --git a/liquid-staking/wasm/src/lib.rs b/liquid-staking/wasm/src/lib.rs index 0b09c8c..aed2896 100644 --- a/liquid-staking/wasm/src/lib.rs +++ b/liquid-staking/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 31 +// Endpoints: 32 // Async Callback: 1 -// Total number of exported functions: 33 +// Total number of exported functions: 34 #![no_std] #![allow(internal_features)] @@ -20,13 +20,7 @@ multiversx_sc_wasm_adapter::endpoints! { liquid_staking ( init => init - addLiquidity => add_liquidity - removeLiquidity => remove_liquidity - unbondTokens => unbond_tokens - claimRewards => claim_rewards - recomputeTokenReserve => recompute_token_reserve - delegateRewards => delegate_rewards - getLsValueForPosition => get_ls_value_for_position + upgrade => upgrade registerLsToken => register_ls_token registerUnstakeToken => register_unstake_token setStateActive => set_state_active @@ -51,6 +45,13 @@ multiversx_sc_wasm_adapter::endpoints! { getDelegationClaimStatus => delegation_claim_status maxDelegationAddresses => max_delegation_addresses getDelegationContractData => delegation_contract_data + getLsValueForPosition => get_ls_value_for_position + addLiquidity => add_liquidity + removeLiquidity => remove_liquidity + unbondTokens => unbond_tokens + claimRewards => claim_rewards + delegateRewards => delegate_rewards + recomputeTokenReserve => recompute_token_reserve ) } From 5d86294848ca204d85a5c5a891d302a87c4e125b Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Tue, 14 May 2024 12:18:37 +0300 Subject: [PATCH 5/5] rip --- Cargo.lock | 28 -- Cargo.toml | 2 - liquid-staking/.gitignore | 7 - liquid-staking/Cargo.toml | 25 -- liquid-staking/interaction/devnet.snippets.sh | 271 -------------- liquid-staking/meta/Cargo.toml | 11 - liquid-staking/meta/src/main.rs | 3 - liquid-staking/multiversx.json | 3 - liquid-staking/src/config.rs | 112 ------ liquid-staking/src/contexts/base.rs | 51 --- liquid-staking/src/contexts/mod.rs | 1 - liquid-staking/src/delegation.rs | 347 ------------------ liquid-staking/src/delegation_proxy.rs | 17 - liquid-staking/src/errors.rs | 34 -- liquid-staking/src/events.rs | 111 ------ liquid-staking/src/lib.rs | 64 ---- liquid-staking/src/liquidity_pool.rs | 87 ----- .../src/user_actions/add_liquidity.rs | 83 ----- .../src/user_actions/claim_rewards.rs | 82 ----- liquid-staking/src/user_actions/common.rs | 44 --- .../src/user_actions/delegate_rewards.rs | 77 ---- liquid-staking/src/user_actions/mod.rs | 7 - .../user_actions/recompute_token_reserve.rs | 61 --- .../src/user_actions/remove_liquidity.rs | 126 ------- liquid-staking/src/user_actions/unbond.rs | 121 ------ liquid-staking/testnet.toml | 0 .../tests/contract_interactions/mod.rs | 327 ----------------- liquid-staking/tests/contract_setup/mod.rs | 102 ----- liquid-staking/tests/test.rs | 278 -------------- liquid-staking/wasm/Cargo.lock | 215 ----------- liquid-staking/wasm/Cargo.toml | 31 -- liquid-staking/wasm/src/lib.rs | 58 --- 32 files changed, 2786 deletions(-) delete mode 100644 liquid-staking/.gitignore delete mode 100644 liquid-staking/Cargo.toml delete mode 100644 liquid-staking/interaction/devnet.snippets.sh delete mode 100644 liquid-staking/meta/Cargo.toml delete mode 100644 liquid-staking/meta/src/main.rs delete mode 100644 liquid-staking/multiversx.json delete mode 100644 liquid-staking/src/config.rs delete mode 100644 liquid-staking/src/contexts/base.rs delete mode 100644 liquid-staking/src/contexts/mod.rs delete mode 100644 liquid-staking/src/delegation.rs delete mode 100644 liquid-staking/src/delegation_proxy.rs delete mode 100644 liquid-staking/src/errors.rs delete mode 100644 liquid-staking/src/events.rs delete mode 100644 liquid-staking/src/lib.rs delete mode 100644 liquid-staking/src/liquidity_pool.rs delete mode 100644 liquid-staking/src/user_actions/add_liquidity.rs delete mode 100644 liquid-staking/src/user_actions/claim_rewards.rs delete mode 100644 liquid-staking/src/user_actions/common.rs delete mode 100644 liquid-staking/src/user_actions/delegate_rewards.rs delete mode 100644 liquid-staking/src/user_actions/mod.rs delete mode 100644 liquid-staking/src/user_actions/recompute_token_reserve.rs delete mode 100644 liquid-staking/src/user_actions/remove_liquidity.rs delete mode 100644 liquid-staking/src/user_actions/unbond.rs delete mode 100644 liquid-staking/testnet.toml delete mode 100644 liquid-staking/tests/contract_interactions/mod.rs delete mode 100644 liquid-staking/tests/contract_setup/mod.rs delete mode 100644 liquid-staking/tests/test.rs delete mode 100644 liquid-staking/wasm/Cargo.lock delete mode 100644 liquid-staking/wasm/Cargo.toml delete mode 100644 liquid-staking/wasm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a5edd6f..495b6d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,25 +898,6 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" -[[package]] -name = "liquid-staking" -version = "0.0.0" -dependencies = [ - "delegation-mock", - "itertools 0.10.5", - "multiversx-sc", - "multiversx-sc-modules", - "multiversx-sc-scenario", -] - -[[package]] -name = "liquid-staking-abi" -version = "0.0.0" -dependencies = [ - "liquid-staking", - "multiversx-sc-meta", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -1099,15 +1080,6 @@ dependencies = [ "zip", ] -[[package]] -name = "multiversx-sc-modules" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e507a180afcab409cc3d920bc12f3852cf481a6657428879d1a70f6c2666c94" -dependencies = [ - "multiversx-sc", -] - [[package]] name = "multiversx-sc-scenario" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index e0c2c4b..6b2e4d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,6 @@ resolver = "2" members = [ "gravity-restaking", "gravity-restaking/meta", - "liquid-staking", - "liquid-staking/meta", "delegation-mock", "delegation-mock/meta" ] diff --git a/liquid-staking/.gitignore b/liquid-staking/.gitignore deleted file mode 100644 index eaf5915..0000000 --- a/liquid-staking/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -*/target/ - -# The erdpy output -output diff --git a/liquid-staking/Cargo.toml b/liquid-staking/Cargo.toml deleted file mode 100644 index 8585289..0000000 --- a/liquid-staking/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "liquid-staking" -version = "0.0.0" -authors = ["Sorin Petreasca "] -edition = "2021" -publish = false - -[lib] -path = "src/lib.rs" - -[dependencies.itertools] -version = "0.10.1" -default-features = false - -[dependencies.multiversx-sc] -version = "=0.48.0" - -[dependencies.multiversx-sc-modules] -version = "=0.48.0" - -[dependencies.delegation-mock] -path = "../delegation-mock" - -[dev-dependencies.multiversx-sc-scenario] -version = "=0.48.0" diff --git a/liquid-staking/interaction/devnet.snippets.sh b/liquid-staking/interaction/devnet.snippets.sh deleted file mode 100644 index 02a7bc2..0000000 --- a/liquid-staking/interaction/devnet.snippets.sh +++ /dev/null @@ -1,271 +0,0 @@ -WALLET_PEM="/Users/sorinpetreasca/DevKitt/walletKey.pem" -WALLET_PEM2="/Users/sorinpetreasca/DevKitt/walletKey2.pem" -WALLET_PEM3="/Users/sorinpetreasca/DevKitt/walletKey3.pem" -PROXY="https://testnet-gateway.elrond.com" -CHAIN_ID="T" - -LIQUID_STAKING_WASM_PATH="/Users/sorinpetreasca/Elrond/sc-liquid-staking-rs/liquid-staking/output/liquid-staking.wasm" - -OWNER_ADDRESS="erd14nw9pukqyqu75gj0shm8upsegjft8l0awjefp877phfx74775dsq49swp3" -CONTRACT_ADDRESS="erd1qqqqqqqqqqqqqpgq4dzfldya86ht366xrd4w78809rlxcfzn5dsqdnffz7" - -deploySC() { - erdpy --verbose contract deploy --recall-nonce \ - --bytecode=${LIQUID_STAKING_WASM_PATH} \ - --pem=${WALLET_PEM} \ - --gas-limit=100000000 \ - --metadata-payable-by-sc \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --send || return -} - -upgradeSC() { - erdpy --verbose contract upgrade ${CONTRACT_ADDRESS} --recall-nonce \ - --bytecode=${LIQUID_STAKING_WASM_PATH} \ - --pem=${WALLET_PEM} \ - --gas-limit=100000000 \ - --metadata-payable-by-sc \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --send || return -} - -TOKEN_NAME=0x4c53544f4b454e #LSTOKEN -TOKEN_TICKER=0x4c5354 #LST -TOKEN_DECIMALS=18 -registerLsToken() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=100000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --value=50000000000000000 \ - --function="registerLsToken" \ - --arguments ${TOKEN_NAME} ${TOKEN_TICKER} ${TOKEN_DECIMALS} \ - --send || return -} - -UNSTAKE_TOKEN_NAME=0x4c53554e5354414b45 #LSUNSTAKE -UNSTAKE_TOKEN_TICKER=0x4c5355 #LSU -registerUnstakeToken() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=100000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --value=50000000000000000 \ - --function="registerUnstakeToken" \ - --arguments ${UNSTAKE_TOKEN_NAME} ${UNSTAKE_TOKEN_TICKER} ${TOKEN_DECIMALS} \ - --send || return -} - -setStateActive() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=6000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="setStateActive" \ - --send || return -} - -setStateInactive() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=6000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="setStateInactive" \ - --send || return -} - -###PARAMS -### Contracts - erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqq80llllsrepk69 erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxlllllsmehg53 -DELEGATION_ADDRESS="erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqq80llllsrepk69" -TOTAL_STAKED=1000000000000000000000 -DELEGATION_CAP=3000000000000000000000 -NR_NODES=3 -APY=1974 -whitelistDelegationContract() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=10000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="whitelistDelegationContract" \ - --arguments ${DELEGATION_ADDRESS} ${OWNER_ADDRESS} ${TOTAL_STAKED} ${DELEGATION_CAP} ${NR_NODES} ${APY}\ - --send || return -} - -NEW_TOTAL_STAKED=1500000000000000000000 -NEW_DELEGATION_CAP=5000000000000000000000 -NEW_APY=18830 -changeDelegationContractParams() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=60000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="changeDelegationContractParams" \ - --arguments ${DELEGATION_ADDRESS} ${NEW_TOTAL_STAKED} ${NEW_DELEGATION_CAP} ${NR_NODES} ${NEW_APY}\ - --send || return -} - -###PARAMS -#1 - Amount -addLiquidity() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=60000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --value=$1 \ - --function="addLiquidity" \ - --send || return -} - -###PARAMS -#1 - Amount -LS_TOKEN=str:LST-05ef8b -REMOVE_LIQUIDITY_METHOD=str:removeLiquidity -removeLiquidity() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=60000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="ESDTTransfer" \ - --arguments ${LS_TOKEN} $1 ${REMOVE_LIQUIDITY_METHOD} \ - --send || return -} - -###PARAMS -## TESTING: 1437 unstake epoch -#1 - Nonce -unbondTokens() { - user_address="$(erdpy wallet pem-address $WALLET_PEM2)" - method_name=str:unbondTokens - unbond_token=str:LSU-1cd7b4 - unbond_token_nonce=$1 - UNBOND_TOKEN_AMOUNT=1 - erdpy --verbose contract call $user_address --recall-nonce \ - --pem=${WALLET_PEM2} \ - --gas-limit=60000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="ESDTNFTTransfer" \ - --arguments $unbond_token $1 ${UNBOND_TOKEN_AMOUNT} ${CONTRACT_ADDRESS} $method_name \ - --send || return -} - -claimRewards() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=60000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="claimRewards" \ - --send || return -} - -recomputeTokenReserve() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=6000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="recomputeTokenReserve" \ - --send || return -} - -delegateRewards() { - erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \ - --pem=${WALLET_PEM} \ - --gas-limit=60000000 \ - --proxy=${PROXY} --chain=${CHAIN_ID} \ - --function="delegateRewards" \ - --send || return -} - -# VIEWS - -getLsTokenId() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getLsTokenId" \ -} - -###PARAMS -#1 - Amount -getLsValueForPosition() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --arguments $1 \ - --function="getLsValueForPosition" \ -} - -getUnstakeTokenId() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getUnstakeTokenId" \ -} - -getUnstakeTokenSupply() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getUnstakeTokenSupply" \ -} - -getVirtualEgldReserve() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getVirtualEgldReserve" \ -} - -getRewardsReserve() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getRewardsReserve" \ -} - -getTotalWithdrawnEgld() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getTotalWithdrawnEgld" \ -} - -getDelegationAddressesList() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getDelegationAddressesList" \ -} - -###PARAMS -#1 - Address -getDelegationContractData() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --arguments $1 \ - --function="getDelegationContractData" \ -} - -getDelegationStatus() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getDelegationStatus" \ -} - -getDelegationClaimStatus() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --function="getDelegationClaimStatus" \ -} - -getDelegationContractStakedAmount() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --arguments ${DELEGATION_ADDRESS} \ - --function="getDelegationContractStakedAmount" \ -} - -getDelegationContractUnstakedAmount() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --arguments ${DELEGATION_ADDRESS} \ - --function="getDelegationContractUnstakedAmount" \ -} - -getDelegationContractUnbondedAmount() { - erdpy --verbose contract query ${CONTRACT_ADDRESS} \ - --proxy=${PROXY} \ - --arguments ${DELEGATION_ADDRESS} \ - --function="getDelegationContractUnbondedAmount" \ -} diff --git a/liquid-staking/meta/Cargo.toml b/liquid-staking/meta/Cargo.toml deleted file mode 100644 index 253da3f..0000000 --- a/liquid-staking/meta/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "liquid-staking-abi" -version = "0.0.0" -authors = ["Sorin Petreasca "] -edition = "2021" -publish = false -[dependencies.liquid-staking] -path = ".." - -[dependencies.multiversx-sc-meta] -version = "=0.48.0" diff --git a/liquid-staking/meta/src/main.rs b/liquid-staking/meta/src/main.rs deleted file mode 100644 index 5e33cf1..0000000 --- a/liquid-staking/meta/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - multiversx_sc_meta::cli_main::(); -} diff --git a/liquid-staking/multiversx.json b/liquid-staking/multiversx.json deleted file mode 100644 index 7365539..0000000 --- a/liquid-staking/multiversx.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "language": "rust" -} \ No newline at end of file diff --git a/liquid-staking/src/config.rs b/liquid-staking/src/config.rs deleted file mode 100644 index 097260e..0000000 --- a/liquid-staking/src/config.rs +++ /dev/null @@ -1,112 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use delegation_mock::Epoch; - -use crate::{liquidity_pool::State, Percent}; - -pub const MAX_PERCENTAGE: Percent = 100_000; -pub const UNBOND_PERIOD: Epoch = 10; - -#[derive( - TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, Clone, PartialEq, Eq, Debug, -)] -pub struct UnstakeTokenAttributes { - pub delegation_contract: ManagedAddress, - pub unstake_epoch: Epoch, - pub unstake_amount: BigUint, - pub unbond_epoch: Epoch, -} - -#[multiversx_sc::module] -pub trait ConfigModule: - multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule -{ - #[only_owner] - #[payable("EGLD")] - #[endpoint(registerLsToken)] - fn register_ls_token( - &self, - token_display_name: ManagedBuffer, - token_ticker: ManagedBuffer, - num_decimals: usize, - ) { - let payment_amount = self.call_value().egld_value().clone_value(); - self.ls_token().issue_and_set_all_roles( - payment_amount, - token_display_name, - token_ticker, - num_decimals, - None, - ); - } - - #[only_owner] - #[payable("EGLD")] - #[endpoint(registerUnstakeToken)] - fn register_unstake_token( - &self, - token_display_name: ManagedBuffer, - token_ticker: ManagedBuffer, - num_decimals: usize, - ) { - let payment_amount = self.call_value().egld_value().clone_value(); - self.unstake_token().issue_and_set_all_roles( - EsdtTokenType::NonFungible, - payment_amount, - token_display_name, - token_ticker, - num_decimals, - None, - ); - } - - #[only_owner] - #[endpoint(setStateActive)] - fn set_state_active(&self) { - self.state().set(State::Active); - } - - #[only_owner] - #[endpoint(setStateInactive)] - fn set_state_inactive(&self) { - self.state().set(State::Inactive); - } - - #[inline] - fn is_state_active(&self, state: State) -> bool { - state == State::Active - } - - #[view(getState)] - #[storage_mapper("state")] - fn state(&self) -> SingleValueMapper; - - #[view(getLsTokenId)] - #[storage_mapper("lsTokenId")] - fn ls_token(&self) -> FungibleTokenMapper; - - #[view(getLsSupply)] - #[storage_mapper("lsTokenSupply")] - fn ls_token_supply(&self) -> SingleValueMapper; - - #[view(getVirtualEgldReserve)] - #[storage_mapper("virtualEgldReserve")] - fn virtual_egld_reserve(&self) -> SingleValueMapper; - - #[view(getRewardsReserve)] - #[storage_mapper("rewardsReserve")] - fn rewards_reserve(&self) -> SingleValueMapper; - - #[view(getTotalWithdrawnEgld)] - #[storage_mapper("totalWithdrawnEgld")] - fn total_withdrawn_egld(&self) -> SingleValueMapper; - - #[view(getUnstakeTokenId)] - #[storage_mapper("unstakeTokenId")] - fn unstake_token(&self) -> NonFungibleTokenMapper; - - #[view(getUnstakeTokenSupply)] - #[storage_mapper("unstakeTokenSupply")] - fn unstake_token_supply(&self) -> SingleValueMapper; -} diff --git a/liquid-staking/src/contexts/base.rs b/liquid-staking/src/contexts/base.rs deleted file mode 100644 index c383e1a..0000000 --- a/liquid-staking/src/contexts/base.rs +++ /dev/null @@ -1,51 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use crate::State; - -pub struct StorageCache<'a, C> -where - C: crate::config::ConfigModule, -{ - sc_ref: &'a C, - pub contract_state: State, - pub ls_token_id: TokenIdentifier, - pub ls_token_supply: BigUint, - pub virtual_egld_reserve: BigUint, - pub rewards_reserve: BigUint, - pub total_withdrawn_egld: BigUint, -} - -impl<'a, C> StorageCache<'a, C> -where - C: crate::config::ConfigModule, -{ - pub fn new(sc_ref: &'a C) -> Self { - StorageCache { - contract_state: sc_ref.state().get(), - ls_token_id: sc_ref.ls_token().get_token_id(), - ls_token_supply: sc_ref.ls_token_supply().get(), - virtual_egld_reserve: sc_ref.virtual_egld_reserve().get(), - rewards_reserve: sc_ref.rewards_reserve().get(), - total_withdrawn_egld: sc_ref.total_withdrawn_egld().get(), - sc_ref, - } - } -} - -impl<'a, C> Drop for StorageCache<'a, C> -where - C: crate::config::ConfigModule, -{ - fn drop(&mut self) { - // commit changes to storage for the mutable fields - self.sc_ref.ls_token_supply().set(&self.ls_token_supply); - self.sc_ref - .virtual_egld_reserve() - .set(&self.virtual_egld_reserve); - self.sc_ref.rewards_reserve().set(&self.rewards_reserve); - self.sc_ref - .total_withdrawn_egld() - .set(&self.total_withdrawn_egld); - } -} diff --git a/liquid-staking/src/contexts/mod.rs b/liquid-staking/src/contexts/mod.rs deleted file mode 100644 index 6cf245d..0000000 --- a/liquid-staking/src/contexts/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod base; diff --git a/liquid-staking/src/delegation.rs b/liquid-staking/src/delegation.rs deleted file mode 100644 index e6aefeb..0000000 --- a/liquid-staking/src/delegation.rs +++ /dev/null @@ -1,347 +0,0 @@ -use delegation_mock::Epoch; - -use crate::errors::{ - ERROR_ALREADY_WHITELISTED, ERROR_BAD_DELEGATION_ADDRESS, ERROR_CLAIM_EPOCH, ERROR_CLAIM_START, - ERROR_DELEGATION_CAP, ERROR_FIRST_DELEGATION_NODE, ERROR_NOT_WHITELISTED, - ERROR_NO_DELEGATION_CONTRACTS, ERROR_OLD_CLAIM_START, ERROR_ONLY_DELEGATION_ADMIN, -}; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq, Eq, TypeAbi, Clone)] -pub enum ClaimStatusType { - None, - Pending, - Finished, - Delegable, - Insufficient, - Redelegated, -} - -#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq, Eq, TypeAbi, Clone)] -pub struct ClaimStatus { - pub status: ClaimStatusType, - pub last_claim_epoch: Epoch, - pub last_claim_block: Epoch, - pub current_node: u32, - pub starting_token_reserve: BigUint, -} - -impl Default for ClaimStatus { - fn default() -> Self { - Self { - status: ClaimStatusType::None, - last_claim_epoch: 0, - last_claim_block: 0, - current_node: 0, - starting_token_reserve: BigUint::zero(), - } - } -} - -#[derive( - TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, Clone, PartialEq, Eq, Debug, -)] -pub struct DelegationContractData { - pub admin_address: ManagedAddress, - pub total_staked: BigUint, - pub delegation_contract_cap: BigUint, - pub nr_nodes: u64, - pub apy: u64, - pub total_staked_from_ls_contract: BigUint, - pub total_unstaked_from_ls_contract: BigUint, - pub total_unbonded_from_ls_contract: BigUint, -} - -#[multiversx_sc::module] -pub trait DelegationModule: - crate::config::ConfigModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule -{ - #[only_owner] - #[endpoint(updateMaxDelegationAddressesNumber)] - fn update_max_delegation_addresses_number(&self, number: usize) { - self.max_delegation_addresses().set(number); - } - - #[only_owner] - #[endpoint(whitelistDelegationContract)] - fn whitelist_delegation_contract( - &self, - contract_address: ManagedAddress, - admin_address: ManagedAddress, - total_staked: BigUint, - delegation_contract_cap: BigUint, - nr_nodes: u64, - apy: u64, - ) { - require!( - self.delegation_addresses_list().len() <= self.max_delegation_addresses().get(), - "Maximum number of delegation addresses reached" - ); - - require!( - self.delegation_contract_data(&contract_address).is_empty(), - ERROR_ALREADY_WHITELISTED - ); - - require!( - total_staked <= delegation_contract_cap, - ERROR_DELEGATION_CAP - ); - - let contract_data = DelegationContractData { - admin_address, - total_staked, - delegation_contract_cap, - nr_nodes, - apy, - total_staked_from_ls_contract: BigUint::zero(), - total_unstaked_from_ls_contract: BigUint::zero(), - total_unbonded_from_ls_contract: BigUint::zero(), - }; - - self.delegation_contract_data(&contract_address) - .set(contract_data); - self.add_and_order_delegation_address_in_list(contract_address, apy); - } - - #[only_owner] - #[endpoint(changeDelegationContractAdmin)] - fn change_delegation_contract_admin( - &self, - contract_address: ManagedAddress, - admin_address: ManagedAddress, - ) { - let delegation_address_mapper = self.delegation_contract_data(&contract_address); - require!(!delegation_address_mapper.is_empty(), ERROR_NOT_WHITELISTED); - - delegation_address_mapper.update(|contract_data| { - contract_data.admin_address = admin_address; - }); - } - - #[endpoint(changeDelegationContractParams)] - fn change_delegation_contract_params( - &self, - contract_address: ManagedAddress, - total_staked: BigUint, - delegation_contract_cap: BigUint, - nr_nodes: u64, - apy: u64, - ) { - let caller = self.blockchain().get_caller(); - let delegation_address_mapper = self.delegation_contract_data(&contract_address); - let old_contract_data = delegation_address_mapper.get(); - require!(!delegation_address_mapper.is_empty(), ERROR_NOT_WHITELISTED); - require!( - old_contract_data.admin_address == caller, - ERROR_ONLY_DELEGATION_ADMIN - ); - require!( - total_staked <= delegation_contract_cap, - ERROR_DELEGATION_CAP - ); - - if old_contract_data.apy != apy { - self.remove_delegation_address_from_list(&contract_address); - self.add_and_order_delegation_address_in_list(contract_address, apy) - } - - delegation_address_mapper.update(|contract_data| { - contract_data.total_staked = total_staked; - contract_data.delegation_contract_cap = delegation_contract_cap; - contract_data.nr_nodes = nr_nodes; - contract_data.apy = apy; - }); - } - - fn add_and_order_delegation_address_in_list(&self, contract_address: ManagedAddress, apy: u64) { - let mut delegation_addresses_mapper = self.delegation_addresses_list(); - if delegation_addresses_mapper.is_empty() { - delegation_addresses_mapper.push_front(contract_address); - - return; - } - - let mut added = false; - for delegation_address_element in delegation_addresses_mapper.iter() { - let node_id = delegation_address_element.get_node_id(); - let delegation_address = delegation_address_element.into_value(); - let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); - if apy >= delegation_contract_data.apy { - self.delegation_addresses_list() - .push_before_node_id(node_id, contract_address.clone()); - added = true; - - break; - } - } - - if !added { - delegation_addresses_mapper.push_back(contract_address); - } - } - - fn remove_delegation_address_from_list(&self, contract_address: &ManagedAddress) { - for delegation_address_element in self.delegation_addresses_list().iter() { - let node_id = delegation_address_element.get_node_id(); - let delegation_address = delegation_address_element.into_value(); - if contract_address == &delegation_address { - self.delegation_addresses_list().remove_node_by_id(node_id); - - break; - } - } - } - - fn move_delegation_contract_to_back(&self, delegation_contract: ManagedAddress) { - self.remove_delegation_address_from_list(&delegation_contract); - self.delegation_addresses_list() - .push_back(delegation_contract); - } - - fn get_delegation_contract_for_delegate( - &self, - amount_to_delegate: &BigUint, - ) -> ManagedAddress { - require!( - !self.delegation_addresses_list().is_empty(), - ERROR_NO_DELEGATION_CONTRACTS - ); - - let delegation_addresses_mapper = self.delegation_addresses_list(); - for delegation_address_element in delegation_addresses_mapper.iter() { - let delegation_address = delegation_address_element.into_value(); - let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); - - let delegation_space_left = &delegation_contract_data.delegation_contract_cap - - &delegation_contract_data.total_staked; - if amount_to_delegate <= &delegation_space_left { - return delegation_address; - } - } - - sc_panic!(ERROR_BAD_DELEGATION_ADDRESS); - } - - fn get_delegation_contract_for_undelegate( - &self, - amount_to_undelegate: &BigUint, - ) -> ManagedAddress { - require!( - !self.delegation_addresses_list().is_empty(), - ERROR_NO_DELEGATION_CONTRACTS - ); - - let delegation_addresses_mapper = self.delegation_addresses_list(); - for delegation_address_element in delegation_addresses_mapper.iter() { - let delegation_address = delegation_address_element.into_value(); - let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); - - if &delegation_contract_data.total_staked_from_ls_contract >= amount_to_undelegate { - return delegation_address; - } - } - - sc_panic!(ERROR_BAD_DELEGATION_ADDRESS); - } - - fn check_claim_operation( - &self, - current_claim_status: &ClaimStatus, - old_claim_status: ClaimStatus, - current_epoch: Epoch, - ) { - require!( - current_claim_status.status == ClaimStatusType::None - || current_claim_status.status == ClaimStatusType::Pending, - ERROR_CLAIM_START - ); - require!( - old_claim_status.status == ClaimStatusType::Redelegated - || old_claim_status.status == ClaimStatusType::Insufficient, - ERROR_OLD_CLAIM_START - ); - require!( - current_epoch > old_claim_status.last_claim_epoch, - ERROR_CLAIM_EPOCH - ); - } - - fn prepare_claim_operation( - &self, - current_claim_status: &mut ClaimStatus, - current_epoch: Epoch, - ) { - if current_claim_status.status != ClaimStatusType::None { - return; - } - - let delegation_addresses_mapper = self.delegation_addresses_list(); - require!( - delegation_addresses_mapper.front().unwrap().get_node_id() != 0, - ERROR_FIRST_DELEGATION_NODE - ); - current_claim_status.status = ClaimStatusType::Pending; - current_claim_status.last_claim_epoch = current_epoch; - current_claim_status.current_node = - delegation_addresses_mapper.front().unwrap().get_node_id(); - - let current_total_withdrawn_egld = self.total_withdrawn_egld().get(); - let egld_balance = self - .blockchain() - .get_sc_balance(&EgldOrEsdtTokenIdentifier::egld(), 0); - current_claim_status.starting_token_reserve = egld_balance - current_total_withdrawn_egld; - } - - #[view(getDelegationStatus)] - fn get_delegation_status(&self) -> ClaimStatusType { - let claim_status = self.delegation_claim_status().get(); - claim_status.status - } - - #[view(getDelegationContractStakedAmount)] - fn get_delegation_contract_staked_amount(&self, delegation_address: ManagedAddress) -> BigUint { - let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); - delegation_contract_data.total_staked_from_ls_contract - } - - #[view(getDelegationContractUnstakedAmount)] - fn get_delegation_contract_unstaked_amount( - &self, - delegation_address: ManagedAddress, - ) -> BigUint { - let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); - delegation_contract_data.total_unstaked_from_ls_contract - } - - #[view(getDelegationContractUnbondedAmount)] - fn get_delegation_contract_unbonded_amount( - &self, - delegation_address: ManagedAddress, - ) -> BigUint { - let delegation_contract_data = self.delegation_contract_data(&delegation_address).get(); - delegation_contract_data.total_unbonded_from_ls_contract - } - - #[view(getDelegationAddressesList)] - #[storage_mapper("delegationAddressesList")] - fn delegation_addresses_list(&self) -> LinkedListMapper; - - #[view(getDelegationClaimStatus)] - #[storage_mapper("delegationClaimStatus")] - fn delegation_claim_status(&self) -> SingleValueMapper>; - - #[view(maxDelegationAddresses)] - #[storage_mapper("maxDelegationAddresses")] - fn max_delegation_addresses(&self) -> SingleValueMapper; - - #[view(getDelegationContractData)] - #[storage_mapper("delegationContractData")] - fn delegation_contract_data( - &self, - contract_address: &ManagedAddress, - ) -> SingleValueMapper>; -} diff --git a/liquid-staking/src/delegation_proxy.rs b/liquid-staking/src/delegation_proxy.rs deleted file mode 100644 index 0dba0d6..0000000 --- a/liquid-staking/src/delegation_proxy.rs +++ /dev/null @@ -1,17 +0,0 @@ -multiversx_sc::imports!(); - -#[multiversx_sc::proxy] -pub trait DelegationProxy { - #[payable("EGLD")] - #[endpoint(delegate)] - fn delegate(&self); - - #[endpoint(unDelegate)] - fn undelegate(&self, egld_amount: BigUint); - - #[endpoint(withdraw)] - fn withdraw(&self); - - #[endpoint(claimRewards)] - fn claim_rewards(&self); -} diff --git a/liquid-staking/src/errors.rs b/liquid-staking/src/errors.rs deleted file mode 100644 index c6efa93..0000000 --- a/liquid-staking/src/errors.rs +++ /dev/null @@ -1,34 +0,0 @@ -pub static ERROR_ACTIVE: &[u8] = b"Active state"; -pub static ERROR_NOT_ACTIVE: &[u8] = b"Not active"; -pub static ERROR_LS_TOKEN_NOT_ISSUED: &[u8] = b"LS token not issued"; -pub static ERROR_INSUFFICIENT_GAS: &[u8] = b"Insufficient gas remaining for the callback"; - -pub static ERROR_CLAIM_START: &[u8] = b"Claim operation must be new or pending"; -pub static ERROR_OLD_CLAIM_START: &[u8] = - b"Previous claimed rewards must be redelegated or lesser than 1 EGLD"; -pub static ERROR_CLAIM_REDELEGATE: &[u8] = b"Old claimed rewards must be greater than 1 EGLD"; -pub static ERROR_RECOMPUTE_RESERVES: &[u8] = b"Claim operation must be in the finished status"; -pub static ERROR_RECOMPUTE_TOO_SOON: &[u8] = b"Recompute operation called too soon"; -pub static ERROR_CLAIM_EPOCH: &[u8] = b"The rewards were already claimed for this epoch"; -pub static ERROR_UNSTAKE_PERIOD_NOT_PASSED: &[u8] = b"The unstake period has not passed"; - -pub static ERROR_BAD_PAYMENT_TOKEN: &[u8] = b"Bad payment token"; -pub static ERROR_BAD_PAYMENT_AMOUNT: &[u8] = b"Insufficient delegated amount"; -pub static ERROR_INSUFFICIENT_UNSTAKE_AMOUNT: &[u8] = b"Insufficient unstake amount"; -pub static ERROR_INSUFFICIENT_UNBONDED_AMOUNT: &[u8] = b"Insufficient incoming withdraw amount"; -pub static ERROR_INSUFFICIENT_LIQUIDITY: &[u8] = b"Insufficient liquidity minted"; -pub static ERROR_INSUFFICIENT_LIQ_BURNED: &[u8] = b"Insufficient liquidity burned"; - -pub static ERROR_NOT_ENOUGH_RESERVE: &[u8] = b"Not enough reserve"; -pub static ERROR_NOT_ENOUGH_LP: &[u8] = b"Not enough LP token supply"; - -pub static ERROR_BAD_DELEGATION_ADDRESS: &[u8] = b"No delegation contract available"; -pub static ERROR_BAD_DELEGATION_AMOUNT: &[u8] = b"Delegation amount must be greater than 0"; -pub static ERROR_NO_DELEGATION_CONTRACTS: &[u8] = b"There are no delegation contracts whitelisted"; -pub static ERROR_FIRST_DELEGATION_NODE: &[u8] = b"The first delegation node is incorrect"; -pub static ERROR_ALREADY_WHITELISTED: &[u8] = b"Delegation contract already whitelisted"; -pub static ERROR_NOT_WHITELISTED: &[u8] = b"Delegation contract is not whitelisted"; -pub static ERROR_DELEGATION_CAP: &[u8] = - b"Delegation cap must be higher than the total staked amount"; -pub static ERROR_ONLY_DELEGATION_ADMIN: &[u8] = - b"Only the admin of the delegation contract can change the status"; diff --git a/liquid-staking/src/events.rs b/liquid-staking/src/events.rs deleted file mode 100644 index ea0dd5c..0000000 --- a/liquid-staking/src/events.rs +++ /dev/null @@ -1,111 +0,0 @@ -use delegation_mock::Epoch; - -use crate::contexts::base::StorageCache; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -#[derive(TypeAbi, TopEncode)] -pub struct AddLiquidityEvent { - caller: ManagedAddress, - ls_token_id: TokenIdentifier, - ls_token_amount: BigUint, - ls_token_supply: BigUint, - virtual_egld_reserve: BigUint, - rewards_reserve: BigUint, - block: u64, - epoch: Epoch, - timestamp: u64, -} - -#[derive(TypeAbi, TopEncode)] -pub struct RemoveLiquidityEvent { - caller: ManagedAddress, - ls_token_id: TokenIdentifier, - ls_token_amount: BigUint, - unstake_token_id: TokenIdentifier, - unstake_token_amount: BigUint, - ls_token_supply: BigUint, - virtual_egld_reserve: BigUint, - rewards_reserve: BigUint, - block: u64, - epoch: Epoch, - timestamp: u64, -} - -#[multiversx_sc::module] -pub trait EventsModule: - crate::config::ConfigModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule -{ - fn emit_add_liquidity_event( - &self, - storage_cache: &StorageCache, - caller: &ManagedAddress, - ls_token_amount: BigUint, - ) { - let epoch = self.blockchain().get_block_epoch(); - self.add_liquidity_event( - &storage_cache.ls_token_id, - caller, - epoch, - &AddLiquidityEvent { - caller: caller.clone(), - ls_token_id: storage_cache.ls_token_id.clone(), - ls_token_amount, - ls_token_supply: storage_cache.ls_token_supply.clone(), - virtual_egld_reserve: storage_cache.virtual_egld_reserve.clone(), - rewards_reserve: storage_cache.rewards_reserve.clone(), - block: self.blockchain().get_block_nonce(), - epoch, - timestamp: self.blockchain().get_block_timestamp(), - }, - ) - } - - fn emit_remove_liquidity_event( - &self, - storage_cache: &StorageCache, - ls_token_amount: BigUint, - unstake_token_amount: BigUint, - ) { - let epoch = self.blockchain().get_block_epoch(); - let caller = self.blockchain().get_caller(); - self.remove_liquidity_event( - &storage_cache.ls_token_id, - &caller, - epoch, - &RemoveLiquidityEvent { - caller: caller.clone(), - ls_token_id: storage_cache.ls_token_id.clone(), - ls_token_amount, - unstake_token_id: self.unstake_token().get_token_id(), - unstake_token_amount, - ls_token_supply: storage_cache.ls_token_supply.clone(), - virtual_egld_reserve: storage_cache.virtual_egld_reserve.clone(), - rewards_reserve: storage_cache.rewards_reserve.clone(), - block: self.blockchain().get_block_nonce(), - epoch, - timestamp: self.blockchain().get_block_timestamp(), - }, - ) - } - - #[event("add_liquidity")] - fn add_liquidity_event( - &self, - #[indexed] ls_token: &TokenIdentifier, - #[indexed] caller: &ManagedAddress, - #[indexed] epoch: Epoch, - add_liquidity_event: &AddLiquidityEvent, - ); - - #[event("remove_liquidity")] - fn remove_liquidity_event( - &self, - #[indexed] ls_token: &TokenIdentifier, - #[indexed] caller: &ManagedAddress, - #[indexed] epoch: Epoch, - remove_liquidity_event: &RemoveLiquidityEvent, - ); -} diff --git a/liquid-staking/src/lib.rs b/liquid-staking/src/lib.rs deleted file mode 100644 index 83668ab..0000000 --- a/liquid-staking/src/lib.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![no_std] - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -pub const MAX_DELEGATION_ADDRESSES: usize = 50; - -pub type Epoch = u64; -pub type Percent = u64; - -pub mod config; -mod contexts; -pub mod delegation; -pub mod delegation_proxy; -pub mod errors; -mod events; -mod liquidity_pool; -pub mod user_actions; - -use crate::{ - delegation::{ClaimStatus, ClaimStatusType}, - errors::*, -}; - -use contexts::base::*; -use liquidity_pool::State; - -#[multiversx_sc::contract] -pub trait LiquidStaking: - liquidity_pool::LiquidityPoolModule - + config::ConfigModule - + events::EventsModule - + delegation::DelegationModule - + multiversx_sc_modules::ongoing_operation::OngoingOperationModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule - + user_actions::common::CommonModule - + user_actions::add_liquidity::AddLiquidityModule - + user_actions::remove_liquidity::RemoveLiquidityModule - + user_actions::unbond::UnbondModule - + user_actions::claim_rewards::ClaimRewardsModule - + user_actions::delegate_rewards::DelegateRewardsModule - + user_actions::recompute_token_reserve::RecomputeTokenReserveModule -{ - #[init] - fn init(&self) { - self.state().set(State::Inactive); - self.max_delegation_addresses() - .set(MAX_DELEGATION_ADDRESSES); - - let current_epoch = self.blockchain().get_block_epoch(); - let claim_status = ClaimStatus { - status: ClaimStatusType::Insufficient, - last_claim_epoch: current_epoch, - last_claim_block: 0u64, - current_node: 0, - starting_token_reserve: BigUint::zero(), - }; - - self.delegation_claim_status().set(claim_status); - } - - #[upgrade] - fn upgrade(&self) {} -} diff --git a/liquid-staking/src/liquidity_pool.rs b/liquid-staking/src/liquidity_pool.rs deleted file mode 100644 index b706ee1..0000000 --- a/liquid-staking/src/liquidity_pool.rs +++ /dev/null @@ -1,87 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use crate::contexts::base::StorageCache; -use crate::errors::*; - -use super::config; - -const MINIMUM_LIQUIDITY: u64 = 1_000; - -#[derive(TypeAbi, TopEncode, TopDecode, PartialEq, Eq, Copy, Clone, Debug)] -pub enum State { - Inactive, - Active, -} - -#[multiversx_sc::module] -pub trait LiquidityPoolModule: - config::ConfigModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule -{ - fn pool_add_liquidity( - &self, - token_amount: &BigUint, - storage_cache: &mut StorageCache, - ) -> BigUint { - let ls_amount = if storage_cache.virtual_egld_reserve > 0 { - token_amount.clone() * &storage_cache.ls_token_supply - / (&storage_cache.virtual_egld_reserve + &storage_cache.rewards_reserve) - } else { - token_amount.clone() - }; - - require!(ls_amount > 0, ERROR_INSUFFICIENT_LIQUIDITY); - - storage_cache.ls_token_supply += &ls_amount; - storage_cache.virtual_egld_reserve += token_amount; - - ls_amount - } - - fn pool_remove_liquidity( - &self, - token_amount: &BigUint, - storage_cache: &mut StorageCache, - ) -> BigUint { - let egld_amount = self.get_egld_amount(token_amount, storage_cache); - storage_cache.ls_token_supply -= token_amount; - storage_cache.virtual_egld_reserve -= &egld_amount; - - egld_amount - } - - fn get_egld_amount( - &self, - ls_token_amount: &BigUint, - storage_cache: &StorageCache, - ) -> BigUint { - require!( - storage_cache.ls_token_supply >= ls_token_amount + MINIMUM_LIQUIDITY, - ERROR_NOT_ENOUGH_LP - ); - - let egld_amount = - ls_token_amount * &storage_cache.virtual_egld_reserve / &storage_cache.ls_token_supply; - require!(egld_amount > 0u64, ERROR_INSUFFICIENT_LIQ_BURNED); - - egld_amount - } - - fn mint_ls_token(&self, amount: BigUint) -> EsdtTokenPayment { - self.ls_token().mint(amount) - } - - fn burn_ls_token(&self, amount: &BigUint) { - self.ls_token().burn(amount); - } - - fn mint_unstake_tokens(&self, attributes: &T) -> EsdtTokenPayment { - self.unstake_token() - .nft_create(BigUint::from(1u64), attributes) - } - - fn burn_unstake_tokens(&self, token_nonce: u64) { - self.unstake_token() - .nft_burn(token_nonce, &BigUint::from(1u64)); - } -} diff --git a/liquid-staking/src/user_actions/add_liquidity.rs b/liquid-staking/src/user_actions/add_liquidity.rs deleted file mode 100644 index e92e11a..0000000 --- a/liquid-staking/src/user_actions/add_liquidity.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - delegation_proxy::ProxyTrait as _, user_actions::common::MIN_EGLD_TO_DELEGATE, StorageCache, - ERROR_BAD_PAYMENT_AMOUNT, ERROR_NOT_ACTIVE, -}; - -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait AddLiquidityModule: - crate::config::ConfigModule - + crate::events::EventsModule - + crate::delegation::DelegationModule - + crate::liquidity_pool::LiquidityPoolModule - + multiversx_sc_modules::ongoing_operation::OngoingOperationModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule - + super::common::CommonModule -{ - #[payable("EGLD")] - #[endpoint(addLiquidity)] - fn add_liquidity(&self) { - self.blockchain().check_caller_is_user_account(); - - let storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - - let payment = self.call_value().egld_value().clone_value(); - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!(payment >= MIN_EGLD_TO_DELEGATE, ERROR_BAD_PAYMENT_AMOUNT); - - let delegation_contract = self.get_delegation_contract_for_delegate(&payment); - let gas_for_async_call = self.get_gas_for_async_call(); - - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .delegate() - .with_gas_limit(gas_for_async_call) - .with_egld_transfer(payment.clone()) - .async_call() - .with_callback(AddLiquidityModule::callbacks(self).add_liquidity_callback( - caller, - delegation_contract, - payment, - )) - .call_and_exit() - } - - #[callback] - fn add_liquidity_callback( - &self, - caller: ManagedAddress, - delegation_contract: ManagedAddress, - staked_tokens: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - match result { - ManagedAsyncCallResult::Ok(()) => { - let mut storage_cache = StorageCache::new(self); - self.delegation_contract_data(&delegation_contract) - .update(|contract_data| { - contract_data.total_staked_from_ls_contract += &staked_tokens; - }); - - let ls_token_amount = self.pool_add_liquidity(&staked_tokens, &mut storage_cache); - let user_payment = self.mint_ls_token(ls_token_amount); - self.send().direct_esdt( - &caller, - &user_payment.token_identifier, - user_payment.token_nonce, - &user_payment.amount, - ); - - self.emit_add_liquidity_event(&storage_cache, &caller, user_payment.amount); - } - ManagedAsyncCallResult::Err(_) => { - self.send().direct_egld(&caller, &staked_tokens); - self.move_delegation_contract_to_back(delegation_contract); - } - } - } -} diff --git a/liquid-staking/src/user_actions/claim_rewards.rs b/liquid-staking/src/user_actions/claim_rewards.rs deleted file mode 100644 index b687adc..0000000 --- a/liquid-staking/src/user_actions/claim_rewards.rs +++ /dev/null @@ -1,82 +0,0 @@ -use multiversx_sc_modules::ongoing_operation::{ - CONTINUE_OP, DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, STOP_OP, -}; - -use crate::{ - delegation::{ClaimStatus, ClaimStatusType}, - delegation_proxy::ProxyTrait as _, - StorageCache, ERROR_NOT_ACTIVE, ERROR_NO_DELEGATION_CONTRACTS, -}; - -multiversx_sc::imports!(); - -pub const DEFAULT_GAS_TO_CLAIM_REWARDS: u64 = 6_000_000; - -#[multiversx_sc::module] -pub trait ClaimRewardsModule: - crate::config::ConfigModule - + crate::events::EventsModule - + crate::delegation::DelegationModule - + crate::liquidity_pool::LiquidityPoolModule - + multiversx_sc_modules::ongoing_operation::OngoingOperationModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule - + super::common::CommonModule -{ - #[endpoint(claimRewards)] - fn claim_rewards(&self) { - let storage_cache = StorageCache::new(self); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - - let delegation_addresses_mapper = self.delegation_addresses_list(); - require!( - !delegation_addresses_mapper.is_empty(), - ERROR_NO_DELEGATION_CONTRACTS - ); - let claim_status_mapper = self.delegation_claim_status(); - let old_claim_status = claim_status_mapper.get(); - let current_epoch = self.blockchain().get_block_epoch(); - let mut current_claim_status = self.load_operation::>(); - - self.check_claim_operation(¤t_claim_status, old_claim_status, current_epoch); - self.prepare_claim_operation(&mut current_claim_status, current_epoch); - - let run_result = self.run_while_it_has_gas(DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, || { - let delegation_address_node = delegation_addresses_mapper - .get_node_by_id(current_claim_status.current_node) - .unwrap(); - let next_node = delegation_address_node.get_next_node_id(); - let delegation_address = delegation_address_node.into_value(); - - self.delegation_proxy_obj() - .contract(delegation_address) - .claim_rewards() - .with_gas_limit(DEFAULT_GAS_TO_CLAIM_REWARDS) - .transfer_execute(); - - if next_node == 0 { - claim_status_mapper.set(current_claim_status.clone()); - return STOP_OP; - } else { - current_claim_status.current_node = next_node; - } - - CONTINUE_OP - }); - - match run_result { - OperationCompletionStatus::InterruptedBeforeOutOfGas => { - self.save_progress(¤t_claim_status); - } - OperationCompletionStatus::Completed => { - claim_status_mapper.update(|claim_status| { - claim_status.status = ClaimStatusType::Finished; - claim_status.last_claim_block = self.blockchain().get_block_nonce(); - }); - } - }; - } -} diff --git a/liquid-staking/src/user_actions/common.rs b/liquid-staking/src/user_actions/common.rs deleted file mode 100644 index 2fa401c..0000000 --- a/liquid-staking/src/user_actions/common.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::{delegation_proxy, StorageCache, ERROR_INSUFFICIENT_GAS}; - -multiversx_sc::imports!(); - -pub const MIN_GAS_FOR_CALLBACK: u64 = 12_000_000; -pub const MIN_GAS_FOR_ASYNC_CALL: u64 = 12_000_000; -pub const MIN_EGLD_TO_DELEGATE: u64 = 1_000_000_000_000_000_000; - -#[multiversx_sc::module] -pub trait CommonModule: - crate::config::ConfigModule - + crate::liquidity_pool::LiquidityPoolModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule -{ - // views - #[view(getLsValueForPosition)] - fn get_ls_value_for_position(&self, ls_token_amount: BigUint) -> BigUint { - let storage_cache = StorageCache::new(self); - self.get_egld_amount(&ls_token_amount, &storage_cache) - } - - fn get_gas_for_async_call(&self) -> u64 { - let gas_left = self.blockchain().get_gas_left(); - require!( - gas_left > MIN_GAS_FOR_ASYNC_CALL + MIN_GAS_FOR_CALLBACK, - ERROR_INSUFFICIENT_GAS - ); - - gas_left - MIN_GAS_FOR_CALLBACK - } - - fn send_back_unbond_nft(&self, caller: &ManagedAddress, unstake_token_nonce: u64) { - let unstake_token_id = self.unstake_token().get_token_id(); - self.send().direct_esdt( - caller, - &unstake_token_id, - unstake_token_nonce, - &BigUint::from(1u64), - ) - } - - #[proxy] - fn delegation_proxy_obj(&self) -> delegation_proxy::Proxy; -} diff --git a/liquid-staking/src/user_actions/delegate_rewards.rs b/liquid-staking/src/user_actions/delegate_rewards.rs deleted file mode 100644 index f992e90..0000000 --- a/liquid-staking/src/user_actions/delegate_rewards.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::{ - delegation::ClaimStatusType, delegation_proxy::ProxyTrait as _, StorageCache, - ERROR_CLAIM_REDELEGATE, ERROR_NOT_ACTIVE, -}; - -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait DelegateRewardsModule: - crate::config::ConfigModule - + crate::events::EventsModule - + crate::delegation::DelegationModule - + crate::liquidity_pool::LiquidityPoolModule - + multiversx_sc_modules::ongoing_operation::OngoingOperationModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule - + super::common::CommonModule -{ - #[endpoint(delegateRewards)] - fn delegate_rewards(&self) { - let mut storage_cache = StorageCache::new(self); - let claim_status = self.delegation_claim_status().get(); - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - claim_status.status == ClaimStatusType::Delegable, - ERROR_CLAIM_REDELEGATE - ); - - let rewards_reserve = storage_cache.rewards_reserve.clone(); - storage_cache.rewards_reserve = BigUint::zero(); - let delegation_contract = self.get_delegation_contract_for_delegate(&rewards_reserve); - let gas_for_async_call = self.get_gas_for_async_call(); - - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .delegate() - .with_gas_limit(gas_for_async_call) - .with_egld_transfer(rewards_reserve.clone()) - .async_call() - .with_callback( - DelegateRewardsModule::callbacks(self) - .delegate_rewards_callback(delegation_contract, rewards_reserve), - ) - .call_and_exit() - } - - #[callback] - fn delegate_rewards_callback( - &self, - delegation_contract: ManagedAddress, - staked_tokens: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - let mut storage_cache = StorageCache::new(self); - match result { - ManagedAsyncCallResult::Ok(()) => { - self.delegation_contract_data(&delegation_contract) - .update(|contract_data| { - contract_data.total_staked_from_ls_contract += &staked_tokens; - }); - - self.delegation_claim_status() - .update(|claim_status| claim_status.status = ClaimStatusType::Redelegated); - - storage_cache.virtual_egld_reserve += &staked_tokens; - let sc_address = self.blockchain().get_sc_address(); - self.emit_add_liquidity_event(&storage_cache, &sc_address, BigUint::zero()); - } - ManagedAsyncCallResult::Err(_) => { - storage_cache.rewards_reserve = staked_tokens; - self.move_delegation_contract_to_back(delegation_contract); - } - } - } -} diff --git a/liquid-staking/src/user_actions/mod.rs b/liquid-staking/src/user_actions/mod.rs deleted file mode 100644 index cf1630d..0000000 --- a/liquid-staking/src/user_actions/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod add_liquidity; -pub mod claim_rewards; -pub mod common; -pub mod delegate_rewards; -pub mod recompute_token_reserve; -pub mod remove_liquidity; -pub mod unbond; diff --git a/liquid-staking/src/user_actions/recompute_token_reserve.rs b/liquid-staking/src/user_actions/recompute_token_reserve.rs deleted file mode 100644 index cd1e4bb..0000000 --- a/liquid-staking/src/user_actions/recompute_token_reserve.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::{ - delegation::ClaimStatusType, user_actions::common::MIN_EGLD_TO_DELEGATE, StorageCache, - ERROR_NOT_ACTIVE, ERROR_RECOMPUTE_RESERVES, ERROR_RECOMPUTE_TOO_SOON, -}; - -multiversx_sc::imports!(); - -pub const RECOMPUTE_BLOCK_OFFSET: u64 = 10; - -#[multiversx_sc::module] -pub trait RecomputeTokenReserveModule: - crate::config::ConfigModule - + crate::events::EventsModule - + crate::delegation::DelegationModule - + crate::liquidity_pool::LiquidityPoolModule - + multiversx_sc_modules::ongoing_operation::OngoingOperationModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule - + super::common::CommonModule -{ - #[endpoint(recomputeTokenReserve)] - fn recompute_token_reserve(&self) { - let mut storage_cache = StorageCache::new(self); - let claim_status_mapper = self.delegation_claim_status(); - let mut claim_status = claim_status_mapper.get(); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - claim_status.status == ClaimStatusType::Finished, - ERROR_RECOMPUTE_RESERVES - ); - - let current_block = self.blockchain().get_block_nonce(); - require!( - current_block >= claim_status.last_claim_block + RECOMPUTE_BLOCK_OFFSET, - ERROR_RECOMPUTE_TOO_SOON - ); - - let current_egld_balance = self - .blockchain() - .get_sc_balance(&EgldOrEsdtTokenIdentifier::egld(), 0); - if current_egld_balance - > &storage_cache.total_withdrawn_egld + &claim_status.starting_token_reserve - { - let rewards = ¤t_egld_balance - - &storage_cache.total_withdrawn_egld - - &claim_status.starting_token_reserve; - storage_cache.rewards_reserve += rewards; - } - - if storage_cache.rewards_reserve >= MIN_EGLD_TO_DELEGATE { - claim_status.status = ClaimStatusType::Delegable; - } else { - claim_status.status = ClaimStatusType::Insufficient; - } - - claim_status_mapper.set(claim_status); - } -} diff --git a/liquid-staking/src/user_actions/remove_liquidity.rs b/liquid-staking/src/user_actions/remove_liquidity.rs deleted file mode 100644 index 1f8bb49..0000000 --- a/liquid-staking/src/user_actions/remove_liquidity.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::{ - config::{UnstakeTokenAttributes, UNBOND_PERIOD}, - delegation_proxy::ProxyTrait as _, - user_actions::common::MIN_EGLD_TO_DELEGATE, - StorageCache, ERROR_BAD_PAYMENT_AMOUNT, ERROR_BAD_PAYMENT_TOKEN, - ERROR_INSUFFICIENT_UNSTAKE_AMOUNT, ERROR_LS_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, -}; - -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait RemoveLiquidityModule: - crate::config::ConfigModule - + crate::events::EventsModule - + crate::delegation::DelegationModule - + crate::liquidity_pool::LiquidityPoolModule - + multiversx_sc_modules::ongoing_operation::OngoingOperationModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule - + super::common::CommonModule -{ - #[payable("*")] - #[endpoint(removeLiquidity)] - fn remove_liquidity(&self) { - self.blockchain().check_caller_is_user_account(); - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt(); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - storage_cache.ls_token_id.is_valid_esdt_identifier(), - ERROR_LS_TOKEN_NOT_ISSUED - ); - require!( - payment.token_identifier == storage_cache.ls_token_id, - ERROR_BAD_PAYMENT_TOKEN - ); - require!(payment.amount > 0, ERROR_BAD_PAYMENT_AMOUNT); - - let egld_to_unstake = self.pool_remove_liquidity(&payment.amount, &mut storage_cache); - require!( - egld_to_unstake >= MIN_EGLD_TO_DELEGATE, - ERROR_INSUFFICIENT_UNSTAKE_AMOUNT - ); - self.burn_ls_token(&payment.amount); - - let delegation_contract = self.get_delegation_contract_for_undelegate(&egld_to_unstake); - let gas_for_async_call = self.get_gas_for_async_call(); - - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .undelegate(egld_to_unstake.clone()) - .with_gas_limit(gas_for_async_call) - .async_call() - .with_callback( - RemoveLiquidityModule::callbacks(self).remove_liquidity_callback( - caller, - delegation_contract, - egld_to_unstake, - payment.amount, - ), - ) - .call_and_exit() - } - - #[callback] - fn remove_liquidity_callback( - &self, - caller: ManagedAddress, - delegation_contract: ManagedAddress, - egld_to_unstake: BigUint, - ls_tokens_to_be_burned: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - let mut storage_cache = StorageCache::new(self); - match result { - ManagedAsyncCallResult::Ok(()) => { - let current_epoch = self.blockchain().get_block_epoch(); - let unbond_epoch = current_epoch + UNBOND_PERIOD; - - self.delegation_contract_data(&delegation_contract) - .update(|contract_data| { - contract_data.total_staked_from_ls_contract -= &egld_to_unstake; - contract_data.total_unstaked_from_ls_contract += &egld_to_unstake; - }); - self.unstake_token_supply() - .update(|x| *x += &egld_to_unstake); - - let virtual_position = UnstakeTokenAttributes { - delegation_contract, - unstake_epoch: current_epoch, - unstake_amount: egld_to_unstake, - unbond_epoch, - }; - - let user_payment = self.mint_unstake_tokens(&virtual_position); - self.send().direct_esdt( - &caller, - &user_payment.token_identifier, - user_payment.token_nonce, - &user_payment.amount, - ); - - self.emit_remove_liquidity_event( - &storage_cache, - ls_tokens_to_be_burned, - user_payment.amount, - ); - } - ManagedAsyncCallResult::Err(_) => { - let ls_token_amount = self.pool_add_liquidity(&egld_to_unstake, &mut storage_cache); - let user_payment = self.mint_ls_token(ls_token_amount); - self.send().direct_esdt( - &caller, - &user_payment.token_identifier, - user_payment.token_nonce, - &user_payment.amount, - ); - self.move_delegation_contract_to_back(delegation_contract); - } - } - } -} diff --git a/liquid-staking/src/user_actions/unbond.rs b/liquid-staking/src/user_actions/unbond.rs deleted file mode 100644 index 090b72f..0000000 --- a/liquid-staking/src/user_actions/unbond.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::{ - config::UnstakeTokenAttributes, delegation_proxy::ProxyTrait as _, StorageCache, - ERROR_BAD_PAYMENT_AMOUNT, ERROR_BAD_PAYMENT_TOKEN, ERROR_NOT_ACTIVE, - ERROR_UNSTAKE_PERIOD_NOT_PASSED, -}; - -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait UnbondModule: - crate::config::ConfigModule - + crate::events::EventsModule - + crate::delegation::DelegationModule - + crate::liquidity_pool::LiquidityPoolModule - + multiversx_sc_modules::ongoing_operation::OngoingOperationModule - + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule - + super::common::CommonModule -{ - #[payable("*")] - #[endpoint(unbondTokens)] - fn unbond_tokens(&self) { - self.blockchain().check_caller_is_user_account(); - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt(); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - payment.token_identifier == self.unstake_token().get_token_id(), - ERROR_BAD_PAYMENT_TOKEN - ); - require!(payment.amount > 0, ERROR_BAD_PAYMENT_AMOUNT); - - let unstake_token_attributes: UnstakeTokenAttributes = self - .unstake_token() - .get_token_attributes(payment.token_nonce); - - let current_epoch = self.blockchain().get_block_epoch(); - require!( - current_epoch >= unstake_token_attributes.unbond_epoch, - ERROR_UNSTAKE_PERIOD_NOT_PASSED - ); - - let delegation_contract = unstake_token_attributes.delegation_contract; - let unstake_amount = unstake_token_attributes.unstake_amount; - let delegation_contract_mapper = self.delegation_contract_data(&delegation_contract); - let delegation_contract_data = delegation_contract_mapper.get(); - if delegation_contract_data.total_unbonded_from_ls_contract >= unstake_amount { - delegation_contract_mapper.update(|contract_data| { - contract_data.total_unstaked_from_ls_contract -= &unstake_amount; - contract_data.total_unbonded_from_ls_contract -= &unstake_amount - }); - - storage_cache.total_withdrawn_egld -= &unstake_amount; - self.unstake_token_supply() - .update(|x| *x -= &unstake_amount); - self.burn_unstake_tokens(payment.token_nonce); - self.send().direct_egld(&caller, &unstake_amount); - } else { - let gas_for_async_call = self.get_gas_for_async_call(); - self.delegation_proxy_obj() - .contract(delegation_contract.clone()) - .withdraw() - .with_gas_limit(gas_for_async_call) - .async_call() - .with_callback(UnbondModule::callbacks(self).withdraw_tokens_callback( - caller, - delegation_contract, - payment.token_nonce, - unstake_amount, - )) - .call_and_exit(); - } - } - - #[callback] - fn withdraw_tokens_callback( - &self, - caller: ManagedAddress, - delegation_contract: ManagedAddress, - unstake_token_nonce: u64, - unstake_token_amount: BigUint, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - match result { - ManagedAsyncCallResult::Ok(()) => { - let withdraw_amount = self.call_value().egld_value().clone_value(); - let mut storage_cache = StorageCache::new(self); - let delegation_contract_mapper = - self.delegation_contract_data(&delegation_contract); - if withdraw_amount > 0u64 { - delegation_contract_mapper.update(|contract_data| { - contract_data.total_unbonded_from_ls_contract += &withdraw_amount - }); - storage_cache.total_withdrawn_egld += &withdraw_amount; - } - let delegation_contract_data = delegation_contract_mapper.get(); - if delegation_contract_data.total_unbonded_from_ls_contract >= unstake_token_amount - { - delegation_contract_mapper.update(|contract_data| { - contract_data.total_unstaked_from_ls_contract -= &unstake_token_amount; - contract_data.total_unbonded_from_ls_contract -= &unstake_token_amount; - }); - storage_cache.total_withdrawn_egld -= &unstake_token_amount; - self.unstake_token_supply() - .update(|x| *x -= &unstake_token_amount); - self.burn_unstake_tokens(unstake_token_nonce); - self.send().direct_egld(&caller, &unstake_token_amount); - } else { - self.send_back_unbond_nft(&caller, unstake_token_nonce); - } - } - ManagedAsyncCallResult::Err(_) => { - self.send_back_unbond_nft(&caller, unstake_token_nonce); - } - } - } -} diff --git a/liquid-staking/testnet.toml b/liquid-staking/testnet.toml deleted file mode 100644 index e69de29..0000000 diff --git a/liquid-staking/tests/contract_interactions/mod.rs b/liquid-staking/tests/contract_interactions/mod.rs deleted file mode 100644 index 9861e8a..0000000 --- a/liquid-staking/tests/contract_interactions/mod.rs +++ /dev/null @@ -1,327 +0,0 @@ -use crate::contract_setup::LiquidStakingContractSetup; -use liquid_staking::config::{ConfigModule, UnstakeTokenAttributes}; -use liquid_staking::user_actions::add_liquidity::AddLiquidityModule; -use liquid_staking::user_actions::claim_rewards::ClaimRewardsModule; -use liquid_staking::user_actions::common::CommonModule; -use liquid_staking::user_actions::delegate_rewards::DelegateRewardsModule; -use liquid_staking::user_actions::recompute_token_reserve::RecomputeTokenReserveModule; -use liquid_staking::user_actions::remove_liquidity::RemoveLiquidityModule; -use liquid_staking::user_actions::unbond::UnbondModule; -use multiversx_sc::types::Address; -use multiversx_sc_scenario::{managed_address, num_bigint, rust_biguint, DebugApi}; - -use delegation_mock::*; -use liquid_staking::delegation::DelegationModule; - -impl LiquidStakingContractSetup -where - LiquidStakingContractObjBuilder: 'static + Copy + Fn() -> liquid_staking::ContractObj, -{ - pub fn deploy_staking_contract( - &mut self, - owner_address: &Address, - egld_balance: u64, - total_staked: u64, - delegation_contract_cap: u64, - nr_nodes: u64, - apy: u64, - ) -> Address { - let rust_zero = rust_biguint!(0u64); - let egld_balance_biguint = &Self::exp18(egld_balance); - let total_staked_biguint = Self::exp18(total_staked); - let delegation_contract_cap_biguint = Self::exp18(delegation_contract_cap); - - self.b_mock - .set_egld_balance(owner_address, egld_balance_biguint); - - let delegation_wrapper = self.b_mock.create_sc_account( - &rust_zero, - Some(owner_address), - delegation_mock::contract_obj, - "delegation-mock.wasm", - ); - - self.b_mock - .execute_tx(owner_address, &delegation_wrapper, &rust_zero, |sc| { - sc.init(); - }) - .assert_ok(); - - self.b_mock - .execute_tx( - owner_address, - &delegation_wrapper, - egld_balance_biguint, - |sc| { - sc.deposit_egld(); - }, - ) - .assert_ok(); - - self.b_mock - .execute_tx(owner_address, &self.sc_wrapper, &rust_zero, |sc| { - sc.whitelist_delegation_contract( - managed_address!(delegation_wrapper.address_ref()), - managed_address!(owner_address), - Self::to_managed_biguint(total_staked_biguint), - Self::to_managed_biguint(delegation_contract_cap_biguint), - nr_nodes, - apy, - ); - }) - .assert_ok(); - - delegation_wrapper.address_ref().clone() - } - - pub fn update_staking_contract_params( - &mut self, - owner_address: &Address, - contract_address: &Address, - total_staked: u64, - delegation_contract_cap: u64, - nr_nodes: u64, - apy: u64, - ) { - let rust_zero = rust_biguint!(0u64); - let total_staked_biguint = Self::exp18(total_staked); - let delegation_contract_cap_biguint = Self::exp18(delegation_contract_cap); - - self.b_mock - .execute_tx(owner_address, &self.sc_wrapper, &rust_zero, |sc| { - sc.change_delegation_contract_params( - managed_address!(contract_address), - Self::to_managed_biguint(total_staked_biguint), - Self::to_managed_biguint(delegation_contract_cap_biguint), - nr_nodes, - apy, - ); - }) - .assert_ok(); - } - - pub fn add_liquidity(&mut self, caller: &Address, payment_amount: u64) { - self.b_mock - .execute_tx( - caller, - &self.sc_wrapper, - &Self::exp18(payment_amount), - |sc| { - sc.add_liquidity(); - }, - ) - .assert_ok(); - } - - pub fn remove_liquidity( - &mut self, - caller: &Address, - payment_token: &[u8], - payment_amount: u64, - ) { - self.b_mock - .execute_esdt_transfer( - caller, - &self.sc_wrapper, - payment_token, - 0, - &Self::exp18(payment_amount), - |sc| { - sc.remove_liquidity(); - }, - ) - .assert_ok(); - } - - pub fn claim_rewards(&mut self, caller: &Address) { - let rust_zero = rust_biguint!(0u64); - self.b_mock - .execute_tx(caller, &self.sc_wrapper, &rust_zero, |sc| { - sc.claim_rewards(); - }) - .assert_ok(); - } - - pub fn recompute_token_reserve(&mut self, caller: &Address) { - let rust_zero = rust_biguint!(0u64); - self.b_mock - .execute_tx(caller, &self.sc_wrapper, &rust_zero, |sc| { - sc.recompute_token_reserve(); - }) - .assert_ok(); - } - - pub fn delegate_rewards(&mut self, caller: &Address) { - let rust_zero = rust_biguint!(0u64); - self.b_mock - .execute_tx(caller, &self.sc_wrapper, &rust_zero, |sc| { - sc.delegate_rewards(); - }) - .assert_ok(); - } - - pub fn delegate_rewards_check_insufficient(&mut self, caller: &Address) { - let rust_zero = rust_biguint!(0u64); - self.b_mock - .execute_tx(caller, &self.sc_wrapper, &rust_zero, |sc| { - sc.delegate_rewards(); - }) - .assert_error(4, "Old claimed rewards must be greater than 1 EGLD"); - } - - pub fn unbond_tokens(&mut self, caller: &Address, payment_token: &[u8], token_nonce: u64) { - self.b_mock - .execute_esdt_transfer( - caller, - &self.sc_wrapper, - payment_token, - token_nonce, - &num_bigint::BigUint::from(1u64), // NFT - |sc| { - sc.unbond_tokens(); - }, - ) - .assert_ok(); - } - - pub fn setup_new_user(&mut self, egld_token_amount: u64) -> Address { - let rust_zero = rust_biguint!(0); - - let new_user = self.b_mock.create_user_account(&rust_zero); - self.b_mock - .set_egld_balance(&new_user, &Self::exp18(egld_token_amount)); - new_user - } - - pub fn check_user_balance(&self, address: &Address, token_id: &[u8], token_balance: u64) { - self.b_mock - .check_esdt_balance(address, token_id, &Self::exp18(token_balance)); - } - - pub fn check_user_balance_denominated( - &self, - address: &Address, - token_id: &[u8], - token_balance: u128, - ) { - self.b_mock.check_esdt_balance( - address, - token_id, - &num_bigint::BigUint::from(token_balance), - ); - } - - pub fn check_user_egld_balance(&self, address: &Address, token_balance: u64) { - self.b_mock - .check_egld_balance(address, &Self::exp18(token_balance)); - } - - pub fn check_user_egld_balance_denominated(&self, address: &Address, token_balance: u128) { - self.b_mock - .check_egld_balance(address, &num_bigint::BigUint::from(token_balance)); - } - - pub fn check_contract_storage( - &mut self, - ls_token_supply: u64, - virtual_egld_reserve: u64, - rewards_reserve: u64, - withdrawn_egld: u64, - ) { - self.b_mock - .execute_query(&self.sc_wrapper, |sc| { - assert_eq!( - sc.ls_token_supply().get(), - Self::to_managed_biguint(Self::exp18(ls_token_supply)) - ); - assert_eq!( - sc.virtual_egld_reserve().get(), - Self::to_managed_biguint(Self::exp18(virtual_egld_reserve)) - ); - assert_eq!( - sc.rewards_reserve().get(), - Self::to_managed_biguint(Self::exp18(rewards_reserve)) - ); - assert_eq!( - sc.total_withdrawn_egld().get(), - Self::to_managed_biguint(Self::exp18(withdrawn_egld)) - ); - }) - .assert_ok(); - } - - pub fn check_contract_rewards_storage_denominated(&mut self, rewards_reserve: u128) { - self.b_mock - .execute_query(&self.sc_wrapper, |sc| { - assert_eq!( - sc.rewards_reserve().get(), - Self::to_managed_biguint(num_bigint::BigUint::from(rewards_reserve)) - ); - }) - .assert_ok(); - } - - pub fn check_delegation_contract_values( - &mut self, - delegation_contract: &Address, - total_staked: u64, - ) { - self.b_mock - .execute_query(&self.sc_wrapper, |sc| { - assert_eq!( - sc.delegation_contract_data(&managed_address!(delegation_contract)) - .get() - .total_staked_from_ls_contract, - Self::to_managed_biguint(Self::exp18(total_staked)) - ); - }) - .assert_ok(); - } - - pub fn get_ls_value_for_position(&mut self, token_amount: u64) -> u128 { - let mut ls_value = 0u64; - self.b_mock - .execute_query(&self.sc_wrapper, |sc| { - let ls_value_biguint = sc - .get_ls_value_for_position(Self::to_managed_biguint(Self::exp18(token_amount))); - ls_value = ls_value_biguint.to_u64().unwrap(); - }) - .assert_ok(); - - u128::from(ls_value) - } - - pub fn check_delegation_contract_values_denominated( - &mut self, - delegation_contract: &Address, - total_staked: u128, - ) { - self.b_mock - .execute_query(&self.sc_wrapper, |sc| { - assert_eq!( - sc.delegation_contract_data(&managed_address!(delegation_contract)) - .get() - .total_staked_from_ls_contract, - Self::to_managed_biguint(num_bigint::BigUint::from(total_staked)) - ); - }) - .assert_ok(); - } - - pub fn check_user_nft_balance_denominated( - &self, - address: &Address, - token_id: &[u8], - token_nonce: u64, - token_balance: u64, - ) { - self.b_mock - .check_nft_balance::>( - address, - token_id, - token_nonce, - &num_bigint::BigUint::from(token_balance), - None, - ); - } -} diff --git a/liquid-staking/tests/contract_setup/mod.rs b/liquid-staking/tests/contract_setup/mod.rs deleted file mode 100644 index 2587ea0..0000000 --- a/liquid-staking/tests/contract_setup/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::ops::Mul; - -use multiversx_sc::{ - storage::mappers::StorageTokenWrapper, - types::{Address, BigUint, EsdtLocalRole}, -}; - -use multiversx_sc_scenario::{ - managed_token_id, num_bigint, rust_biguint, - whitebox_legacy::{BlockchainStateWrapper, ContractObjWrapper}, - DebugApi, -}; - -use liquid_staking::config::ConfigModule; -use liquid_staking::*; - -pub const LIQUID_STAKING_WASM_PATH: &str = "liquid-staking/output/liquid-staking.wasm"; - -pub static LS_TOKEN_ID: &[u8] = b"LSTOKEN-123456"; -pub static UNSTAKE_TOKEN_ID: &[u8] = b"UNSTAKE-123456"; - -pub static ESDT_ROLES: &[EsdtLocalRole] = &[ - EsdtLocalRole::Mint, - EsdtLocalRole::Burn, - EsdtLocalRole::Transfer, -]; - -pub static SFT_ROLES: &[EsdtLocalRole] = &[ - EsdtLocalRole::NftCreate, - EsdtLocalRole::NftAddQuantity, - EsdtLocalRole::NftBurn, -]; - -pub struct LiquidStakingContractSetup -where - LiquidStakingContractObjBuilder: 'static + Copy + Fn() -> liquid_staking::ContractObj, -{ - pub b_mock: BlockchainStateWrapper, - pub owner_address: Address, - pub sc_wrapper: - ContractObjWrapper, LiquidStakingContractObjBuilder>, -} - -impl LiquidStakingContractSetup -where - LiquidStakingContractObjBuilder: 'static + Copy + Fn() -> liquid_staking::ContractObj, -{ - pub fn new(sc_builder: LiquidStakingContractObjBuilder) -> Self { - let rust_zero = rust_biguint!(0u64); - let mut b_mock = BlockchainStateWrapper::new(); - let owner_address = b_mock.create_user_account(&rust_zero); - - let sc_wrapper = b_mock.create_sc_account( - &rust_zero, - Some(&owner_address), - sc_builder, - LIQUID_STAKING_WASM_PATH, - ); - - b_mock - .execute_tx(&owner_address, &sc_wrapper, &rust_zero, |sc| { - sc.init(); - }) - .assert_ok(); - - b_mock - .execute_tx(&owner_address, &sc_wrapper, &rust_zero, |sc| { - sc.ls_token().set_token_id(managed_token_id!(LS_TOKEN_ID)); - }) - .assert_ok(); - - b_mock - .execute_tx(&owner_address, &sc_wrapper, &rust_zero, |sc| { - sc.unstake_token() - .set_token_id(managed_token_id!(UNSTAKE_TOKEN_ID)); - }) - .assert_ok(); - - b_mock.set_esdt_local_roles(sc_wrapper.address_ref(), LS_TOKEN_ID, ESDT_ROLES); - b_mock.set_esdt_local_roles(sc_wrapper.address_ref(), UNSTAKE_TOKEN_ID, SFT_ROLES); - - b_mock - .execute_tx(&owner_address, &sc_wrapper, &rust_zero, |sc| { - sc.set_state_active(); - }) - .assert_ok(); - - LiquidStakingContractSetup { - b_mock, - owner_address, - sc_wrapper, - } - } - - pub fn to_managed_biguint(value: num_bigint::BigUint) -> BigUint { - BigUint::from_bytes_be(&value.to_bytes_be()) - } - - pub fn exp18(value: u64) -> num_bigint::BigUint { - value.mul(rust_biguint!(10).pow(18)) - } -} diff --git a/liquid-staking/tests/test.rs b/liquid-staking/tests/test.rs deleted file mode 100644 index 1ba9d4d..0000000 --- a/liquid-staking/tests/test.rs +++ /dev/null @@ -1,278 +0,0 @@ -mod contract_interactions; -mod contract_setup; -use std::ops::Mul; - -use contract_setup::*; - -use multiversx_sc_scenario::{num_bigint, rust_biguint, DebugApi}; - -#[test] -fn init_test() { - let _ = LiquidStakingContractSetup::new(liquid_staking::contract_obj); -} - -#[test] -fn liquid_staking_add_liquidity_test() { - DebugApi::dummy(); - let mut sc_setup = LiquidStakingContractSetup::new(liquid_staking::contract_obj); - - sc_setup.deploy_staking_contract(&sc_setup.owner_address.clone(), 1000, 1000, 1500, 0, 0); - - let first_user = sc_setup.setup_new_user(100u64); - sc_setup.add_liquidity(&first_user, 100u64); - sc_setup.check_contract_storage(100, 100, 0, 0); - sc_setup.check_user_balance(&first_user, LS_TOKEN_ID, 100u64); -} - -#[test] -fn liquid_staking_remove_liquidity_test() { - DebugApi::dummy(); - let mut sc_setup = LiquidStakingContractSetup::new(liquid_staking::contract_obj); - - sc_setup.deploy_staking_contract(&sc_setup.owner_address.clone(), 1000, 1000, 1500, 0, 0); - - let first_user = sc_setup.setup_new_user(100u64); - sc_setup.add_liquidity(&first_user, 100u64); - sc_setup.remove_liquidity(&first_user, LS_TOKEN_ID, 90u64); - sc_setup.check_contract_storage(10, 10, 0, 0); - sc_setup.check_user_balance(&first_user, LS_TOKEN_ID, 10u64); - sc_setup.check_user_nft_balance_denominated(&first_user, UNSTAKE_TOKEN_ID, 1, 1); - sc_setup.check_user_egld_balance(&first_user, 0u64); -} - -#[test] -fn liquid_staking_claim_rewards_and_withdraw_test() { - DebugApi::dummy(); - let mut sc_setup = LiquidStakingContractSetup::new(liquid_staking::contract_obj); - - sc_setup.deploy_staking_contract(&sc_setup.owner_address.clone(), 1000, 1000, 1500, 0, 0); - - let first_user = sc_setup.setup_new_user(100u64); - sc_setup.add_liquidity(&first_user, 100u64); - sc_setup.b_mock.set_block_epoch(50u64); - sc_setup.b_mock.set_block_nonce(10u64); - sc_setup.claim_rewards(&first_user); - sc_setup.b_mock.set_block_nonce(20u64); - sc_setup.recompute_token_reserve(&first_user); - sc_setup.delegate_rewards(&first_user); - - sc_setup.remove_liquidity(&first_user, LS_TOKEN_ID, 90u64); - - sc_setup.b_mock.set_block_epoch(60u64); - sc_setup.unbond_tokens(&first_user, UNSTAKE_TOKEN_ID, 1); - - sc_setup.check_user_balance(&first_user, LS_TOKEN_ID, 10u64); - sc_setup.check_user_egld_balance_denominated(&first_user, 91232876712328767122u128); -} - -#[test] -fn liquid_staking_multiple_operations() { - DebugApi::dummy(); - let mut sc_setup = LiquidStakingContractSetup::new(liquid_staking::contract_obj); - - let delegation_contract1 = sc_setup.deploy_staking_contract( - &sc_setup.owner_address.clone(), - 1000, - 1000, - 1100, - 3, - 10_000u64, - ); - let delegation_contract2 = sc_setup.deploy_staking_contract( - &sc_setup.owner_address.clone(), - 1000, - 1000, - 1100, - 3, - 13_000u64, - ); - let delegation_contract3 = sc_setup.deploy_staking_contract( - &sc_setup.owner_address.clone(), - 1000, - 1000, - 1100, - 3, - 11_000u64, - ); - - let first_user = sc_setup.setup_new_user(100u64); - let second_user = sc_setup.setup_new_user(100u64); - let third_user = sc_setup.setup_new_user(100u64); - sc_setup.add_liquidity(&first_user, 10u64); - sc_setup.check_delegation_contract_values(&delegation_contract2, 10u64); - - sc_setup.add_liquidity(&first_user, 20u64); - sc_setup.check_delegation_contract_values(&delegation_contract2, 30u64); - - sc_setup.add_liquidity(&second_user, 50u64); - sc_setup.check_delegation_contract_values(&delegation_contract2, 80u64); - - sc_setup.update_staking_contract_params( - &sc_setup.owner_address.clone(), - &delegation_contract2, - 1080, - 1100, - 3, - 13_000u64, - ); - - sc_setup.add_liquidity(&third_user, 30u64); - sc_setup.check_delegation_contract_values(&delegation_contract3, 30u64); - - sc_setup.update_staking_contract_params( - &sc_setup.owner_address.clone(), - &delegation_contract2, - 1080, - 1100, - 3, - 8_000u64, - ); - sc_setup.update_staking_contract_params( - &sc_setup.owner_address.clone(), - &delegation_contract3, - 1030, - 1100, - 3, - 9_000u64, - ); - - sc_setup.check_user_balance(&first_user, LS_TOKEN_ID, 30u64); - sc_setup.check_user_balance(&second_user, LS_TOKEN_ID, 50u64); - sc_setup.check_user_balance(&third_user, LS_TOKEN_ID, 30u64); - - sc_setup.b_mock.set_block_epoch(10u64); - sc_setup.claim_rewards(&first_user); - sc_setup.b_mock.set_block_nonce(10u64); - sc_setup.recompute_token_reserve(&first_user); - sc_setup.check_user_egld_balance_denominated( - sc_setup.sc_wrapper.address_ref(), - 301369863013698629u128, - ); - sc_setup.check_contract_rewards_storage_denominated(301369863013698629u128); - sc_setup.delegate_rewards_check_insufficient(&first_user); - - sc_setup.add_liquidity(&third_user, 10u64); - - sc_setup.b_mock.set_block_epoch(50u64); - sc_setup.claim_rewards(&first_user); - sc_setup.b_mock.set_block_nonce(20u64); - sc_setup.recompute_token_reserve(&first_user); - sc_setup.check_user_egld_balance_denominated( - sc_setup.sc_wrapper.address_ref(), - 1643835616438356161u128, - ); - sc_setup.check_contract_rewards_storage_denominated(1643835616438356161u128); - sc_setup.delegate_rewards(&first_user); - sc_setup.check_user_egld_balance_denominated(sc_setup.sc_wrapper.address_ref(), 0u128); - - sc_setup.add_liquidity(&first_user, 50u64); - sc_setup.check_delegation_contract_values_denominated( - &delegation_contract1, - 61643835616438356161u128, - ); - sc_setup.update_staking_contract_params( - &sc_setup.owner_address.clone(), - &delegation_contract1, - 1061, - 1100, - 3, - 10_000u64, - ); - - sc_setup.add_liquidity(&second_user, 40u64); - sc_setup.check_delegation_contract_values_denominated( - &delegation_contract1, - 61643835616438356161u128, - ); - sc_setup.update_staking_contract_params( - &sc_setup.owner_address.clone(), - &delegation_contract1, - 1090, - 1100, - 3, - 10_000u64, - ); - - sc_setup.add_liquidity(&third_user, 30u64); - sc_setup.check_delegation_contract_values(&delegation_contract3, 100u64); - sc_setup.update_staking_contract_params( - &sc_setup.owner_address.clone(), - &delegation_contract3, - 1100, - 1100, - 3, - 9_000u64, - ); - - sc_setup.check_user_balance_denominated(&first_user, LS_TOKEN_ID, 79313093831536454488u128); - sc_setup.check_user_balance_denominated(&second_user, LS_TOKEN_ID, 89450475065229163590u128); - sc_setup.check_user_balance_denominated(&third_user, LS_TOKEN_ID, 69560533894550287992u128); - - sc_setup.remove_liquidity(&first_user, LS_TOKEN_ID, 70u64); - sc_setup.check_user_balance_denominated(&first_user, LS_TOKEN_ID, 9313093831536454488u128); - - sc_setup.b_mock.set_block_epoch(60u64); - sc_setup.check_user_egld_balance(&first_user, 20u64); - sc_setup.unbond_tokens(&first_user, UNSTAKE_TOKEN_ID, 1); - - let ls_value = sc_setup.get_ls_value_for_position(1u64); - let initial_egld_balance = exp18_128(20u64); - let ls_token_balance_in_egld = 70 * ls_value; - let rounding_offset = 6u128; - sc_setup.check_user_egld_balance_denominated( - &first_user, - initial_egld_balance + ls_token_balance_in_egld + rounding_offset, - ); -} - -#[test] -fn liquid_staking_multiple_withdraw_test() { - DebugApi::dummy(); - let mut sc_setup = LiquidStakingContractSetup::new(liquid_staking::contract_obj); - - sc_setup.deploy_staking_contract(&sc_setup.owner_address.clone(), 1000, 1000, 1500, 0, 0); - - let first_user = sc_setup.setup_new_user(100u64); - let second_user = sc_setup.setup_new_user(100u64); - let third_user = sc_setup.setup_new_user(100u64); - sc_setup.add_liquidity(&first_user, 50u64); - sc_setup.add_liquidity(&second_user, 40u64); - sc_setup.add_liquidity(&third_user, 40u64); - sc_setup.check_contract_storage(130, 130, 0, 0); - - sc_setup.b_mock.set_block_epoch(50u64); - sc_setup.remove_liquidity(&first_user, LS_TOKEN_ID, 20u64); - sc_setup.remove_liquidity(&second_user, LS_TOKEN_ID, 20u64); - sc_setup.remove_liquidity(&third_user, LS_TOKEN_ID, 20u64); - sc_setup.check_contract_storage(70, 70, 0, 0); - - sc_setup.b_mock.set_block_epoch(60u64); - sc_setup.unbond_tokens(&first_user, UNSTAKE_TOKEN_ID, 1); - sc_setup.check_user_balance(&first_user, LS_TOKEN_ID, 30u64); - sc_setup.check_user_egld_balance(&first_user, 70); - sc_setup.check_user_balance(&second_user, LS_TOKEN_ID, 20u64); - sc_setup.check_user_egld_balance(&second_user, 60); - sc_setup.check_user_balance(&third_user, LS_TOKEN_ID, 20u64); - sc_setup.check_user_egld_balance(&third_user, 60); - sc_setup.check_contract_storage(70, 70, 0, 40); // 20 + 20 (second_user + third_user) -} - -pub fn exp9(value: u64) -> num_bigint::BigUint { - value.mul(rust_biguint!(10).pow(9)) -} - -pub fn exp15(value: u64) -> num_bigint::BigUint { - value.mul(rust_biguint!(10).pow(15)) -} - -pub fn exp17(value: u64) -> num_bigint::BigUint { - value.mul(rust_biguint!(10).pow(17)) -} - -pub fn exp18(value: u64) -> num_bigint::BigUint { - value.mul(rust_biguint!(10).pow(18)) -} - -pub fn exp18_128(value: u64) -> u128 { - u128::from(value).mul(10u128.pow(18)) -} diff --git a/liquid-staking/wasm/Cargo.lock b/liquid-staking/wasm/Cargo.lock deleted file mode 100644 index e4eb316..0000000 --- a/liquid-staking/wasm/Cargo.lock +++ /dev/null @@ -1,215 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "autocfg" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" - -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - -[[package]] -name = "delegation-mock" -version = "0.0.0" -dependencies = [ - "itertools", - "multiversx-sc", -] - -[[package]] -name = "either" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "liquid-staking" -version = "0.0.0" -dependencies = [ - "delegation-mock", - "itertools", - "multiversx-sc", - "multiversx-sc-modules", -] - -[[package]] -name = "liquid-staking-wasm" -version = "0.0.0" -dependencies = [ - "liquid-staking", - "multiversx-sc-wasm-adapter", -] - -[[package]] -name = "multiversx-sc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1291e3712731f4e34775254e9a9b8ff70132d15414f2e51f9bc82c23faf48c" -dependencies = [ - "bitflags", - "hex-literal", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.18.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1da6db65170105c9495848c5e4ba388abb1f9201ff2ca362056c9328f36b7760" -dependencies = [ - "arrayvec", - "multiversx-sc-codec-derive", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.18.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631c4d4b37fc94659c8d6cf559c21b68c68899095201de2e1b779fccad7b0b03" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95f0331893be2b757401b2deaf7d8b8c90745fa69114000d83fe26d8487d35a5" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-modules" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e507a180afcab409cc3d920bc12f3852cf481a6657428879d1a70f6c2666c94" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bd18dbc72c83aa2e9ad3f4a00dcc27c03ada66c13f9056001dc9157055d616" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "proc-macro2" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "syn" -version = "2.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/liquid-staking/wasm/Cargo.toml b/liquid-staking/wasm/Cargo.toml deleted file mode 100644 index 12367cb..0000000 --- a/liquid-staking/wasm/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "liquid-staking-wasm" -version = "0.0.0" -edition = "2021" -publish = false - -[lib] -crate-type = ["cdylib"] - -[profile.release] -codegen-units = 1 -opt-level = "z" -lto = true -debug = false -panic = "abort" -overflow-checks = false - -[dependencies.liquid-staking] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "=0.48.0" - -[workspace] -members = ["."] diff --git a/liquid-staking/wasm/src/lib.rs b/liquid-staking/wasm/src/lib.rs deleted file mode 100644 index aed2896..0000000 --- a/liquid-staking/wasm/src/lib.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Endpoints: 32 -// Async Callback: 1 -// Total number of exported functions: 34 - -#![no_std] -#![allow(internal_features)] -#![feature(lang_items)] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::endpoints! { - liquid_staking - ( - init => init - upgrade => upgrade - registerLsToken => register_ls_token - registerUnstakeToken => register_unstake_token - setStateActive => set_state_active - setStateInactive => set_state_inactive - getState => state - getLsTokenId => ls_token - getLsSupply => ls_token_supply - getVirtualEgldReserve => virtual_egld_reserve - getRewardsReserve => rewards_reserve - getTotalWithdrawnEgld => total_withdrawn_egld - getUnstakeTokenId => unstake_token - getUnstakeTokenSupply => unstake_token_supply - updateMaxDelegationAddressesNumber => update_max_delegation_addresses_number - whitelistDelegationContract => whitelist_delegation_contract - changeDelegationContractAdmin => change_delegation_contract_admin - changeDelegationContractParams => change_delegation_contract_params - getDelegationStatus => get_delegation_status - getDelegationContractStakedAmount => get_delegation_contract_staked_amount - getDelegationContractUnstakedAmount => get_delegation_contract_unstaked_amount - getDelegationContractUnbondedAmount => get_delegation_contract_unbonded_amount - getDelegationAddressesList => delegation_addresses_list - getDelegationClaimStatus => delegation_claim_status - maxDelegationAddresses => max_delegation_addresses - getDelegationContractData => delegation_contract_data - getLsValueForPosition => get_ls_value_for_position - addLiquidity => add_liquidity - removeLiquidity => remove_liquidity - unbondTokens => unbond_tokens - claimRewards => claim_rewards - delegateRewards => delegate_rewards - recomputeTokenReserve => recompute_token_reserve - ) -} - -multiversx_sc_wasm_adapter::async_callback! { liquid_staking }