diff --git a/common/common_structs/src/farm_types.rs b/common/common_structs/src/farm_types.rs index db4d515c9..d7d85e20b 100644 --- a/common/common_structs/src/farm_types.rs +++ b/common/common_structs/src/farm_types.rs @@ -51,8 +51,8 @@ impl FixedSupplyToken for FarmTokenAttributes { impl Mergeable for FarmTokenAttributes { #[inline] - fn can_merge_with(&self, other: &Self) -> bool { - self.original_owner == other.original_owner + fn can_merge_with(&self, _other: &Self) -> bool { + true } fn merge_with(&mut self, other: Self) { diff --git a/dex/farm-with-locked-rewards/src/lib.rs b/dex/farm-with-locked-rewards/src/lib.rs index e7fd871c4..cfe9838e5 100644 --- a/dex/farm-with-locked-rewards/src/lib.rs +++ b/dex/farm-with-locked-rewards/src/lib.rs @@ -8,6 +8,7 @@ multiversx_sc::derive_imports!(); use common_structs::FarmTokenAttributes; use contexts::storage_cache::StorageCache; use core::marker::PhantomData; +use fixed_supply_token::FixedSupplyToken; use farm::{ base_functions::{BaseFunctionsModule, ClaimRewardsResultType, DoubleMultiPayment, Wrapper}, @@ -198,7 +199,13 @@ pub trait Farm: self.migrate_old_farm_positions(&orig_caller); let boosted_rewards = self.claim_only_boosted_payment(&orig_caller); - let merged_farm_token = self.merge_farm_tokens::>(); + let mut output_attributes = self.merge_and_return_attributes::>(); + output_attributes.original_owner = orig_caller.clone(); + + let new_token_amount = output_attributes.get_total_supply(); + let merged_farm_token = self + .farm_token() + .nft_create(new_token_amount, &output_attributes); self.send_payment_non_zero(&caller, &merged_farm_token); let locked_rewards_payment = self.send_to_lock_contract_non_zero( diff --git a/dex/farm/src/base_functions.rs b/dex/farm/src/base_functions.rs index 76ce024c9..be97f7e80 100644 --- a/dex/farm/src/base_functions.rs +++ b/dex/farm/src/base_functions.rs @@ -11,7 +11,6 @@ use common_structs::FarmTokenAttributes; use contexts::storage_cache::StorageCache; use farm_base_impl::base_traits_impl::{DefaultFarmWrapper, FarmContract}; -use fixed_supply_token::FixedSupplyToken; use crate::{exit_penalty, MAX_PERCENT}; @@ -183,7 +182,7 @@ pub trait BaseFunctionsModule: } } - fn merge_farm_tokens>(&self) -> EsdtTokenPayment { + fn merge_and_return_attributes>(&self) -> FC::AttributesType { let payments = self.get_non_empty_payments(); let token_mapper = self.farm_token(); token_mapper.require_all_same_token(&payments); @@ -191,10 +190,7 @@ pub trait BaseFunctionsModule: let caller = self.blockchain().get_caller(); FC::check_and_update_user_farm_position(self, &caller, &payments); - let output_attributes: FC::AttributesType = - self.merge_from_payments_and_burn(payments, &token_mapper); - let new_token_amount = output_attributes.get_total_supply(); - token_mapper.nft_create(new_token_amount, &output_attributes) + self.merge_from_payments_and_burn(payments, &token_mapper) } fn claim_only_boosted_payment(&self, caller: &ManagedAddress) -> BigUint { diff --git a/dex/farm/src/lib.rs b/dex/farm/src/lib.rs index 0afc41c62..a200881c6 100644 --- a/dex/farm/src/lib.rs +++ b/dex/farm/src/lib.rs @@ -16,6 +16,7 @@ use exit_penalty::{ DEFAULT_BURN_GAS_LIMIT, DEFAULT_MINUMUM_FARMING_EPOCHS, DEFAULT_PENALTY_PERCENT, }; use farm_base_impl::base_traits_impl::FarmContract; +use fixed_supply_token::FixedSupplyToken; pub type EnterFarmResultType = DoubleMultiPayment; pub type ExitFarmWithPartialPosResultType = DoubleMultiPayment; @@ -195,7 +196,14 @@ pub trait Farm: let boosted_rewards_payment = EsdtTokenPayment::new(self.reward_token_id().get(), 0, boosted_rewards); - let merged_farm_token = self.merge_farm_tokens::>(); + let mut output_attributes = self.merge_and_return_attributes::>(); + output_attributes.original_owner = orig_caller; + + let new_token_amount = output_attributes.get_total_supply(); + let merged_farm_token = self + .farm_token() + .nft_create(new_token_amount, &output_attributes); + self.send_payment_non_zero(&caller, &merged_farm_token); self.send_payment_non_zero(&caller, &boosted_rewards_payment); 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 2e74ab42c..60a82cea2 100644 --- a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs +++ b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs @@ -33,6 +33,7 @@ pub static FARM_TOKEN_ID: &[u8] = b"FARM-123456"; pub const DIV_SAFETY: u64 = 1_000_000_000_000; pub const PER_BLOCK_REWARD_AMOUNT: u64 = 1_000; pub const FARMING_TOKEN_BALANCE: u64 = 200_000_000; +pub const MAX_PERCENTAGE: u64 = 10_000; // 100% pub const BOOSTED_YIELDS_PERCENTAGE: u64 = 2_500; // 25% pub const MAX_REWARDS_FACTOR: u64 = 10; pub const USER_REWARDS_ENERGY_CONST: u64 = 3; @@ -48,6 +49,11 @@ pub struct RawFarmTokenAttributes { pub original_owner_bytes: [u8; 32], } +pub struct NonceAmountPair { + pub nonce: u64, + pub amount: u64, +} + pub struct MultiUserFarmSetup where FarmObjBuilder: 'static + Copy + Fn() -> farm::ContractObj, @@ -310,28 +316,18 @@ where result } - pub fn merge_farm_tokens( - &mut self, - user: &Address, - first_token_nonce: u64, - first_token_amount: u64, - second_token_nonce: u64, - second_token_amount: u64, - ) { + pub fn merge_farm_tokens(&mut self, user: &Address, farm_tokens: Vec) { self.last_farm_token_nonce += 1; - let expected_farm_token_nonce = self.last_farm_token_nonce; - let expected_farm_token_amount = first_token_amount + second_token_amount; + let mut expected_farm_token_amount = 0; let mut payments = Vec::new(); - payments.push(TxTokenTransfer { - token_identifier: FARM_TOKEN_ID.to_vec(), - nonce: first_token_nonce, - value: rust_biguint!(first_token_amount), - }); - payments.push(TxTokenTransfer { - token_identifier: FARM_TOKEN_ID.to_vec(), - nonce: second_token_nonce, - value: rust_biguint!(second_token_amount), - }); + for farm_token in farm_tokens { + expected_farm_token_amount += farm_token.amount; + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: farm_token.nonce, + value: rust_biguint!(farm_token.amount), + }); + } self.b_mock .execute_esdt_multi_transfer(user, &self.farm_wrapper, &payments, |sc| { @@ -342,7 +338,7 @@ where out_farm_token.token_identifier, managed_token_id!(FARM_TOKEN_ID) ); - assert_eq!(out_farm_token.token_nonce, expected_farm_token_nonce); + assert_eq!(out_farm_token.token_nonce, self.last_farm_token_nonce); assert_eq!( out_farm_token.amount, managed_biguint!(expected_farm_token_amount) @@ -451,6 +447,54 @@ where result } + pub fn claim_rewards_with_multiple_payments( + &mut self, + user: &Address, + farm_token_payments: Vec, + ) -> u64 { + self.last_farm_token_nonce += 1; + + let mut expected_farm_token_amount = 0; + let mut payments = vec![]; + + for farm_token_payment in farm_token_payments { + expected_farm_token_amount += farm_token_payment.amount; + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: farm_token_payment.nonce, + value: rust_biguint!(farm_token_payment.amount), + }); + } + + let expected_farm_token_nonce = self.last_farm_token_nonce; + let mut result = 0; + self.b_mock + .execute_esdt_multi_transfer(user, &self.farm_wrapper, &payments, |sc| { + let (out_farm_token, out_reward_token) = + sc.claim_rewards_endpoint(OptionalValue::None).into_tuple(); + assert_eq!( + out_farm_token.token_identifier, + managed_token_id!(FARM_TOKEN_ID) + ); + assert_eq!(out_farm_token.token_nonce, expected_farm_token_nonce); + assert_eq!( + out_farm_token.amount, + managed_biguint!(expected_farm_token_amount) + ); + + assert_eq!( + out_reward_token.token_identifier, + managed_token_id!(REWARD_TOKEN_ID) + ); + assert_eq!(out_reward_token.token_nonce, 0); + + result = out_reward_token.amount.to_u64().unwrap(); + }) + .assert_ok(); + + result + } + pub fn claim_boosted_rewards_for_user(&mut self, owner: &Address, broker: &Address) -> u64 { self.last_farm_token_nonce += 1; @@ -691,4 +735,89 @@ where }) .assert_ok(); } + + // With the current checks, works only on full position sent (amount/nonce) + pub fn send_farm_position( + &mut self, + sender: &Address, + receiver: &Address, + nonce: u64, + amount: u64, + attr_reward_per_share: u64, + attr_entering_epoch: u64, + ) { + self.b_mock.check_nft_balance( + sender, + FARM_TOKEN_ID, + nonce, + &rust_biguint!(amount), + Some(&FarmTokenAttributes:: { + reward_per_share: managed_biguint!(attr_reward_per_share), + entering_epoch: attr_entering_epoch, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(amount), + original_owner: managed_address!(&sender), + }), + ); + + self.b_mock + .check_nft_balance::>( + receiver, + FARM_TOKEN_ID, + nonce, + &rust_biguint!(0), + None, + ); + + self.b_mock.set_nft_balance( + sender, + FARM_TOKEN_ID, + nonce, + &rust_biguint!(0), + &FarmTokenAttributes:: { + reward_per_share: managed_biguint!(attr_reward_per_share), + entering_epoch: attr_entering_epoch, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(amount), + original_owner: managed_address!(&sender), + }, + ); + + self.b_mock.set_nft_balance( + receiver, + FARM_TOKEN_ID, + nonce, + &rust_biguint!(amount), + &FarmTokenAttributes:: { + reward_per_share: managed_biguint!(attr_reward_per_share), + entering_epoch: attr_entering_epoch, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(amount), + original_owner: managed_address!(&sender), + }, + ); + + self.b_mock + .check_nft_balance::>( + sender, + FARM_TOKEN_ID, + nonce, + &rust_biguint!(0), + None, + ); + + self.b_mock.check_nft_balance( + receiver, + FARM_TOKEN_ID, + nonce, + &rust_biguint!(amount), + Some(&FarmTokenAttributes:: { + reward_per_share: managed_biguint!(attr_reward_per_share), + entering_epoch: attr_entering_epoch, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(amount), + original_owner: managed_address!(&sender), + }), + ); + } } diff --git a/dex/farm/tests/total_farm_position_test.rs b/dex/farm/tests/total_farm_position_test.rs index 26756ebaa..47518da0c 100644 --- a/dex/farm/tests/total_farm_position_test.rs +++ b/dex/farm/tests/total_farm_position_test.rs @@ -4,7 +4,10 @@ mod farm_setup; use common_structs::FarmTokenAttributes; use config::ConfigModule; -use farm_setup::multi_user_farm_setup::{MultiUserFarmSetup, BOOSTED_YIELDS_PERCENTAGE}; +use farm_setup::multi_user_farm_setup::{ + MultiUserFarmSetup, NonceAmountPair, BOOSTED_YIELDS_PERCENTAGE, MAX_PERCENTAGE, + PER_BLOCK_REWARD_AMOUNT, +}; use multiversx_sc_scenario::{managed_address, managed_biguint, rust_biguint, DebugApi}; use crate::farm_setup::multi_user_farm_setup::{FARM_TOKEN_ID, REWARD_TOKEN_ID}; @@ -480,6 +483,177 @@ fn farm_total_position_exit_migration_test() { ); } +#[test] +fn farm_total_position_on_claim_migration_test() { + DebugApi::dummy(); + let mut farm_setup = MultiUserFarmSetup::new( + farm::contract_obj, + energy_factory_mock::contract_obj, + energy_update::contract_obj, + ); + + farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + farm_setup.set_boosted_yields_factors(); + farm_setup.b_mock.set_block_epoch(2); + + // first user enter farm + let farm_in_amount = 50_000_000; + let first_user = farm_setup.first_user.clone(); + farm_setup.enter_farm(&first_user, farm_in_amount); + + // Remove current farm position from storage + farm_setup.set_user_total_farm_position(&first_user, 0); + farm_setup.check_user_total_farm_position(&first_user, 0); + + // User enters farm again + farm_setup.enter_farm(&first_user, farm_in_amount); + farm_setup.check_user_total_farm_position(&first_user, farm_in_amount); + + // Set farm position migration nonce + farm_setup + .b_mock + .execute_tx( + &farm_setup.owner, + &farm_setup.farm_wrapper, + &rust_biguint!(0), + |sc| { + sc.farm_position_migration_nonce().set(2); + }, + ) + .assert_ok(); + + farm_setup.check_farm_token_supply(farm_in_amount * 2); + + // claim rewards with both positions + let total_farm_tokens = farm_in_amount * 2; + let payments = vec![ + NonceAmountPair { + nonce: 1, + amount: farm_in_amount, + }, + NonceAmountPair { + nonce: 2, + amount: farm_in_amount, + }, + ]; + + let block_nonce = 10; + farm_setup.b_mock.set_block_nonce(block_nonce); + + farm_setup.check_user_total_farm_position(&first_user, farm_in_amount); + let _ = farm_setup.claim_rewards_with_multiple_payments(&first_user, payments); + farm_setup.check_user_total_farm_position(&first_user, total_farm_tokens); + + farm_setup + .b_mock + .check_nft_balance::>( + &first_user, + FARM_TOKEN_ID, + 3, + &rust_biguint!(total_farm_tokens), + None, + ); + + // User receives rewards only for the new position + let expected_user_rewards = block_nonce + * PER_BLOCK_REWARD_AMOUNT + * (MAX_PERCENTAGE - BOOSTED_YIELDS_PERCENTAGE) + * farm_in_amount + / total_farm_tokens + / MAX_PERCENTAGE; + farm_setup.b_mock.check_esdt_balance( + &first_user, + REWARD_TOKEN_ID, + &rust_biguint!(expected_user_rewards), + ); +} + +#[test] +fn farm_total_position_on_merge_migration_test() { + DebugApi::dummy(); + let mut farm_setup = MultiUserFarmSetup::new( + farm::contract_obj, + energy_factory_mock::contract_obj, + energy_update::contract_obj, + ); + + farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + farm_setup.set_boosted_yields_factors(); + farm_setup.b_mock.set_block_epoch(2); + + // user has 2 old farm position + let farm_in_amount = 25_000_000; + let first_user = farm_setup.first_user.clone(); + farm_setup.enter_farm(&first_user, farm_in_amount); + farm_setup.enter_farm(&first_user, farm_in_amount); + + // Remove current farm position from storage + farm_setup.set_user_total_farm_position(&first_user, 0); + farm_setup.check_user_total_farm_position(&first_user, 0); + + // User enters farm again, with 2 new positions + farm_setup.enter_farm(&first_user, farm_in_amount); + farm_setup.enter_farm(&first_user, farm_in_amount); + farm_setup.check_user_total_farm_position(&first_user, farm_in_amount * 2); + + // Set farm position migration nonce + farm_setup + .b_mock + .execute_tx( + &farm_setup.owner, + &farm_setup.farm_wrapper, + &rust_biguint!(0), + |sc| { + sc.farm_position_migration_nonce().set(3); + }, + ) + .assert_ok(); + + let total_farm_tokens = farm_in_amount * 4; + farm_setup.check_farm_token_supply(total_farm_tokens); + + // merge all 4 farm positions + let payments = vec![ + NonceAmountPair { + nonce: 1, + amount: farm_in_amount, + }, + NonceAmountPair { + nonce: 2, + amount: farm_in_amount, + }, + NonceAmountPair { + nonce: 3, + amount: farm_in_amount, + }, + NonceAmountPair { + nonce: 4, + amount: farm_in_amount, + }, + ]; + + let block_nonce = 10; + farm_setup.b_mock.set_block_nonce(block_nonce); + + farm_setup.check_user_total_farm_position(&first_user, farm_in_amount * 2); + farm_setup.merge_farm_tokens(&first_user, payments); + farm_setup.check_user_total_farm_position(&first_user, total_farm_tokens); + + farm_setup + .b_mock + .check_nft_balance::>( + &first_user, + FARM_TOKEN_ID, + 5, + &rust_biguint!(total_farm_tokens), + None, + ); + + farm_setup + .b_mock + .check_esdt_balance(&first_user, REWARD_TOKEN_ID, &rust_biguint!(0)); +} + #[test] fn no_boosted_rewards_penalty_for_no_energy_test() { DebugApi::dummy(); @@ -560,3 +734,205 @@ fn no_boosted_rewards_penalty_for_no_energy_test() { &rust_biguint!(first_receveived_reward_amt), ); } + +#[test] +fn total_farm_position_owner_change_test() { + DebugApi::dummy(); + let mut farm_setup = MultiUserFarmSetup::new( + farm::contract_obj, + energy_factory_mock::contract_obj, + energy_update::contract_obj, + ); + + farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + farm_setup.set_boosted_yields_factors(); + farm_setup.b_mock.set_block_epoch(2); + + // first user enters farm 6 times + let farm_token_amount = 10_000_000; + let first_user = farm_setup.first_user.clone(); + let second_user = farm_setup.second_user.clone(); + let third_user = farm_setup.third_user.clone(); + + farm_setup.set_user_energy(&first_user, 1_000, 2, 1); + farm_setup.enter_farm(&first_user, farm_token_amount); + farm_setup.enter_farm(&first_user, farm_token_amount); + farm_setup.enter_farm(&first_user, farm_token_amount); + farm_setup.enter_farm(&first_user, farm_token_amount); + farm_setup.enter_farm(&first_user, farm_token_amount); + farm_setup.enter_farm(&first_user, farm_token_amount); + + farm_setup.check_user_total_farm_position(&first_user, farm_token_amount * 6); + farm_setup.check_user_total_farm_position(&second_user, 0); + + assert_eq!(farm_setup.last_farm_token_nonce, 6); + + // First user transfers 5 position to second user + farm_setup.send_farm_position(&first_user, &second_user, 1, farm_token_amount, 0, 2); + farm_setup.send_farm_position(&first_user, &second_user, 2, farm_token_amount, 0, 2); + farm_setup.send_farm_position(&first_user, &second_user, 3, farm_token_amount, 0, 2); + farm_setup.send_farm_position(&first_user, &second_user, 4, farm_token_amount, 0, 2); + farm_setup.send_farm_position(&first_user, &second_user, 5, farm_token_amount, 0, 2); + + // Total farm position unchanged as users only transfered the farm positions + farm_setup.check_user_total_farm_position(&first_user, farm_token_amount * 6); + farm_setup.check_user_total_farm_position(&second_user, 0); + + // second user enter farm + farm_setup.set_user_energy(&second_user, 4_000, 2, 1); + farm_setup.enter_farm_with_additional_payment( + &second_user, + farm_token_amount, + 1, + farm_token_amount, + ); + + // 1 farm position was removed from first user and added to the second user (who entered the farm with a position of his own) + farm_setup.check_user_total_farm_position(&first_user, farm_token_amount * 5); + farm_setup.check_user_total_farm_position(&second_user, farm_token_amount * 2); + + // users claim rewards to get their energy registered + let _ = farm_setup.claim_rewards(&first_user, 6, farm_token_amount); + let _ = farm_setup.claim_rewards(&second_user, 7, farm_token_amount); + + // random tx on end of week 1, to cummulate rewards + farm_setup.b_mock.set_block_nonce(10); + farm_setup.b_mock.set_block_epoch(6); + farm_setup.set_user_energy(&first_user, 1_000, 6, 1); + farm_setup.set_user_energy(&second_user, 4_000, 6, 1); + farm_setup.set_user_energy(&third_user, 1, 6, 1); + farm_setup.enter_farm(&third_user, 1); + farm_setup.exit_farm(&third_user, 10, 1); + + // advance 1 week + farm_setup.b_mock.set_block_epoch(10); + farm_setup.set_user_energy(&first_user, 1_000, 10, 1); + farm_setup.set_user_energy(&second_user, 4_000, 10, 1); + + // Second user claims with a position from the first user + let base_rewards_amount = 1071; + let boosted_rewards_amount = 1485; + let mut second_user_reward_balance = base_rewards_amount + boosted_rewards_amount; + + let second_received_reward_amt = farm_setup.claim_rewards(&second_user, 2, farm_token_amount); + assert_eq!(second_received_reward_amt, second_user_reward_balance); + + farm_setup.b_mock.check_esdt_balance( + &second_user, + REWARD_TOKEN_ID, + &rust_biguint!(second_user_reward_balance), + ); + + farm_setup.b_mock.check_nft_balance( + &second_user, + FARM_TOKEN_ID, + 11, + &rust_biguint!(farm_token_amount), + Some(&FarmTokenAttributes:: { + reward_per_share: managed_biguint!(107142857), + entering_epoch: 2, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(farm_token_amount), + original_owner: managed_address!(&second_user), + }), + ); + + // Check users positions after claim + farm_setup.check_user_total_farm_position(&first_user, farm_token_amount * 4); + farm_setup.check_user_total_farm_position(&second_user, farm_token_amount * 3); + + // random tx on end of week 2, to cummulate rewards + farm_setup.b_mock.set_block_nonce(20); + farm_setup.b_mock.set_block_epoch(13); + farm_setup.set_user_energy(&first_user, 1_000, 13, 1); + farm_setup.set_user_energy(&second_user, 4_000, 13, 1); + farm_setup.set_user_energy(&third_user, 1, 13, 1); + farm_setup.enter_farm(&third_user, 1); + farm_setup.exit_farm(&third_user, 12, 1); + + // advance 1 week + farm_setup.b_mock.set_block_epoch(15); + farm_setup.set_user_energy(&first_user, 1_000, 15, 1); + farm_setup.set_user_energy(&second_user, 4_000, 15, 1); + + // Second user exits farm with a position previously owned by user 1 + second_user_reward_balance += 2142; // base rewards + second_user_reward_balance += 1630; // boosted rewards + farm_setup.exit_farm(&second_user, 3, farm_token_amount); + farm_setup + .b_mock + .check_esdt_balance(&second_user, REWARD_TOKEN_ID, &rust_biguint!(6328)); + + // Check users positions after exit + farm_setup.check_user_total_farm_position(&first_user, farm_token_amount * 3); + farm_setup.check_user_total_farm_position(&second_user, farm_token_amount * 3); + + // random tx on end of week 3, to cummulate rewards + farm_setup.b_mock.set_block_nonce(30); + farm_setup.b_mock.set_block_epoch(20); + farm_setup.set_user_energy(&first_user, 1_000, 20, 1); + farm_setup.set_user_energy(&second_user, 4_000, 20, 1); + farm_setup.set_user_energy(&third_user, 1, 20, 1); + farm_setup.enter_farm(&third_user, 1); + farm_setup.exit_farm(&third_user, 13, 1); + + // advance 1 week + farm_setup.b_mock.set_block_epoch(25); + farm_setup.set_user_energy(&first_user, 1_000, 25, 1); + farm_setup.set_user_energy(&second_user, 4_000, 25, 1); + + // First user claims rewards + let first_user_received_reward_amt = + farm_setup.claim_rewards(&first_user, 8, farm_token_amount); + assert_eq!(first_user_received_reward_amt, 5642); + + // Check users positions after first user claim + farm_setup.check_user_total_farm_position(&first_user, farm_token_amount * 3); + farm_setup.check_user_total_farm_position(&second_user, farm_token_amount * 3); + + // Second user merges his tokens with first user initial token + let farm_tokens = vec![ + NonceAmountPair { + nonce: 4, + amount: farm_token_amount, + }, + NonceAmountPair { + nonce: 5, + amount: farm_token_amount, + }, + NonceAmountPair { + nonce: 11, + amount: farm_token_amount, + }, + ]; + + farm_setup.b_mock.check_esdt_balance( + &second_user, + REWARD_TOKEN_ID, + &rust_biguint!(second_user_reward_balance), + ); + farm_setup.merge_farm_tokens(&second_user, farm_tokens); + second_user_reward_balance += 1703; // boosted rewards + farm_setup.b_mock.check_esdt_balance( + &second_user, + REWARD_TOKEN_ID, + &rust_biguint!(second_user_reward_balance), + ); + farm_setup.b_mock.check_nft_balance( + &second_user, + FARM_TOKEN_ID, + 15, + &rust_biguint!(farm_token_amount * 3), + Some(&FarmTokenAttributes:: { + reward_per_share: managed_biguint!(35714286), + entering_epoch: 2, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(farm_token_amount * 3), + original_owner: managed_address!(&second_user), + }), + ); + + // Check users positions after merge + farm_setup.check_user_total_farm_position(&first_user, farm_token_amount); + farm_setup.check_user_total_farm_position(&second_user, farm_token_amount * 5); +}