From 6ff91e73462d893df3f29a3ac74c8c31f2ce50f7 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Mon, 13 Jan 2025 02:44:17 +0200 Subject: [PATCH 1/5] audit fixes + unit tests --- .../modules/original_owner_helper/src/lib.rs | 19 +-- .../src/permissions_hub_module.rs | 5 +- .../src/external_interaction.rs | 43 +++--- dex/farm-with-locked-rewards/src/lib.rs | 15 --- dex/farm/src/external_interaction.rs | 11 +- dex/farm/tests/external_interaction_test.rs | 33 ++++- .../tests/farm_setup/multi_user_farm_setup.rs | 13 ++ .../src/proxy_actions/external_interaction.rs | 34 +++-- .../tests/staking_farm_with_lp.rs | 126 +++++++++++++++++- .../farm-staking/src/external_interaction.rs | 13 +- 10 files changed, 238 insertions(+), 74 deletions(-) diff --git a/common/modules/original_owner_helper/src/lib.rs b/common/modules/original_owner_helper/src/lib.rs index 3f5126929..ae85d073c 100644 --- a/common/modules/original_owner_helper/src/lib.rs +++ b/common/modules/original_owner_helper/src/lib.rs @@ -6,25 +6,14 @@ use common_structs::{FarmToken, PaymentsVec}; #[multiversx_sc::module] pub trait OriginalOwnerHelperModule { - fn check_and_return_original_owner + TopDecode>( + fn get_claim_original_owner + TopDecode>( &self, - payments: &PaymentsVec, farm_token_mapper: &NonFungibleTokenMapper, ) -> ManagedAddress { - let mut original_owner = ManagedAddress::zero(); - for payment in payments.iter() { - let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); - let payment_original_owner = attributes.get_original_owner(); + let payment = self.call_value().single_esdt(); - if original_owner.is_zero() { - original_owner = payment_original_owner; - } else { - require!( - original_owner == payment_original_owner, - "All position must have the same original owner" - ); - } - } + let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); + let original_owner = attributes.get_original_owner(); require!( !original_owner.is_zero(), diff --git a/common/modules/permissions_hub_module/src/permissions_hub_module.rs b/common/modules/permissions_hub_module/src/permissions_hub_module.rs index 3b7832d06..c09f63dad 100644 --- a/common/modules/permissions_hub_module/src/permissions_hub_module.rs +++ b/common/modules/permissions_hub_module/src/permissions_hub_module.rs @@ -12,7 +12,10 @@ pub trait PermissionsHubModule { .is_whitelisted(user, authorized_address) .execute_on_dest_context(); - require!(is_whitelisted, "Caller is not whitelisted by the user"); + require!( + is_whitelisted, + "Caller is not whitelisted by the user or is blacklisted" + ); } #[only_owner] diff --git a/dex/farm-with-locked-rewards/src/external_interaction.rs b/dex/farm-with-locked-rewards/src/external_interaction.rs index be29004f0..0cc362894 100644 --- a/dex/farm-with-locked-rewards/src/external_interaction.rs +++ b/dex/farm-with-locked-rewards/src/external_interaction.rs @@ -55,6 +55,8 @@ pub trait ExternalInteractionsModule: &farm_token_mapper, ); + self.migrate_old_farm_positions(&user); + let boosted_rewards = self.claim_only_boosted_payment(&user); let new_farm_token = self.enter_farm::>(user.clone()); self.send_payment_non_zero(&caller, &new_farm_token); @@ -79,32 +81,41 @@ pub trait ExternalInteractionsModule: #[payable("*")] #[endpoint(claimRewardsOnBehalf)] fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { - let payments = self.get_non_empty_payments(); let farm_token_mapper = self.farm_token(); let caller = self.blockchain().get_caller(); - let user = self.check_and_return_original_owner::>( - &payments, - &farm_token_mapper, - ); + let user = + self.get_claim_original_owner::>(&farm_token_mapper); self.require_user_whitelisted(&user, &caller); + self.migrate_old_farm_positions(&user); + let claim_rewards_result = self.claim_rewards::>(user.clone()); self.send_payment_non_zero(&caller, &claim_rewards_result.new_farm_token); let rewards_payment = claim_rewards_result.rewards; - let locked_rewards_payment = if rewards_payment.amount == 0 { - let locked_token_id = self.get_locked_token_id(); - EsdtTokenPayment::new(locked_token_id, 0, rewards_payment.amount) - } else { - self.lock_virtual( - rewards_payment.token_identifier, - rewards_payment.amount, - user.clone(), - user, - ) - }; + let locked_rewards_payment = self.send_to_lock_contract_non_zero( + rewards_payment.token_identifier, + rewards_payment.amount, + user.clone(), + user, + ); (claim_rewards_result.new_farm_token, locked_rewards_payment).into() } + + fn send_to_lock_contract_non_zero( + &self, + token_id: TokenIdentifier, + amount: BigUint, + destination_address: ManagedAddress, + energy_address: ManagedAddress, + ) -> EsdtTokenPayment { + if amount == 0 { + let locked_token_id = self.get_locked_token_id(); + return EsdtTokenPayment::new(locked_token_id, 0, amount); + } + + self.lock_virtual(token_id, amount, destination_address, energy_address) + } } diff --git a/dex/farm-with-locked-rewards/src/lib.rs b/dex/farm-with-locked-rewards/src/lib.rs index 69b5dc4a1..a8a3c22f8 100644 --- a/dex/farm-with-locked-rewards/src/lib.rs +++ b/dex/farm-with-locked-rewards/src/lib.rs @@ -301,21 +301,6 @@ pub trait Farm: &storage_cache, ) } - - fn send_to_lock_contract_non_zero( - &self, - token_id: TokenIdentifier, - amount: BigUint, - destination_address: ManagedAddress, - energy_address: ManagedAddress, - ) -> EsdtTokenPayment { - if amount == 0 { - let locked_token_id = self.get_locked_token_id(); - return EsdtTokenPayment::new(locked_token_id, 0, amount); - } - - self.lock_virtual(token_id, amount, destination_address, energy_address) - } } pub struct NoMintWrapper { diff --git a/dex/farm/src/external_interaction.rs b/dex/farm/src/external_interaction.rs index 0af8fe5a5..7d597a691 100644 --- a/dex/farm/src/external_interaction.rs +++ b/dex/farm/src/external_interaction.rs @@ -45,6 +45,8 @@ pub trait ExternalInteractionsModule: let caller = self.blockchain().get_caller(); self.require_user_whitelisted(&user, &caller); + self.migrate_old_farm_positions(&user); + let payments = self.get_non_empty_payments(); let farm_token_mapper = self.farm_token(); self.check_additional_payments_original_owner::>( @@ -70,16 +72,15 @@ pub trait ExternalInteractionsModule: #[payable("*")] #[endpoint(claimRewardsOnBehalf)] fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { - let payments = self.get_non_empty_payments(); let farm_token_mapper = self.farm_token(); let caller = self.blockchain().get_caller(); - let user = self.check_and_return_original_owner::>( - &payments, - &farm_token_mapper, - ); + let user = + self.get_claim_original_owner::>(&farm_token_mapper); self.require_user_whitelisted(&user, &caller); + self.migrate_old_farm_positions(&user); + let claim_rewards_result = self.claim_rewards::>(user.clone()); self.send_payment_non_zero(&caller, &claim_rewards_result.new_farm_token); diff --git a/dex/farm/tests/external_interaction_test.rs b/dex/farm/tests/external_interaction_test.rs index 6a36ae680..b7ec8a6b1 100644 --- a/dex/farm/tests/external_interaction_test.rs +++ b/dex/farm/tests/external_interaction_test.rs @@ -194,7 +194,7 @@ fn test_enter_and_claim_farm_on_behalf_not_whitelisted_error() { sc.enter_farm_on_behalf(managed_address!(&external_user)); }, ) - .assert_error(4, "Caller is not whitelisted by the user"); + .assert_error(4, "Caller is not whitelisted by the user or is blacklisted"); let farm_token_amount = 100_000_000; farm_setup.whitelist_address_on_behalf(&external_user, &authorized_address); @@ -214,7 +214,7 @@ fn test_enter_and_claim_farm_on_behalf_not_whitelisted_error() { sc.claim_rewards_on_behalf(); }, ) - .assert_error(4, "Caller is not whitelisted by the user"); + .assert_error(4, "Caller is not whitelisted by the user or is blacklisted"); } #[test] @@ -283,7 +283,7 @@ fn test_wrong_original_owner_on_behalf_validation() { ) .assert_error(4, "Provided address is not the same as the original owner"); - // Try claim with different original owners + // Try claim with multiple positions let mut claim_payments = Vec::new(); claim_payments.push(TxTokenTransfer { token_identifier: FARM_TOKEN_ID.to_vec(), @@ -305,5 +305,30 @@ fn test_wrong_original_owner_on_behalf_validation() { sc.claim_rewards_on_behalf(); }, ) - .assert_error(4, "All position must have the same original owner"); + .assert_error(4, "incorrect number of ESDT transfers"); + + // Check enter on behalf with blacklisted address + let blacklisted_address = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); + farm_setup.whitelist_address_on_behalf(&external_user1, &blacklisted_address); + farm_setup.blacklist_address_on_behalf(&blacklisted_address); + + farm_setup.b_mock.set_esdt_balance( + &blacklisted_address, + FARMING_TOKEN_ID, + &rust_biguint!(farm_token_amount), + ); + + farm_setup + .b_mock + .execute_esdt_transfer( + &blacklisted_address, + &farm_setup.farm_wrapper, + &FARMING_TOKEN_ID, + 0, + &rust_biguint!(farm_token_amount), + |sc| { + sc.enter_farm_on_behalf(managed_address!(&external_user1)); + }, + ) + .assert_error(4, "Caller is not whitelisted by the user or is blacklisted"); } diff --git a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs index 3398fd679..fdcb073dc 100644 --- a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs +++ b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs @@ -685,6 +685,19 @@ where .assert_ok(); } + pub fn blacklist_address_on_behalf(&mut self, address_to_blacklist: &Address) { + self.b_mock + .execute_tx( + &self.owner, + &self.permissions_hub_wrapper, + &rust_biguint!(0), + |sc| { + sc.blacklist(managed_address!(address_to_blacklist)); + }, + ) + .assert_ok(); + } + pub fn enter_farm_on_behalf( &mut self, caller: &Address, diff --git a/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs index 722bc2eb8..30abcbbc4 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs @@ -1,6 +1,7 @@ multiversx_sc::imports!(); use common_structs::FarmTokenAttributes; +use farm_staking::token_attributes::StakingFarmTokenAttributes; use crate::{ dual_yield_token::DualYieldTokenAttributes, @@ -45,7 +46,7 @@ pub trait ProxyExternalInteractionsModule: let payment = self.call_value().single_esdt(); let caller = self.blockchain().get_caller(); - let original_owner = self.get_underlying_farm_position_original_owner(&payment); + let original_owner = self.get_underlying_positions_original_owner(&payment); self.require_user_whitelisted(&original_owner, &caller); let claim_result = self.claim_dual_yield_common(original_owner.clone(), payment); @@ -85,35 +86,50 @@ pub trait ProxyExternalInteractionsModule: for payment in additional_payments.into_iter() { require!( - &self.get_underlying_farm_position_original_owner(&payment) == original_owner, + &self.get_underlying_positions_original_owner(&payment) == original_owner, "Provided address is not the same as the original owner" ); } } - fn get_underlying_farm_position_original_owner( + fn get_underlying_positions_original_owner( &self, payment: &EsdtTokenPayment, ) -> ManagedAddress { let dual_yield_token_mapper = self.dual_yield_token(); dual_yield_token_mapper.require_same_token(&payment.token_identifier); - let attributes: DualYieldTokenAttributes = + let dual_yield_attributes: DualYieldTokenAttributes = self.get_attributes_as_part_of_fixed_supply(payment, &dual_yield_token_mapper); let lp_farm_token_id = self.lp_farm_token_id().get(); - let attributes = self + let lp_attributes = self .blockchain() .get_token_attributes::>( &lp_farm_token_id, - attributes.lp_farm_token_nonce, + dual_yield_attributes.lp_farm_token_nonce, + ); + + let lp_original_owner = lp_attributes.original_owner; + require!( + lp_original_owner != ManagedAddress::zero(), + "LP Token original owner incorrect" + ); + + let staking_farm_token_id = self.staking_farm_token_id().get(); + let staking_attributes = self + .blockchain() + .get_token_attributes::>( + &staking_farm_token_id, + dual_yield_attributes.staking_farm_token_nonce, ); + let staking_original_owner = staking_attributes.original_owner; require!( - attributes.original_owner != ManagedAddress::zero(), - "Invalid original owner" + lp_original_owner == staking_original_owner, + "Underlying positions original owners do not match" ); - attributes.original_owner + lp_original_owner } } diff --git a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp.rs b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp.rs index 9a790bb19..d035fcad5 100644 --- a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp.rs +++ b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp.rs @@ -7,9 +7,13 @@ pub mod staking_farm_with_lp_staking_contract_setup; multiversx_sc::imports!(); +use common_structs::FarmTokenAttributes; use config::ConfigModule; use constants::*; -use farm_staking_proxy::dual_yield_token::DualYieldTokenAttributes; +use farm_staking_proxy::{ + dual_yield_token::DualYieldTokenAttributes, + proxy_actions::external_interaction::ProxyExternalInteractionsModule, +}; use farm_staking_proxy::proxy_actions::unstake::ProxyUnstakeModule; @@ -1399,3 +1403,123 @@ fn test_multiple_positions_on_behalf() { Some(&dual_yield_token_attributes), ); } + +#[test] +fn test_on_behalf_original_owner_validation() { + DebugApi::dummy(); + + let mut setup = FarmStakingSetup::new( + pair::contract_obj, + farm_with_locked_rewards::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + farm_staking::contract_obj, + farm_staking_proxy::contract_obj, + ); + + // Boosted rewards setup + setup + .b_mock + .execute_tx( + &setup.owner_addr, + &setup.staking_farm_wrapper, + &rust_biguint!(0), + |sc| { + sc.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + }, + ) + .assert_ok(); + + setup.set_lp_farm_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + let farm_amount = 100_000_000u64; + let user_address = setup.user_addr.clone(); + let authorized_address = setup.b_mock.create_user_account(&rust_biguint!(0)); + + setup.exit_lp_farm(&user_address, 1, USER_TOTAL_LP_TOKENS); + setup + .b_mock + .set_esdt_balance(&setup.user_addr, LP_TOKEN_ID, &rust_biguint!(farm_amount)); + + let block_nonce = 2u64; + setup.b_mock.set_block_epoch(2u64); + + setup.set_user_energy(&user_address, 1_000, 2, 1); + setup + .b_mock + .set_esdt_balance(&user_address, LP_TOKEN_ID, &rust_biguint!(farm_amount * 2)); + setup + .b_mock + .set_esdt_balance(&user_address, STAKING_REWARD_TOKEN_ID, &rust_biguint!(0)); + let farm_token_nonce = setup.enter_lp_farm(&user_address, farm_amount * 2); + + setup.check_user_total_staking_farm_position(&user_address, 0); + + // authorize address + setup.whitelist_address_on_behalf(&user_address, &authorized_address); + + setup.send_farm_position( + &user_address, + &authorized_address, + farm_token_nonce, + farm_amount * 2, + 0, + block_nonce, + ); + + setup.b_mock.check_esdt_balance( + &authorized_address, + STAKING_REWARD_TOKEN_ID, + &rust_biguint!(0), // should always be 0 + ); + + setup.stake_farm_on_behalf( + &authorized_address, + &user_address, + farm_token_nonce, + farm_amount, + 0, + 0, + 1, + farm_amount, + ); + + // Update farm original owner to test validation + // How we get to this point is out of scope for this unit test + let temp_user = setup.b_mock.create_user_account(&rust_biguint!(0)); + + let dual_yield_token_attributes: DualYieldTokenAttributes = setup + .b_mock + .get_nft_attributes(setup.proxy_wrapper.address_ref(), DUAL_YIELD_TOKEN_ID, 1) + .unwrap(); + let mut lp_farm_token_attributes: FarmTokenAttributes = setup + .b_mock + .get_nft_attributes( + setup.proxy_wrapper.address_ref(), + LP_FARM_TOKEN_ID, + dual_yield_token_attributes.lp_farm_token_nonce, + ) + .unwrap(); + lp_farm_token_attributes.original_owner = managed_address!(&temp_user); + + setup.b_mock.set_nft_balance( + setup.proxy_wrapper.address_ref(), + LP_FARM_TOKEN_ID, + dual_yield_token_attributes.lp_farm_token_nonce, + &rust_biguint!(farm_amount), + &lp_farm_token_attributes, + ); + + setup + .b_mock + .execute_esdt_transfer( + &authorized_address, + &setup.proxy_wrapper, + DUAL_YIELD_TOKEN_ID, + 1, + &rust_biguint!(1), + |sc| { + sc.claim_dual_yield_on_behalf(); + }, + ) + .assert_error(4, "Underlying positions original owners do not match"); +} diff --git a/farm-staking/farm-staking/src/external_interaction.rs b/farm-staking/farm-staking/src/external_interaction.rs index 1ece48d9a..0dcd17d60 100644 --- a/farm-staking/farm-staking/src/external_interaction.rs +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -75,7 +75,7 @@ pub trait ExternalInteractionsModule: self.update_energy_and_progress(&user); self.emit_enter_farm_event( - &caller, + &user, enter_result.context.farming_token_payment, enter_result.new_farm_token, enter_result.created_with_merge, @@ -88,15 +88,13 @@ pub trait ExternalInteractionsModule: #[payable("*")] #[endpoint(claimRewardsOnBehalf)] fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { - let payments = self.get_non_empty_payments(); let farm_token_mapper = self.farm_token(); let caller = self.blockchain().get_caller(); - let user = self.check_and_return_original_owner::>( - &payments, - &farm_token_mapper, - ); + let user = self + .get_claim_original_owner::>(&farm_token_mapper); self.require_user_whitelisted(&user, &caller); + let payments = self.get_non_empty_payments(); let claim_result = self.claim_rewards_base_no_farm_token_mint::>( user.clone(), payments, @@ -115,12 +113,11 @@ pub trait ExternalInteractionsModule: ); virtual_farm_token.payment.token_nonce = new_farm_token_nonce; - let caller = self.blockchain().get_caller(); self.send_payment_non_zero(&caller, &virtual_farm_token.payment); self.send_payment_non_zero(&user, &claim_result.rewards); self.emit_claim_rewards_event( - &caller, + &user, claim_result.context, virtual_farm_token.clone(), claim_result.rewards.clone(), From c755d381c476594048c5dd092bb9383276ec94d7 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Mon, 13 Jan 2025 02:48:07 +0200 Subject: [PATCH 2/5] clippy fix --- dex/farm/tests/external_interaction_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dex/farm/tests/external_interaction_test.rs b/dex/farm/tests/external_interaction_test.rs index b7ec8a6b1..6bf3b3eed 100644 --- a/dex/farm/tests/external_interaction_test.rs +++ b/dex/farm/tests/external_interaction_test.rs @@ -323,7 +323,7 @@ fn test_wrong_original_owner_on_behalf_validation() { .execute_esdt_transfer( &blacklisted_address, &farm_setup.farm_wrapper, - &FARMING_TOKEN_ID, + FARMING_TOKEN_ID, 0, &rust_biguint!(farm_token_amount), |sc| { From ca2aef1fc778250674835a48934ea22d51738a0f Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Mon, 13 Jan 2025 16:51:33 +0200 Subject: [PATCH 3/5] allow multiple payments for claim onBehalf --- .../modules/original_owner_helper/src/lib.rs | 24 +++- dex/farm/tests/external_interaction_test.rs | 123 +++++++++++++++++- 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/common/modules/original_owner_helper/src/lib.rs b/common/modules/original_owner_helper/src/lib.rs index ae85d073c..4fbd5b973 100644 --- a/common/modules/original_owner_helper/src/lib.rs +++ b/common/modules/original_owner_helper/src/lib.rs @@ -10,10 +10,28 @@ pub trait OriginalOwnerHelperModule { &self, farm_token_mapper: &NonFungibleTokenMapper, ) -> ManagedAddress { - let payment = self.call_value().single_esdt(); + let payments = self.call_value().all_esdt_transfers(); + let farm_token_id = farm_token_mapper.get_token_id(); + + let mut original_owner = ManagedAddress::zero(); + for payment in payments.into_iter() { + require!( + payment.token_identifier == farm_token_id, + "Invalid payment token" + ); - let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); - let original_owner = attributes.get_original_owner(); + let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); + let payment_original_owner = attributes.get_original_owner(); + + if original_owner.is_zero() { + original_owner = payment_original_owner; + } else { + require!( + original_owner == payment_original_owner, + "Original owner is not the same for all payments" + ); + } + } require!( !original_owner.is_zero(), diff --git a/dex/farm/tests/external_interaction_test.rs b/dex/farm/tests/external_interaction_test.rs index 6bf3b3eed..1a62f71b3 100644 --- a/dex/farm/tests/external_interaction_test.rs +++ b/dex/farm/tests/external_interaction_test.rs @@ -283,7 +283,7 @@ fn test_wrong_original_owner_on_behalf_validation() { ) .assert_error(4, "Provided address is not the same as the original owner"); - // Try claim with multiple positions + // Try claim with different original owners let mut claim_payments = Vec::new(); claim_payments.push(TxTokenTransfer { token_identifier: FARM_TOKEN_ID.to_vec(), @@ -305,7 +305,7 @@ fn test_wrong_original_owner_on_behalf_validation() { sc.claim_rewards_on_behalf(); }, ) - .assert_error(4, "incorrect number of ESDT transfers"); + .assert_error(4, "Original owner is not the same for all payments"); // Check enter on behalf with blacklisted address let blacklisted_address = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); @@ -332,3 +332,122 @@ fn test_wrong_original_owner_on_behalf_validation() { ) .assert_error(4, "Caller is not whitelisted by the user or is blacklisted"); } + +#[test] +fn test_multiple_position_claim_on_behalf_average_rps_computation() { + DebugApi::dummy(); + + let mut farm_setup = MultiUserFarmSetup::new( + farm::contract_obj, + energy_factory_mock::contract_obj, + energy_update::contract_obj, + permissions_hub::contract_obj, + ); + + farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + farm_setup.set_boosted_yields_factors(); + let mut block_nonce = 0u64; + farm_setup.b_mock.set_block_nonce(block_nonce); + + // new external user + let external_user = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); + farm_setup.set_user_energy(&external_user, 1_000, 1, 1); + + // authorized address + let farm_token_amount = 100_000_000; + let authorized_address = farm_setup.first_user.clone(); + + farm_setup.whitelist_address_on_behalf(&external_user, &authorized_address); + + farm_setup.check_farm_token_supply(0); + farm_setup.enter_farm_on_behalf( + &authorized_address, + &external_user, + farm_token_amount / 2, + 0, + 0, + ); + farm_setup.check_farm_token_supply(farm_token_amount / 2); + + farm_setup.enter_farm_on_behalf( + &authorized_address, + &external_user, + farm_token_amount / 2, + 0, + 0, + ); + farm_setup.check_farm_token_supply(farm_token_amount); + + let block_nonce_diff = 10u64; + block_nonce += block_nonce_diff; + farm_setup.b_mock.set_block_nonce(block_nonce); + + // 1000 rewards per block + let total_rewards = PER_BLOCK_REWARD_AMOUNT * block_nonce_diff; + let base_rewards = + total_rewards * (MAX_PERCENTAGE - BOOSTED_YIELDS_PERCENTAGE) / MAX_PERCENTAGE; + + // Claim first base rewards + farm_setup + .b_mock + .check_esdt_balance(&external_user, REWARD_TOKEN_ID, &rust_biguint!(0)); + + let mut payments = Vec::new(); + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: 1, + value: rust_biguint!(farm_token_amount / 2), + }); + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: 2, + value: rust_biguint!(farm_token_amount / 2), + }); + + // First claim, without any position merged + // Should give rewards for the first payment only and compute average RPS from both positions + farm_setup + .b_mock + .execute_esdt_multi_transfer( + &authorized_address, + &farm_setup.farm_wrapper, + &payments, + |sc| { + sc.claim_rewards_on_behalf(); + }, + ) + .assert_ok(); + + farm_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(base_rewards / 2), + ); + + payments = Vec::new(); + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: 3, + value: rust_biguint!(farm_token_amount), + }); + + // Second claim, with both position merged, in the same block + // Should give the rest of the rewards (for the same positions) + farm_setup + .b_mock + .execute_esdt_multi_transfer( + &authorized_address, + &farm_setup.farm_wrapper, + &payments, + |sc| { + sc.claim_rewards_on_behalf(); + }, + ) + .assert_ok(); + + farm_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(base_rewards), + ); +} From 2f788d13f99658c0cd186ff041f0d4de28563197 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Tue, 14 Jan 2025 10:40:36 +0200 Subject: [PATCH 4/5] small code changes --- .../modules/original_owner_helper/src/lib.rs | 21 ++++++++++--------- .../src/proxy_actions/external_interaction.rs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/common/modules/original_owner_helper/src/lib.rs b/common/modules/original_owner_helper/src/lib.rs index 4fbd5b973..39e59ca59 100644 --- a/common/modules/original_owner_helper/src/lib.rs +++ b/common/modules/original_owner_helper/src/lib.rs @@ -13,7 +13,7 @@ pub trait OriginalOwnerHelperModule { let payments = self.call_value().all_esdt_transfers(); let farm_token_id = farm_token_mapper.get_token_id(); - let mut original_owner = ManagedAddress::zero(); + let mut opt_original_owner = None; for payment in payments.into_iter() { require!( payment.token_identifier == farm_token_id, @@ -23,22 +23,23 @@ pub trait OriginalOwnerHelperModule { let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); let payment_original_owner = attributes.get_original_owner(); - if original_owner.is_zero() { - original_owner = payment_original_owner; - } else { - require!( - original_owner == payment_original_owner, - "Original owner is not the same for all payments" - ); + match opt_original_owner { + Some(ref original_owner) => { + require!( + *original_owner == payment_original_owner, + "Original owner is not the same for all payments" + ); + } + None => opt_original_owner = Some(payment_original_owner), } } require!( - !original_owner.is_zero(), + opt_original_owner.is_some(), "Original owner could not be identified" ); - original_owner + unsafe { opt_original_owner.unwrap_unchecked() } } fn check_additional_payments_original_owner + TopDecode>( diff --git a/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs index 30abcbbc4..e97ba529b 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs @@ -112,7 +112,7 @@ pub trait ProxyExternalInteractionsModule: let lp_original_owner = lp_attributes.original_owner; require!( - lp_original_owner != ManagedAddress::zero(), + !lp_original_owner.is_zero(), "LP Token original owner incorrect" ); From 045e4ac9044d200443b74667edebb401b18d8a69 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Thu, 16 Jan 2025 12:15:04 +0200 Subject: [PATCH 5/5] add migration code to farm staking onBehalf --- farm-staking/farm-staking/src/external_interaction.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/farm-staking/farm-staking/src/external_interaction.rs b/farm-staking/farm-staking/src/external_interaction.rs index 0dcd17d60..4e748d108 100644 --- a/farm-staking/farm-staking/src/external_interaction.rs +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -60,6 +60,8 @@ pub trait ExternalInteractionsModule: &farm_token_mapper, ); + self.migrate_old_farm_positions(&user); + let boosted_rewards = self.claim_only_boosted_payment(&user); let boosted_rewards_payment = EsdtTokenPayment::new(self.reward_token_id().get(), 0, boosted_rewards); @@ -94,6 +96,8 @@ pub trait ExternalInteractionsModule: .get_claim_original_owner::>(&farm_token_mapper); self.require_user_whitelisted(&user, &caller); + self.migrate_old_farm_positions(&user); + let payments = self.get_non_empty_payments(); let claim_result = self.claim_rewards_base_no_farm_token_mint::>( user.clone(),