diff --git a/common/common_structs/src/farm_types.rs b/common/common_structs/src/farm_types.rs index d7d85e20b..676b26e36 100644 --- a/common/common_structs/src/farm_types.rs +++ b/common/common_structs/src/farm_types.rs @@ -80,6 +80,8 @@ pub trait FarmToken { fn get_compounded_rewards(&self) -> BigUint; fn get_initial_farming_tokens(&self) -> BigUint; + + fn get_original_owner(&self) -> ManagedAddress; } impl FarmToken for FarmTokenAttributes { @@ -97,4 +99,9 @@ impl FarmToken for FarmTokenAttributes { fn get_initial_farming_tokens(&self) -> BigUint { &self.current_farm_amount - &self.compounded_reward } + + #[inline] + fn get_original_owner(&self) -> ManagedAddress { + self.original_owner.clone() + } } diff --git a/common/modules/farm/contexts/src/claim_rewards_context.rs b/common/modules/farm/contexts/src/claim_rewards_context.rs index 653c6b2e6..78b8f4e67 100644 --- a/common/modules/farm/contexts/src/claim_rewards_context.rs +++ b/common/modules/farm/contexts/src/claim_rewards_context.rs @@ -2,26 +2,28 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); use common_errors::{ERROR_BAD_PAYMENTS, ERROR_EMPTY_PAYMENTS}; -use common_structs::PaymentAttributesPair; +use common_structs::{PaymentAttributesPair, PaymentsVec}; use multiversx_sc::api::BlockchainApi; use multiversx_sc::contract_base::BlockchainWrapper; pub struct ClaimRewardsContext where M: ManagedTypeApi, - T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode, + T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, { pub first_farm_token: PaymentAttributesPair, - pub additional_payments: ManagedVec>, + pub additional_payments: PaymentsVec, + pub additional_attributes: ManagedVec, + pub all_attributes: ManagedVec, } impl ClaimRewardsContext where M: ManagedTypeApi + BlockchainApi, - T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode, + T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, { pub fn new( - mut payments: ManagedVec>, + mut payments: PaymentsVec, farm_token_id: &TokenIdentifier, api_wrapper: BlockchainWrapper, ) -> Self { @@ -29,22 +31,32 @@ where M::error_api_impl().signal_error(ERROR_EMPTY_PAYMENTS); } + let own_sc_address = api_wrapper.get_sc_address(); + let mut all_attributes = ManagedVec::new(); for p in &payments { if &p.token_identifier != farm_token_id { M::error_api_impl().signal_error(ERROR_BAD_PAYMENTS); } + + let token_data = + api_wrapper.get_esdt_token_data(&own_sc_address, farm_token_id, p.token_nonce); + let token_attributes: T = token_data.decode_attributes(); + all_attributes.push(token_attributes); } let first_payment = payments.get(0); payments.remove(0); - let own_sc_address = api_wrapper.get_sc_address(); - let token_data = api_wrapper.get_esdt_token_data( + // dumb framework errors otherwise + let first_token_data = api_wrapper.get_esdt_token_data( &own_sc_address, farm_token_id, first_payment.token_nonce, ); - let first_token_attributes: T = token_data.decode_attributes(); + let first_token_attributes: T = first_token_data.decode_attributes(); + + let mut additional_attributes = all_attributes.clone(); + additional_attributes.remove(0); ClaimRewardsContext { first_farm_token: PaymentAttributesPair { @@ -52,6 +64,8 @@ where attributes: first_token_attributes, }, additional_payments: payments, + additional_attributes, + all_attributes, } } } diff --git a/common/modules/farm/contexts/src/enter_farm_context.rs b/common/modules/farm/contexts/src/enter_farm_context.rs index 9da789d9a..27a060015 100644 --- a/common/modules/farm/contexts/src/enter_farm_context.rs +++ b/common/modules/farm/contexts/src/enter_farm_context.rs @@ -2,17 +2,28 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); use common_errors::{ERROR_BAD_PAYMENTS, ERROR_EMPTY_PAYMENTS}; +use common_structs::PaymentsVec; +use multiversx_sc::{api::BlockchainApi, contract_base::BlockchainWrapper}; -pub struct EnterFarmContext { +pub struct EnterFarmContext< + M: ManagedTypeApi, + T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, +> { pub farming_token_payment: EsdtTokenPayment, - pub additional_farm_tokens: ManagedVec>, + pub additional_farm_tokens: PaymentsVec, + pub additional_attributes: ManagedVec, } -impl EnterFarmContext { +impl< + M: ManagedTypeApi + BlockchainApi, + T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, + > EnterFarmContext +{ pub fn new( - mut payments: ManagedVec>, + mut payments: PaymentsVec, farming_token_id: &TokenIdentifier, farm_token_id: &TokenIdentifier, + api_wrapper: BlockchainWrapper, ) -> Self { if payments.is_empty() { M::error_api_impl().signal_error(ERROR_EMPTY_PAYMENTS); @@ -24,15 +35,24 @@ impl EnterFarmContext { } payments.remove(0); + + let own_sc_address = api_wrapper.get_sc_address(); + let mut additional_attributes = ManagedVec::new(); for p in &payments { if &p.token_identifier != farm_token_id { M::error_api_impl().signal_error(ERROR_BAD_PAYMENTS); } + + let token_data = + api_wrapper.get_esdt_token_data(&own_sc_address, farm_token_id, p.token_nonce); + let token_attributes: T = token_data.decode_attributes(); + additional_attributes.push(token_attributes); } EnterFarmContext { farming_token_payment, additional_farm_tokens: payments, + additional_attributes, } } } diff --git a/common/modules/farm/events/src/events.rs b/common/modules/farm/events/src/events.rs index c32988130..6b55fed0c 100644 --- a/common/modules/farm/events/src/events.rs +++ b/common/modules/farm/events/src/events.rs @@ -142,7 +142,7 @@ pub trait EventsModule { fn emit_claim_rewards_event< 'a, C: FarmContracTraitBounds, - AttributesType: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode, + AttributesType: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, >( &self, original_caller: &ManagedAddress, @@ -189,7 +189,7 @@ pub trait EventsModule { fn emit_compound_rewards_event< 'a, C: FarmContracTraitBounds, - AttributesType: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode, + AttributesType: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, >( self, original_caller: &ManagedAddress, diff --git a/common/modules/farm/farm_base_impl/src/base_traits_impl.rs b/common/modules/farm/farm_base_impl/src/base_traits_impl.rs index f2ccc293c..7fdb6cdc3 100644 --- a/common/modules/farm/farm_base_impl/src/base_traits_impl.rs +++ b/common/modules/farm/farm_base_impl/src/base_traits_impl.rs @@ -1,12 +1,11 @@ multiversx_sc::imports!(); -use common_structs::{FarmToken, FarmTokenAttributes, Nonce}; +use common_structs::{FarmToken, FarmTokenAttributes, Nonce, PaymentsVec}; use config::ConfigModule; use contexts::storage_cache::StorageCache; use core::marker::PhantomData; use fixed_supply_token::FixedSupplyToken; use mergeable::Mergeable; -use multiversx_sc_modules::transfer_role_proxy::PaymentsVec; use rewards::RewardsModule; pub trait AllBaseFarmImplTraits = @@ -30,8 +29,8 @@ pub trait FarmContract { + FixedSupplyToken<::Api> + FarmToken<::Api> + From::Api>> - + Into::Api>> = - FarmTokenAttributes<::Api>; + + Into::Api>> + + ManagedVecItem = FarmTokenAttributes<::Api>; #[inline] fn mint_rewards( @@ -192,27 +191,21 @@ pub trait FarmContract { fn check_and_update_user_farm_position( sc: &Self::FarmSc, user: &ManagedAddress<::Api>, - farm_positions: &PaymentsVec<::Api>, + farm_tokens: &PaymentsVec<::Api>, + farm_attributes: &ManagedVec<::Api, Self::AttributesType>, ) { - let farm_token_mapper = sc.farm_token(); - for farm_position in farm_positions { - farm_token_mapper.require_same_token(&farm_position.token_identifier); - - if sc.is_old_farm_position(farm_position.token_nonce) { + for (attr, farm_token) in farm_attributes.into_iter().zip(farm_tokens.iter()) { + if sc.is_old_farm_position(farm_token.token_nonce) { continue; } - let token_attributes: FarmTokenAttributes<::Api> = - farm_token_mapper.get_token_attributes(farm_position.token_nonce); - - if &token_attributes.original_owner != user { - Self::decrease_user_farm_position(sc, &farm_position); - Self::increase_user_farm_position(sc, user, &farm_position.amount); + if &attr.get_original_owner() != user { + Self::decrease_user_farm_position(sc, &farm_token, &attr); + Self::increase_user_farm_position(sc, user, &farm_token.amount); } } } - #[inline] fn increase_user_farm_position( sc: &Self::FarmSc, user: &ManagedAddress<::Api>, @@ -227,16 +220,13 @@ pub trait FarmContract { fn decrease_user_farm_position( sc: &Self::FarmSc, farm_position: &EsdtTokenPayment<::Api>, + attributes: &Self::AttributesType, ) { if sc.is_old_farm_position(farm_position.token_nonce) { return; } - let farm_token_mapper = sc.farm_token(); - let token_attributes: FarmTokenAttributes<::Api> = - farm_token_mapper.get_token_attributes(farm_position.token_nonce); - - sc.user_total_farm_position(&token_attributes.original_owner) + sc.user_total_farm_position(&attributes.get_original_owner()) .update(|user_total_farm_position| { if user_total_farm_position.total_farm_position > farm_position.amount { user_total_farm_position.total_farm_position -= &farm_position.amount; diff --git a/common/modules/farm/farm_base_impl/src/claim_rewards.rs b/common/modules/farm/farm_base_impl/src/claim_rewards.rs index d9dfe293e..30b943089 100644 --- a/common/modules/farm/farm_base_impl/src/claim_rewards.rs +++ b/common/modules/farm/farm_base_impl/src/claim_rewards.rs @@ -11,7 +11,7 @@ use fixed_supply_token::FixedSupplyToken; pub struct InternalClaimRewardsResult<'a, C, T> where C: FarmContracTraitBounds, - T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode, + T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, { pub context: ClaimRewardsContext, pub storage_cache: StorageCache<'a, C>, @@ -81,7 +81,12 @@ pub trait BaseClaimRewardsModule: ); storage_cache.reward_reserve -= &reward; - FC::check_and_update_user_farm_position(self, &caller, &payments); + FC::check_and_update_user_farm_position( + self, + &caller, + &payments, + &claim_rewards_context.all_attributes, + ); let farm_token_mapper = self.farm_token(); let base_attributes = FC::create_claim_rewards_initial_attributes( @@ -90,10 +95,11 @@ pub trait BaseClaimRewardsModule: token_attributes, storage_cache.reward_per_share.clone(), ); + let new_token_attributes = self.merge_attributes_from_payments( base_attributes, + &claim_rewards_context.additional_attributes, &claim_rewards_context.additional_payments, - &farm_token_mapper, ); let new_farm_token = PaymentAttributesPair { payment: EsdtTokenPayment::new( diff --git a/common/modules/farm/farm_base_impl/src/compound_rewards.rs b/common/modules/farm/farm_base_impl/src/compound_rewards.rs index 06e52585c..530f732c0 100644 --- a/common/modules/farm/farm_base_impl/src/compound_rewards.rs +++ b/common/modules/farm/farm_base_impl/src/compound_rewards.rs @@ -12,7 +12,7 @@ use fixed_supply_token::FixedSupplyToken; pub struct InternalCompoundRewardsResult<'a, C, T> where C: FarmContracTraitBounds, - T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode, + T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, { pub context: CompoundRewardsContext, pub storage_cache: StorageCache<'a, C>, @@ -70,7 +70,12 @@ pub trait BaseCompoundRewardsModule: storage_cache.reward_reserve -= &reward; storage_cache.farm_token_supply += &reward; - FC::check_and_update_user_farm_position(self, &caller, &payments); + FC::check_and_update_user_farm_position( + self, + &caller, + &payments, + &compound_rewards_context.all_attributes, + ); let farm_token_mapper = self.farm_token(); let base_attributes = FC::create_compound_rewards_initial_attributes( @@ -82,6 +87,7 @@ pub trait BaseCompoundRewardsModule: ); let new_farm_token = self.merge_and_create_token( base_attributes, + &compound_rewards_context.additional_attributes, &compound_rewards_context.additional_payments, &farm_token_mapper, ); diff --git a/common/modules/farm/farm_base_impl/src/enter_farm.rs b/common/modules/farm/farm_base_impl/src/enter_farm.rs index 02e20d42a..850606829 100644 --- a/common/modules/farm/farm_base_impl/src/enter_farm.rs +++ b/common/modules/farm/farm_base_impl/src/enter_farm.rs @@ -10,9 +10,9 @@ use contexts::{ pub struct InternalEnterFarmResult<'a, C, T> where C: FarmContracTraitBounds, - T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode, + T: Clone + TopEncode + TopDecode + NestedEncode + NestedDecode + ManagedVecItem, { - pub context: EnterFarmContext, + pub context: EnterFarmContext, pub storage_cache: StorageCache<'a, C>, pub new_farm_token: PaymentAttributesPair, pub created_with_merge: bool, @@ -42,6 +42,7 @@ pub trait BaseEnterFarmModule: payments, &storage_cache.farming_token_id, &storage_cache.farm_token_id, + self.blockchain(), ); // The order is important - first check and update, then increase position @@ -49,6 +50,7 @@ pub trait BaseEnterFarmModule: self, &caller, &enter_farm_context.additional_farm_tokens, + &enter_farm_context.additional_attributes, ); FC::increase_user_farm_position( self, @@ -69,6 +71,7 @@ pub trait BaseEnterFarmModule: ); let new_farm_token = self.merge_and_create_token( base_attributes, + &enter_farm_context.additional_attributes, &enter_farm_context.additional_farm_tokens, &farm_token_mapper, ); diff --git a/common/modules/farm/farm_base_impl/src/exit_farm.rs b/common/modules/farm/farm_base_impl/src/exit_farm.rs index 315abe421..30d420787 100644 --- a/common/modules/farm/farm_base_impl/src/exit_farm.rs +++ b/common/modules/farm/farm_base_impl/src/exit_farm.rs @@ -62,7 +62,7 @@ pub trait BaseExitFarmModule: ); storage_cache.reward_reserve -= &reward; - FC::decrease_user_farm_position(self, &payment); + FC::decrease_user_farm_position(self, &payment, &exit_farm_context.farm_token.attributes); let farming_token_amount = token_attributes.get_total_supply(); let farming_token_payment = EsdtTokenPayment::new( diff --git a/common/modules/utils/src/lib.rs b/common/modules/utils/src/lib.rs index 990b88329..9077b7203 100644 --- a/common/modules/utils/src/lib.rs +++ b/common/modules/utils/src/lib.rs @@ -57,7 +57,7 @@ pub trait UtilsModule { } fn merge_from_payments_and_burn< - T: FixedSupplyToken + Mergeable + TopDecode, + T: FixedSupplyToken + Mergeable + TopDecode + ManagedVecItem, >( &self, mut payments: PaymentsVec, @@ -68,23 +68,29 @@ pub trait UtilsModule { self.get_attributes_as_part_of_fixed_supply(&first_payment, mapper); mapper.nft_burn(first_payment.token_nonce, &first_payment.amount); + let mut other_attributes = ManagedVec::new(); + for payment in &payments { + let attr = mapper.get_token_attributes(payment.token_nonce); + other_attributes.push(attr); + } + let output_attributes = - self.merge_attributes_from_payments(base_attributes, &payments, mapper); + self.merge_attributes_from_payments(base_attributes, &other_attributes, &payments); self.send().esdt_local_burn_multi(&payments); output_attributes } fn merge_attributes_from_payments< - T: FixedSupplyToken + Mergeable + TopDecode, + T: FixedSupplyToken + Mergeable + TopDecode + ManagedVecItem, >( &self, mut base_attributes: T, + other_attributes: &ManagedVec, payments: &PaymentsVec, - mapper: &NonFungibleTokenMapper, ) -> T { - for payment in payments { - let attributes: T = self.get_attributes_as_part_of_fixed_supply(&payment, mapper); + for (attr, payment) in other_attributes.into_iter().zip(payments.iter()) { + let attributes = attr.into_part(&payment.amount); base_attributes.merge_with(attributes); } @@ -98,15 +104,17 @@ pub trait UtilsModule { + TopEncode + TopDecode + NestedEncode - + NestedDecode, + + NestedDecode + + ManagedVecItem, >( &self, base_attributes: T, + other_attributes: &ManagedVec, payments: &PaymentsVec, mapper: &NonFungibleTokenMapper, ) -> PaymentAttributesPair { let output_attributes = - self.merge_attributes_from_payments(base_attributes, payments, mapper); + self.merge_attributes_from_payments(base_attributes, other_attributes, payments); let new_token_amount = output_attributes.get_total_supply(); let new_token_payment = mapper.nft_create(new_token_amount, &output_attributes); diff --git a/dex/farm-with-locked-rewards/src/lib.rs b/dex/farm-with-locked-rewards/src/lib.rs index d704a80df..34600f80b 100644 --- a/dex/farm-with-locked-rewards/src/lib.rs +++ b/dex/farm-with-locked-rewards/src/lib.rs @@ -182,8 +182,6 @@ pub trait Farm: orig_caller.clone(), ); - self.clear_user_energy_if_needed(&orig_caller); - (exit_farm_result.farming_tokens, locked_rewards_payment).into() } diff --git a/dex/farm/src/base_functions.rs b/dex/farm/src/base_functions.rs index 48a59ac56..16704331d 100644 --- a/dex/farm/src/base_functions.rs +++ b/dex/farm/src/base_functions.rs @@ -7,7 +7,9 @@ multiversx_sc::derive_imports!(); use core::marker::PhantomData; use common_errors::ERROR_ZERO_AMOUNT; +use common_structs::FarmToken; use common_structs::FarmTokenAttributes; +use common_structs::PaymentsVec; use contexts::storage_cache::StorageCache; use farm_base_impl::base_traits_impl::{DefaultFarmWrapper, FarmContract}; @@ -81,6 +83,11 @@ pub trait BaseFunctionsModule: &base_enter_farm_result.storage_cache.farm_token_supply, ); + self.delete_user_energy_if_needed::( + &base_enter_farm_result.context.additional_farm_tokens, + &base_enter_farm_result.context.additional_attributes, + ); + self.emit_enter_farm_event( &caller, base_enter_farm_result.context.farming_token_payment, @@ -97,7 +104,8 @@ pub trait BaseFunctionsModule: caller: ManagedAddress, ) -> ClaimRewardsResultWrapper { let payments = self.call_value().all_esdt_transfers().clone_value(); - let base_claim_rewards_result = self.claim_rewards_base::(caller.clone(), payments); + let base_claim_rewards_result = + self.claim_rewards_base::(caller.clone(), payments.clone()); let output_farm_token_payment = base_claim_rewards_result.new_farm_token.payment.clone(); let rewards_payment = base_claim_rewards_result.rewards; @@ -106,6 +114,11 @@ pub trait BaseFunctionsModule: &base_claim_rewards_result.storage_cache.farm_token_supply, ); + self.delete_user_energy_if_needed::( + &payments, + &base_claim_rewards_result.context.all_attributes, + ); + self.emit_claim_rewards_event( &caller, base_claim_rewards_result.context, @@ -127,7 +140,7 @@ pub trait BaseFunctionsModule: ) -> EsdtTokenPayment { let payments = self.call_value().all_esdt_transfers().clone_value(); let base_compound_rewards_result = - self.compound_rewards_base::(caller.clone(), payments); + self.compound_rewards_base::(caller.clone(), payments.clone()); let output_farm_token_payment = base_compound_rewards_result.new_farm_token.payment.clone(); @@ -135,6 +148,11 @@ pub trait BaseFunctionsModule: &base_compound_rewards_result.storage_cache.farm_token_supply, ); + self.delete_user_energy_if_needed::( + &payments, + &base_compound_rewards_result.context.all_attributes, + ); + self.emit_compound_rewards_event( &caller, base_compound_rewards_result.context, @@ -152,7 +170,7 @@ pub trait BaseFunctionsModule: caller: ManagedAddress, payment: EsdtTokenPayment, ) -> ExitFarmResultWrapper { - let base_exit_farm_result = self.exit_farm_base::(caller.clone(), payment); + let base_exit_farm_result = self.exit_farm_base::(caller.clone(), payment.clone()); let mut farming_token_payment = base_exit_farm_result.farming_token_payment; let reward_payment = base_exit_farm_result.reward_payment; @@ -161,6 +179,13 @@ pub trait BaseFunctionsModule: &base_exit_farm_result.storage_cache.farm_token_supply, ); + self.delete_user_energy_if_needed::( + &ManagedVec::from_single_item(payment), + &ManagedVec::from_single_item( + base_exit_farm_result.context.farm_token.attributes.clone(), + ), + ); + FC::apply_penalty( self, &mut farming_token_payment.amount, @@ -188,9 +213,18 @@ pub trait BaseFunctionsModule: ) -> FC::AttributesType { let payments = self.get_non_empty_payments(); let token_mapper = self.farm_token(); - token_mapper.require_all_same_token(&payments); - FC::check_and_update_user_farm_position(self, orig_caller, &payments); + let mut all_attributes = ManagedVec::new(); + for payment in &payments { + token_mapper.require_same_token(&payment.token_identifier); + + let attr = token_mapper.get_token_attributes(payment.token_nonce); + all_attributes.push(attr); + } + + self.delete_user_energy_if_needed::(&payments, &all_attributes); + + FC::check_and_update_user_farm_position(self, orig_caller, &payments, &all_attributes); self.merge_from_payments_and_burn(payments, &token_mapper) } @@ -253,6 +287,26 @@ pub trait BaseFunctionsModule: self.per_block_reward_amount().set(&per_block_amount); } + fn delete_user_energy_if_needed>( + &self, + payments: &PaymentsVec, + all_attributes: &ManagedVec, + ) { + let mut processed_users = ManagedMap::new(); + for (payment, attr) in payments.iter().zip(all_attributes.into_iter()) { + let original_owner = attr.get_original_owner(); + if processed_users.contains(original_owner.as_managed_buffer()) + || self.is_old_farm_position(payment.token_nonce) + { + continue; + } + + self.clear_user_energy_if_needed(&original_owner); + + processed_users.put(original_owner.as_managed_buffer(), &ManagedBuffer::new()); + } + } + fn require_queried(&self) { let caller = self.blockchain().get_caller(); let sc_address = self.blockchain().get_sc_address(); diff --git a/dex/farm/src/lib.rs b/dex/farm/src/lib.rs index 1a35c1631..5bf5dba64 100644 --- a/dex/farm/src/lib.rs +++ b/dex/farm/src/lib.rs @@ -177,8 +177,6 @@ pub trait Farm: self.send_payment_non_zero(&caller, &exit_farm_result.farming_tokens); self.send_payment_non_zero(&caller, &exit_farm_result.rewards); - self.clear_user_energy_if_needed(&orig_caller); - (exit_farm_result.farming_tokens, exit_farm_result.rewards).into() } diff --git a/dex/farm/tests/energy_update_test.rs b/dex/farm/tests/energy_update_test.rs index 1d8f78a98..e091a028e 100644 --- a/dex/farm/tests/energy_update_test.rs +++ b/dex/farm/tests/energy_update_test.rs @@ -2,7 +2,14 @@ mod farm_setup; +use common_structs::FarmTokenAttributes; +use farm::Farm; use farm_setup::multi_user_farm_setup::*; +use multiversx_sc::imports::OptionalValue; +use multiversx_sc_scenario::{ + managed_biguint, rust_biguint, whitebox_legacy::TxTokenTransfer, DebugApi, +}; +use weekly_rewards_splitting::global_info::WeeklyRewardsGlobalInfo; #[test] fn test_farm_setup() { @@ -53,3 +60,129 @@ fn test_energy_update_no_claim_current_week() { farm_setup.update_energy_for_user(); farm_setup.check_farm_claim_progress_energy(0); } + +#[test] +fn enter_farm_other_users_pos_test() { + DebugApi::dummy(); + + let mut farm_setup = MultiUserFarmSetup::new( + farm::contract_obj, + energy_factory_mock::contract_obj, + energy_update::contract_obj, + ); + + let first_farm_token_amount = 100_000_000; + let first_user = farm_setup.first_user.clone(); + let second_user = farm_setup.second_user.clone(); + + let first_user_energy_amount = 1_000; + let second_user_energy_amount = 5_000; + farm_setup.set_user_energy(&first_user, first_user_energy_amount, 13, 1); + farm_setup.set_user_energy(&second_user, second_user_energy_amount, 13, 1); + + farm_setup.enter_farm(&first_user, first_farm_token_amount); + + let token_attributes: FarmTokenAttributes = farm_setup + .b_mock + .get_nft_attributes(&first_user, FARM_TOKEN_ID, 1) + .unwrap(); + + // first user transfer pos to second user + farm_setup.b_mock.set_nft_balance( + &second_user, + FARM_TOKEN_ID, + 1, + &rust_biguint!(first_farm_token_amount), + &token_attributes, + ); + farm_setup.b_mock.set_nft_balance( + &first_user, + FARM_TOKEN_ID, + 1, + &rust_biguint!(0), + &token_attributes, + ); + + let transfers = [ + TxTokenTransfer { + token_identifier: FARMING_TOKEN_ID.to_vec(), + nonce: 0, + value: rust_biguint!(1_000), + }, + TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: 1, + value: rust_biguint!(first_farm_token_amount), + }, + ]; + + farm_setup + .b_mock + .execute_esdt_multi_transfer(&second_user, &farm_setup.farm_wrapper, &transfers, |sc| { + sc.enter_farm_endpoint(OptionalValue::None); + + let actual_energy = sc.total_energy_for_week(1).get(); + assert_eq!(actual_energy, managed_biguint!(second_user_energy_amount)); + }) + .assert_ok(); +} + +#[test] +fn exit_other_users_pos_test() { + DebugApi::dummy(); + + let mut farm_setup = MultiUserFarmSetup::new( + farm::contract_obj, + energy_factory_mock::contract_obj, + energy_update::contract_obj, + ); + + let first_farm_token_amount = 100_000_000; + let first_user = farm_setup.first_user.clone(); + let second_user = farm_setup.second_user.clone(); + + let first_user_energy_amount = 1_000; + let second_user_energy_amount = 200; + farm_setup.set_user_energy(&first_user, first_user_energy_amount, 13, 1); + farm_setup.set_user_energy(&second_user, second_user_energy_amount, 13, 1); + + farm_setup.enter_farm(&first_user, first_farm_token_amount); + + let token_attributes: FarmTokenAttributes = farm_setup + .b_mock + .get_nft_attributes(&first_user, FARM_TOKEN_ID, 1) + .unwrap(); + + // first user transfer pos to second user + farm_setup.b_mock.set_nft_balance( + &second_user, + FARM_TOKEN_ID, + 1, + &rust_biguint!(first_farm_token_amount), + &token_attributes, + ); + farm_setup.b_mock.set_nft_balance( + &first_user, + FARM_TOKEN_ID, + 1, + &rust_biguint!(0), + &token_attributes, + ); + + farm_setup + .b_mock + .execute_esdt_transfer( + &second_user, + &farm_setup.farm_wrapper, + FARM_TOKEN_ID, + 1, + &rust_biguint!(first_farm_token_amount), + |sc| { + sc.exit_farm_endpoint(OptionalValue::None); + + let actual_energy = sc.total_energy_for_week(1).get(); + assert_eq!(actual_energy, managed_biguint!(0)); + }, + ) + .assert_ok(); +} diff --git a/energy-integration/farm-boosted-yields/src/lib.rs b/energy-integration/farm-boosted-yields/src/lib.rs index 09bacc676..0c8543132 100644 --- a/energy-integration/farm-boosted-yields/src/lib.rs +++ b/energy-integration/farm-boosted-yields/src/lib.rs @@ -15,6 +15,7 @@ use weekly_rewards_splitting::{ pub mod boosted_yields_factors; const MAX_PERCENT: u64 = 10_000; +const DEFAULT_MIN_FARM_AMT: u64 = 1; pub struct SplitReward { pub base_farm: BigUint, @@ -124,14 +125,19 @@ pub trait FarmBoostedYieldsModule: fn clear_user_energy_if_needed(&self, original_caller: &ManagedAddress) { let opt_config = self.try_get_boosted_yields_config(); let user_total_farm_position = self.get_user_total_farm_position(original_caller); - if let Some(config) = opt_config { - let boosted_yields_factors = config.get_latest_factors(); - self.clear_user_energy( - original_caller, - &user_total_farm_position.total_farm_position, - &boosted_yields_factors.min_farm_amount, - ); - } + let min_farm_amount = match opt_config { + Some(config) => { + let boosted_yields_factors = config.get_latest_factors(); + boosted_yields_factors.min_farm_amount.clone() + } + None => BigUint::from(DEFAULT_MIN_FARM_AMT), + }; + + self.clear_user_energy( + original_caller, + &user_total_farm_position.total_farm_position, + &min_farm_amount, + ); } #[view(getBoostedYieldsRewardsPercentage)] diff --git a/farm-staking/farm-staking/src/base_impl_wrapper.rs b/farm-staking/farm-staking/src/base_impl_wrapper.rs index 2b671677f..da30a1c84 100644 --- a/farm-staking/farm-staking/src/base_impl_wrapper.rs +++ b/farm-staking/farm-staking/src/base_impl_wrapper.rs @@ -5,7 +5,6 @@ use core::marker::PhantomData; use common_structs::FarmToken; use contexts::storage_cache::StorageCache; use farm_base_impl::base_traits_impl::FarmContract; -use multiversx_sc_modules::transfer_role_proxy::PaymentsVec; use crate::token_attributes::StakingFarmTokenAttributes; @@ -184,61 +183,4 @@ where original_owner: caller, } } - - fn check_and_update_user_farm_position( - sc: &Self::FarmSc, - user: &ManagedAddress<::Api>, - farm_positions: &PaymentsVec<::Api>, - ) { - let farm_token_mapper = sc.farm_token(); - for farm_position in farm_positions { - if sc.is_old_farm_position(farm_position.token_nonce) { - continue; - } - - farm_token_mapper.require_same_token(&farm_position.token_identifier); - - let token_attributes: StakingFarmTokenAttributes<::Api> = - farm_token_mapper.get_token_attributes(farm_position.token_nonce); - - if &token_attributes.original_owner != user { - Self::decrease_user_farm_position(sc, &farm_position); - Self::increase_user_farm_position(sc, user, &farm_position.amount); - } - } - } - - #[inline] - fn increase_user_farm_position( - sc: &Self::FarmSc, - user: &ManagedAddress<::Api>, - increase_farm_position_amount: &BigUint<::Api>, - ) { - let mut user_total_farm_position = sc.get_user_total_farm_position(user); - user_total_farm_position.total_farm_position += increase_farm_position_amount; - sc.user_total_farm_position(user) - .set(user_total_farm_position); - } - - fn decrease_user_farm_position( - sc: &Self::FarmSc, - farm_position: &EsdtTokenPayment<::Api>, - ) { - if sc.is_old_farm_position(farm_position.token_nonce) { - return; - } - - let farm_token_mapper = sc.farm_token(); - let token_attributes: StakingFarmTokenAttributes<::Api> = - farm_token_mapper.get_token_attributes(farm_position.token_nonce); - - sc.user_total_farm_position(&token_attributes.original_owner) - .update(|user_total_farm_position| { - if user_total_farm_position.total_farm_position > farm_position.amount { - user_total_farm_position.total_farm_position -= &farm_position.amount; - } else { - user_total_farm_position.total_farm_position = BigUint::zero(); - } - }); - } } diff --git a/farm-staking/farm-staking/src/claim_stake_farm_rewards.rs b/farm-staking/farm-staking/src/claim_stake_farm_rewards.rs index 93d4802ea..53050ba98 100644 --- a/farm-staking/farm-staking/src/claim_stake_farm_rewards.rs +++ b/farm-staking/farm-staking/src/claim_stake_farm_rewards.rs @@ -30,6 +30,7 @@ pub trait ClaimStakeFarmRewardsModule: + weekly_rewards_splitting::locked_token_buckets::WeeklyRewardsLockedTokenBucketsModule + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + energy_query::EnergyQueryModule + + crate::delete_energy::DeleteEnergyModule { #[payable("*")] #[endpoint(claimRewards)] @@ -62,11 +63,12 @@ pub trait ClaimStakeFarmRewardsModule: opt_new_farming_amount: Option, ) -> ClaimRewardsResultType { self.migrate_old_farm_positions(&original_caller); + let payment = self.call_value().single_esdt(); let mut claim_result = self .claim_rewards_base_no_farm_token_mint::>( original_caller.clone(), - ManagedVec::from_single_item(payment), + ManagedVec::from_single_item(payment.clone()), ); let mut virtual_farm_token = claim_result.new_farm_token.clone(); @@ -86,6 +88,11 @@ pub trait ClaimStakeFarmRewardsModule: self.set_farm_supply_for_current_week(&claim_result.storage_cache.farm_token_supply); } + self.delete_user_energy_if_needed::>( + &ManagedVec::from_single_item(payment), + &claim_result.context.all_attributes, + ); + self.update_energy_and_progress(&original_caller); let new_farm_token_nonce = self.send().esdt_nft_create_compact( diff --git a/farm-staking/farm-staking/src/compound_stake_farm_rewards.rs b/farm-staking/farm-staking/src/compound_stake_farm_rewards.rs index a7d83d574..cd8df29a9 100644 --- a/farm-staking/farm-staking/src/compound_stake_farm_rewards.rs +++ b/farm-staking/farm-staking/src/compound_stake_farm_rewards.rs @@ -28,21 +28,28 @@ pub trait CompoundStakeFarmRewardsModule: + weekly_rewards_splitting::locked_token_buckets::WeeklyRewardsLockedTokenBucketsModule + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + energy_query::EnergyQueryModule + + crate::delete_energy::DeleteEnergyModule { #[payable("*")] #[endpoint(compoundRewards)] fn compound_rewards(&self) -> EsdtTokenPayment { let caller = self.blockchain().get_caller(); self.migrate_old_farm_positions(&caller); + let payments = self.get_non_empty_payments(); - let compound_result = - self.compound_rewards_base::>(caller.clone(), payments); + let compound_result = self + .compound_rewards_base::>(caller.clone(), payments.clone()); let new_farm_token = compound_result.new_farm_token.payment.clone(); self.send_payment_non_zero(&caller, &new_farm_token); self.set_farm_supply_for_current_week(&compound_result.storage_cache.farm_token_supply); + self.delete_user_energy_if_needed::>( + &payments, + &compound_result.context.all_attributes, + ); + self.emit_compound_rewards_event( &caller, compound_result.context, diff --git a/farm-staking/farm-staking/src/delete_energy.rs b/farm-staking/farm-staking/src/delete_energy.rs new file mode 100644 index 000000000..7c80412eb --- /dev/null +++ b/farm-staking/farm-staking/src/delete_energy.rs @@ -0,0 +1,46 @@ +use common_structs::{FarmToken, PaymentsVec}; +use farm_base_impl::base_traits_impl::FarmContract; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait DeleteEnergyModule: + rewards::RewardsModule + + config::ConfigModule + + token_send::TokenSendModule + + farm_token::FarmTokenModule + + pausable::PausableModule + + permissions_module::PermissionsModule + + events::EventsModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + utils::UtilsModule + + farm_boosted_yields::FarmBoostedYieldsModule + + farm_boosted_yields::boosted_yields_factors::BoostedYieldsFactorsModule + + week_timekeeping::WeekTimekeepingModule + + weekly_rewards_splitting::WeeklyRewardsSplittingModule + + weekly_rewards_splitting::events::WeeklyRewardsSplittingEventsModule + + weekly_rewards_splitting::global_info::WeeklyRewardsGlobalInfo + + weekly_rewards_splitting::locked_token_buckets::WeeklyRewardsLockedTokenBucketsModule + + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + + energy_query::EnergyQueryModule +{ + fn delete_user_energy_if_needed>( + &self, + payments: &PaymentsVec, + all_attributes: &ManagedVec, + ) { + let mut processed_users = ManagedMap::new(); + for (payment, attr) in payments.iter().zip(all_attributes.into_iter()) { + let original_owner = attr.get_original_owner(); + if processed_users.contains(original_owner.as_managed_buffer()) + || self.is_old_farm_position(payment.token_nonce) + { + continue; + } + + self.clear_user_energy_if_needed(&original_owner); + + processed_users.put(original_owner.as_managed_buffer(), &ManagedBuffer::new()); + } + } +} diff --git a/farm-staking/farm-staking/src/lib.rs b/farm-staking/farm-staking/src/lib.rs index 5fd2b28d6..589748364 100644 --- a/farm-staking/farm-staking/src/lib.rs +++ b/farm-staking/farm-staking/src/lib.rs @@ -19,6 +19,7 @@ pub mod claim_only_boosted_staking_rewards; pub mod claim_stake_farm_rewards; pub mod compound_stake_farm_rewards; pub mod custom_rewards; +pub mod delete_energy; pub mod farm_token_roles; pub mod stake_farm; pub mod token_attributes; @@ -60,6 +61,7 @@ pub trait FarmStaking: + weekly_rewards_splitting::locked_token_buckets::WeeklyRewardsLockedTokenBucketsModule + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + energy_query::EnergyQueryModule + + delete_energy::DeleteEnergyModule { #[init] fn init( @@ -141,9 +143,18 @@ pub trait FarmStaking: ) -> FC::AttributesType { let payments = self.get_non_empty_payments(); let token_mapper = self.farm_token(); - token_mapper.require_all_same_token(&payments); - FC::check_and_update_user_farm_position(self, orig_caller, &payments); + let mut all_attributes = ManagedVec::new(); + for payment in &payments { + token_mapper.require_same_token(&payment.token_identifier); + + let attr = token_mapper.get_token_attributes(payment.token_nonce); + all_attributes.push(attr); + } + + self.delete_user_energy_if_needed::(&payments, &all_attributes); + + FC::check_and_update_user_farm_position(self, orig_caller, &payments, &all_attributes); self.merge_from_payments_and_burn(payments, &token_mapper) } diff --git a/farm-staking/farm-staking/src/stake_farm.rs b/farm-staking/farm-staking/src/stake_farm.rs index 7ab3dea1c..a022b689d 100644 --- a/farm-staking/farm-staking/src/stake_farm.rs +++ b/farm-staking/farm-staking/src/stake_farm.rs @@ -31,6 +31,7 @@ pub trait StakeFarmModule: + weekly_rewards_splitting::locked_token_buckets::WeeklyRewardsLockedTokenBucketsModule + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + energy_query::EnergyQueryModule + + crate::delete_energy::DeleteEnergyModule { #[payable("*")] #[endpoint(stakeFarmThroughProxy)] @@ -86,6 +87,11 @@ pub trait StakeFarmModule: self.set_farm_supply_for_current_week(&enter_result.storage_cache.farm_token_supply); + self.delete_user_energy_if_needed::>( + &enter_result.context.additional_farm_tokens, + &enter_result.context.additional_attributes, + ); + self.update_energy_and_progress(&original_caller); self.emit_enter_farm_event( diff --git a/farm-staking/farm-staking/src/token_attributes.rs b/farm-staking/farm-staking/src/token_attributes.rs index 59f8c6326..0fcabeba3 100644 --- a/farm-staking/farm-staking/src/token_attributes.rs +++ b/farm-staking/farm-staking/src/token_attributes.rs @@ -82,6 +82,10 @@ impl FarmToken for StakingFarmTokenAttributes { fn get_initial_farming_tokens(&self) -> BigUint { &self.current_farm_amount - &self.compounded_reward } + + fn get_original_owner(&self) -> ManagedAddress { + self.original_owner.clone() + } } impl FixedSupplyToken for StakingFarmTokenAttributes { diff --git a/farm-staking/farm-staking/src/unstake_farm.rs b/farm-staking/farm-staking/src/unstake_farm.rs index e40642b4f..1d40cfd15 100644 --- a/farm-staking/farm-staking/src/unstake_farm.rs +++ b/farm-staking/farm-staking/src/unstake_farm.rs @@ -30,6 +30,7 @@ pub trait UnstakeFarmModule: + weekly_rewards_splitting::locked_token_buckets::WeeklyRewardsLockedTokenBucketsModule + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + energy_query::EnergyQueryModule + + crate::delete_energy::DeleteEnergyModule { #[payable("*")] #[endpoint(unstakeFarm)] @@ -74,8 +75,8 @@ pub trait UnstakeFarmModule: ) -> ExitFarmWithPartialPosResultType { let migrated_amount = self.migrate_old_farm_positions(&original_caller); - let exit_result = - self.exit_farm_base::>(original_caller.clone(), payment); + let exit_result = self + .exit_farm_base::>(original_caller.clone(), payment.clone()); self.decrease_old_farm_positions(migrated_amount, &original_caller); @@ -89,9 +90,13 @@ pub trait UnstakeFarmModule: self.send_payment_non_zero(&caller, &exit_result.reward_payment); - self.clear_user_energy_if_needed(&original_caller); self.set_farm_supply_for_current_week(&exit_result.storage_cache.farm_token_supply); + self.delete_user_energy_if_needed::>( + &ManagedVec::from_single_item(payment), + &ManagedVec::from_single_item(exit_result.context.farm_token.attributes.clone()), + ); + self.emit_exit_farm_event( &caller, exit_result.context,