From 3ae33363a56f57459b58a17fe3ea9b5d6505d0c6 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Sun, 27 Oct 2024 16:34:30 +0200 Subject: [PATCH 01/11] add on behalf features for farm sc --- Cargo.lock | 18 ++ Cargo.toml | 2 + dex/farm/Cargo.toml | 3 + dex/farm/src/external_interaction.rs | 150 +++++++++ dex/farm/src/lib.rs | 2 + dex/farm/tests/external_interaction_test.rs | 288 ++++++++++++++++++ dex/farm/tests/farm_multi_user_test.rs | 8 + .../tests/farm_setup/multi_user_farm_setup.rs | 149 ++++++++- dex/farm/wasm/Cargo.lock | 8 + dex/farm/wasm/src/lib.rs | 7 +- dex/permissions-hub/Cargo.toml | 18 ++ dex/permissions-hub/meta/Cargo.toml | 12 + dex/permissions-hub/meta/src/main.rs | 3 + dex/permissions-hub/multiversx.json | 3 + dex/permissions-hub/src/lib.rs | 57 ++++ dex/permissions-hub/wasm/Cargo.lock | 188 ++++++++++++ dex/permissions-hub/wasm/Cargo.toml | 34 +++ dex/permissions-hub/wasm/src/lib.rs | 26 ++ 18 files changed, 970 insertions(+), 6 deletions(-) create mode 100644 dex/farm/src/external_interaction.rs create mode 100644 dex/farm/tests/external_interaction_test.rs create mode 100644 dex/permissions-hub/Cargo.toml create mode 100644 dex/permissions-hub/meta/Cargo.toml create mode 100644 dex/permissions-hub/meta/src/main.rs create mode 100644 dex/permissions-hub/multiversx.json create mode 100644 dex/permissions-hub/src/lib.rs create mode 100644 dex/permissions-hub/wasm/Cargo.lock create mode 100644 dex/permissions-hub/wasm/Cargo.toml create mode 100644 dex/permissions-hub/wasm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 724f6ca9d..4f8b88d23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,6 +532,7 @@ dependencies = [ "num-bigint", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -1443,6 +1444,23 @@ dependencies = [ "pause-all", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-scenario", + "num-bigint", +] + +[[package]] +name = "permissions-hub-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta-lib", + "permissions-hub", +] + [[package]] name = "permissions_module" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 81e48c99e..b36732c3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ members = [ "dex/proxy-deployer/meta", "dex/pair-mock", "dex/pair-mock/meta", + "dex/permissions-hub", + "dex/permissions-hub/meta", "energy-integration/energy-factory-mock", "energy-integration/energy-factory-mock/meta", diff --git a/dex/farm/Cargo.toml b/dex/farm/Cargo.toml index f883c4579..1c60a80ce 100644 --- a/dex/farm/Cargo.toml +++ b/dex/farm/Cargo.toml @@ -68,6 +68,9 @@ path = "../../energy-integration/common-modules/weekly-rewards-splitting" [dependencies.energy-query] path = "../../energy-integration/common-modules/energy-query" +[dependencies.permissions-hub] +path = "../permissions-hub" + [dependencies.multiversx-sc] version = "=0.53.2" features = ["esdt-token-payment-legacy-decode"] diff --git a/dex/farm/src/external_interaction.rs b/dex/farm/src/external_interaction.rs new file mode 100644 index 000000000..12a0b8366 --- /dev/null +++ b/dex/farm/src/external_interaction.rs @@ -0,0 +1,150 @@ +multiversx_sc::imports!(); + +use common_structs::FarmTokenAttributes; + +use crate::{ + base_functions::{self, ClaimRewardsResultType, Wrapper}, + exit_penalty, EnterFarmResultType, +}; + +#[multiversx_sc::module] +pub trait ExternalInteractionsModule: + rewards::RewardsModule + + config::ConfigModule + + token_send::TokenSendModule + + farm_token::FarmTokenModule + + pausable::PausableModule + + permissions_module::PermissionsModule + + sc_whitelist_module::SCWhitelistModule + + events::EventsModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + base_functions::BaseFunctionsModule + + exit_penalty::ExitPenaltyModule + + farm_base_impl::base_farm_init::BaseFarmInitModule + + farm_base_impl::base_farm_validation::BaseFarmValidationModule + + farm_base_impl::enter_farm::BaseEnterFarmModule + + farm_base_impl::claim_rewards::BaseClaimRewardsModule + + farm_base_impl::compound_rewards::BaseCompoundRewardsModule + + farm_base_impl::exit_farm::BaseExitFarmModule + + 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 + + utils::UtilsModule +{ + #[payable("*")] + #[endpoint(enterFarmOnBehalf)] + fn enter_farm_on_behalf(&self, user: ManagedAddress) -> EnterFarmResultType { + let caller = self.blockchain().get_caller(); + self.require_user_whitelisted(&user, &caller); + + self.check_additional_payments_original_owner(&user); + + let boosted_rewards = self.claim_only_boosted_payment(&user); + + let boosted_rewards_payment = + EsdtTokenPayment::new(self.reward_token_id().get(), 0, boosted_rewards); + + let new_farm_token = self.enter_farm::>(user.clone()); + self.send_payment_non_zero(&caller, &new_farm_token); + self.send_payment_non_zero(&user, &boosted_rewards_payment); + + self.update_energy_and_progress(&user); + + (new_farm_token, boosted_rewards_payment).into() + } + + #[payable("*")] + #[endpoint(claimRewardsOnBehalf)] + fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { + let user = self.check_and_return_original_owner(); + let caller = self.blockchain().get_caller(); + self.require_user_whitelisted(&user, &caller); + + let claim_rewards_result = self.claim_rewards::>(user.clone()); + + self.send_payment_non_zero(&caller, &claim_rewards_result.new_farm_token); + self.send_payment_non_zero(&user, &claim_rewards_result.rewards); + + claim_rewards_result.into() + } + + fn check_and_return_original_owner(&self) -> ManagedAddress { + let payments = self.call_value().all_esdt_transfers().clone_value(); + let farm_token_mapper = self.farm_token(); + let mut original_owner = ManagedAddress::zero(); + for payment in payments.into_iter() { + let attributes: FarmTokenAttributes = + farm_token_mapper.get_token_attributes(payment.token_nonce); + + if original_owner.is_zero() { + original_owner = attributes.original_owner; + } else { + require!( + original_owner == attributes.original_owner, + "All position must have the same original owner" + ); + } + } + + require!( + !original_owner.is_zero(), + "Original owner could not be identified" + ); + + original_owner + } + + fn check_additional_payments_original_owner(&self, user: &ManagedAddress) { + let payments = self.call_value().all_esdt_transfers().clone_value(); + if payments.len() == 1 { + return; + } + + let farm_token_mapper = self.farm_token(); + let farm_token_id = farm_token_mapper.get_token_id(); + for payment in payments.into_iter() { + if payment.token_identifier != farm_token_id { + continue; + } + + let attributes: FarmTokenAttributes = + farm_token_mapper.get_token_attributes(payment.token_nonce); + + require!( + user == &attributes.original_owner, + "Provided address is not the same as the original owner" + ); + } + } + + fn require_user_whitelisted(&self, user: &ManagedAddress, authorized_address: &ManagedAddress) { + let permissions_hub_address = self.permissions_hub_address().get(); + let is_whitelisted: bool = self + .permissions_hub_proxy(permissions_hub_address) + .is_whitelisted(user, authorized_address) + .execute_on_dest_context(); + + require!(is_whitelisted, "Caller is not whitelisted by the user"); + } + + #[only_owner] + #[endpoint(setPermissionsHubAddress)] + fn set_permissions_hub_address(&self, address: ManagedAddress) { + self.permissions_hub_address().set(&address); + } + + #[proxy] + fn permissions_hub_proxy( + &self, + sc_address: ManagedAddress, + ) -> permissions_hub::Proxy; + + #[storage_mapper("permissionsHubAddress")] + fn permissions_hub_address(&self) -> SingleValueMapper; +} diff --git a/dex/farm/src/lib.rs b/dex/farm/src/lib.rs index 2ca53489d..50883f52f 100644 --- a/dex/farm/src/lib.rs +++ b/dex/farm/src/lib.rs @@ -5,6 +5,7 @@ multiversx_sc::derive_imports!(); pub mod base_functions; pub mod exit_penalty; +pub mod external_interaction; use base_functions::{ClaimRewardsResultType, DoubleMultiPayment, Wrapper}; use common_structs::FarmTokenAttributes; @@ -34,6 +35,7 @@ pub trait Farm: + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + base_functions::BaseFunctionsModule + exit_penalty::ExitPenaltyModule + + external_interaction::ExternalInteractionsModule + farm_base_impl::base_farm_init::BaseFarmInitModule + farm_base_impl::base_farm_validation::BaseFarmValidationModule + farm_base_impl::enter_farm::BaseEnterFarmModule diff --git a/dex/farm/tests/external_interaction_test.rs b/dex/farm/tests/external_interaction_test.rs new file mode 100644 index 000000000..990d2d926 --- /dev/null +++ b/dex/farm/tests/external_interaction_test.rs @@ -0,0 +1,288 @@ +#![allow(deprecated)] + +mod farm_setup; + +use farm::external_interaction::ExternalInteractionsModule; +use farm_setup::multi_user_farm_setup::{ + MultiUserFarmSetup, BOOSTED_YIELDS_PERCENTAGE, FARMING_TOKEN_ID, FARM_TOKEN_ID, MAX_PERCENTAGE, + PER_BLOCK_REWARD_AMOUNT, REWARD_TOKEN_ID, +}; +use multiversx_sc_scenario::{imports::TxTokenTransfer, managed_address, rust_biguint}; + +#[test] +fn test_enter_and_claim_farm_on_behalf() { + 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(); + + // new external user + let external_user = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); + + // authorized address + let farm_token_amount = 100_000_000; + let farm_token_nonce = 1u64; + 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, 0, 0); + farm_setup.check_farm_token_supply(farm_token_amount); + + let block_nonce = 10u64; + farm_setup.b_mock.set_block_nonce(block_nonce); + + // 1000 rewards per block + let total_rewards = 1000 * block_nonce; + + // Only base rewards are given + let base_rewards = + total_rewards * (MAX_PERCENTAGE - BOOSTED_YIELDS_PERCENTAGE) / MAX_PERCENTAGE; + farm_setup + .b_mock + .check_esdt_balance(&external_user, REWARD_TOKEN_ID, &rust_biguint!(0)); + farm_setup.claim_rewards_on_behalf(&authorized_address, farm_token_nonce, farm_token_amount); + farm_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(base_rewards), + ); +} + +#[test] +fn test_multiple_positions_on_behalf() { + 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, 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; + let boosted_rewards = total_rewards * BOOSTED_YIELDS_PERCENTAGE / MAX_PERCENTAGE; + + // Only base rewards are given + farm_setup + .b_mock + .check_esdt_balance(&external_user, REWARD_TOKEN_ID, &rust_biguint!(0)); + farm_setup.claim_rewards_on_behalf(&authorized_address, 1, farm_token_amount); + farm_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(base_rewards), + ); + + // random tx on end of week 1, to cummulate rewards + farm_setup.b_mock.set_block_epoch(6); + let temp_user = farm_setup.third_user.clone(); + farm_setup.set_user_energy(&external_user, 1_000, 6, 1); + farm_setup.set_user_energy(&temp_user, 1, 6, 1); + farm_setup.last_farm_token_nonce = 2; + farm_setup.enter_farm(&temp_user, 1); + farm_setup.exit_farm(&temp_user, 3, 1); + + // advance 1 week + block_nonce += block_nonce_diff; + farm_setup.b_mock.set_block_nonce(block_nonce); + farm_setup.b_mock.set_block_epoch(10); + farm_setup.set_user_energy(&external_user, 1_000, 10, 1); + + // enter farm again for the same user (with additional payment) + farm_setup.check_farm_token_supply(farm_token_amount); + farm_setup.enter_farm_on_behalf( + &authorized_address, + &external_user, + farm_token_amount, + 2, // nonce 2 as the user already claimed with this position + farm_token_amount, + ); + farm_setup.check_farm_token_supply(farm_token_amount * 2); + farm_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(base_rewards + boosted_rewards), + ); + + farm_setup.claim_rewards_on_behalf(&authorized_address, 4, farm_token_amount * 2); + farm_setup.check_farm_token_supply(farm_token_amount * 2); + farm_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(total_rewards + base_rewards), + ); +} + +#[test] +fn test_enter_and_claim_farm_on_behalf_not_whitelisted_error() { + 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(); + + // new external user + let external_user = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); + + // authorized address + let authorized_address = farm_setup.first_user.clone(); + + // Try enter without whitelist + farm_setup + .b_mock + .execute_tx( + &authorized_address, + &farm_setup.farm_wrapper, + &rust_biguint!(0), + |sc| { + sc.enter_farm_on_behalf(managed_address!(&external_user)); + }, + ) + .assert_error(4, "Caller is not whitelisted by the user"); + + let farm_token_amount = 100_000_000; + farm_setup.whitelist_address_on_behalf(&external_user, &authorized_address); + farm_setup.enter_farm_on_behalf(&authorized_address, &external_user, farm_token_amount, 0, 0); + + // Try claim without whitelist + farm_setup.remove_whitelist_address_on_behalf(&external_user, &authorized_address); + farm_setup + .b_mock + .execute_esdt_transfer( + &authorized_address, + &farm_setup.farm_wrapper, + FARM_TOKEN_ID, + 1, + &rust_biguint!(farm_token_amount), + |sc| { + sc.claim_rewards_on_behalf(); + }, + ) + .assert_error(4, "Caller is not whitelisted by the user"); +} + +#[test] +fn test_wrong_original_owner_on_behalf_validation() { + 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(); + + // new external users + let external_user1 = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); + let external_user2 = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); + + // authorized address + let authorized_address = farm_setup.first_user.clone(); + + let farm_token_amount = 100_000_000; + farm_setup.whitelist_address_on_behalf(&external_user1, &authorized_address); + farm_setup.whitelist_address_on_behalf(&external_user2, &authorized_address); + farm_setup.enter_farm_on_behalf( + &authorized_address, + &external_user1, + farm_token_amount, + 0, + 0, + ); + farm_setup.enter_farm_on_behalf( + &authorized_address, + &external_user2, + farm_token_amount, + 0, + 0, + ); + + // Try enter farm with wrong position + farm_setup.b_mock.set_esdt_balance( + &authorized_address, + FARMING_TOKEN_ID, + &rust_biguint!(farm_token_amount), + ); + let mut enter_farm_payments = Vec::new(); + enter_farm_payments.push(TxTokenTransfer { + token_identifier: FARMING_TOKEN_ID.to_vec(), + nonce: 0, + value: rust_biguint!(farm_token_amount), + }); + enter_farm_payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: 2, // external_user2 position + value: rust_biguint!(farm_token_amount), + }); + farm_setup + .b_mock + .execute_esdt_multi_transfer( + &authorized_address, + &farm_setup.farm_wrapper, + &enter_farm_payments, + |sc| { + sc.enter_farm_on_behalf(managed_address!(&external_user1)); + }, + ) + .assert_error(4, "Provided address is not the same as the original owner"); + + // Try claim with different original owners + let mut claim_payments = Vec::new(); + claim_payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: 1, // external_user1 position + value: rust_biguint!(farm_token_amount), + }); + claim_payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: 2, // external_user2 position + value: rust_biguint!(farm_token_amount), + }); + farm_setup + .b_mock + .execute_esdt_multi_transfer( + &authorized_address, + &farm_setup.farm_wrapper, + &claim_payments, + |sc| { + sc.claim_rewards_on_behalf(); + }, + ) + .assert_error(4, "All position must have the same original owner"); +} diff --git a/dex/farm/tests/farm_multi_user_test.rs b/dex/farm/tests/farm_multi_user_test.rs index 03315b184..d37bee046 100644 --- a/dex/farm/tests/farm_multi_user_test.rs +++ b/dex/farm/tests/farm_multi_user_test.rs @@ -18,6 +18,7 @@ fn farm_with_no_boost_test() { farm::contract_obj, energy_factory_mock::contract_obj, energy_update::contract_obj, + permissions_hub::contract_obj, ); // first user enter farm @@ -116,6 +117,7 @@ fn farm_with_boosted_yields_test() { 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); @@ -228,6 +230,7 @@ fn farm_change_boosted_yields_factors_test() { 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); @@ -291,6 +294,7 @@ fn farm_boosted_yields_claim_with_different_user_pos_test() { 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); @@ -397,6 +401,7 @@ fn farm_known_proxy_test() { farm::contract_obj, energy_factory_mock::contract_obj, energy_update::contract_obj, + permissions_hub::contract_obj, ); // first user enter farm @@ -496,6 +501,7 @@ fn farm_multiple_claim_weeks_with_collect_undistributed_rewards_test() { 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); @@ -746,6 +752,7 @@ fn farm_enter_with_multiple_farm_token() { 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); @@ -861,6 +868,7 @@ fn farm_claim_with_minimum_tokens() { 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); 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 cb12a2a1e..b19d14aa9 100644 --- a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs +++ b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs @@ -3,6 +3,7 @@ use common_structs::FarmTokenAttributes; use config::ConfigModule; +use farm::external_interaction::ExternalInteractionsModule; use multiversx_sc::codec::multi_types::OptionalValue; use multiversx_sc::{ storage::mappers::StorageTokenWrapper, @@ -23,6 +24,7 @@ use farm_boosted_yields::boosted_yields_factors::BoostedYieldsFactorsModule; use farm_boosted_yields::FarmBoostedYieldsModule; use farm_token::FarmTokenModule; use pausable::{PausableModule, State}; +use permissions_hub::PermissionsHub; use sc_whitelist_module::SCWhitelistModule; use week_timekeeping::Epoch; use weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule; @@ -54,11 +56,16 @@ pub struct NonceAmountPair { pub amount: u64, } -pub struct MultiUserFarmSetup -where +pub struct MultiUserFarmSetup< + FarmObjBuilder, + EnergyFactoryBuilder, + EnergyUpdateObjBuilder, + PermissionsHubObjBuilder, +> where FarmObjBuilder: 'static + Copy + Fn() -> farm::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory_mock::ContractObj, EnergyUpdateObjBuilder: 'static + Copy + Fn() -> energy_update::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, { pub b_mock: BlockchainStateWrapper, pub owner: Address, @@ -71,19 +78,28 @@ where ContractObjWrapper, EnergyFactoryBuilder>, pub eu_wrapper: ContractObjWrapper, EnergyUpdateObjBuilder>, + pub permissions_hub_wrapper: + ContractObjWrapper, PermissionsHubObjBuilder>, } -impl - MultiUserFarmSetup +impl + MultiUserFarmSetup< + FarmObjBuilder, + EnergyFactoryBuilder, + EnergyUpdateObjBuilder, + PermissionsHubObjBuilder, + > where FarmObjBuilder: 'static + Copy + Fn() -> farm::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory_mock::ContractObj, EnergyUpdateObjBuilder: 'static + Copy + Fn() -> energy_update::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, { pub fn new( farm_builder: FarmObjBuilder, energy_factory_builder: EnergyFactoryBuilder, eu_builder: EnergyUpdateObjBuilder, + permissions_hub_builder: PermissionsHubObjBuilder, ) -> Self { let rust_zero = rust_biguint!(0); let mut b_mock = BlockchainStateWrapper::new(); @@ -108,6 +124,19 @@ where }) .assert_ok(); + let permissions_hub_wrapper = b_mock.create_sc_account( + &rust_zero, + Some(&owner), + permissions_hub_builder, + "permissions_hub.wasm", + ); + + b_mock + .execute_tx(&owner, &permissions_hub_wrapper, &rust_zero, |sc| { + sc.init(); + }) + .assert_ok(); + b_mock .execute_tx(&owner, &farm_wrapper, &rust_zero, |sc| { let reward_token_id = managed_token_id!(REWARD_TOKEN_ID); @@ -135,6 +164,10 @@ where sc.set_energy_factory_address(managed_address!( energy_factory_wrapper.address_ref() )); + + sc.set_permissions_hub_address(managed_address!( + permissions_hub_wrapper.address_ref() + )); }) .assert_ok(); @@ -189,6 +222,7 @@ where farm_wrapper, energy_factory_wrapper, eu_wrapper, + permissions_hub_wrapper, } } @@ -616,6 +650,113 @@ where .assert_ok(); } + pub fn whitelist_address_on_behalf(&mut self, user: &Address, address_to_whitelist: &Address) { + self.b_mock + .execute_tx( + user, + &self.permissions_hub_wrapper, + &rust_biguint!(0), + |sc| { + sc.whitelist(managed_address!(address_to_whitelist)); + }, + ) + .assert_ok(); + } + + pub fn remove_whitelist_address_on_behalf( + &mut self, + user: &Address, + address_to_whitelist: &Address, + ) { + self.b_mock + .execute_tx( + user, + &self.permissions_hub_wrapper, + &rust_biguint!(0), + |sc| { + sc.remove_whitelist(managed_address!(address_to_whitelist)); + }, + ) + .assert_ok(); + } + + pub fn enter_farm_on_behalf( + &mut self, + caller: &Address, + user: &Address, + farming_token_amount: u64, + farm_token_nonce: u64, + farm_token_amount: u64, + ) { + let mut payments = Vec::new(); + payments.push(TxTokenTransfer { + token_identifier: FARMING_TOKEN_ID.to_vec(), + nonce: 0, + value: rust_biguint!(farming_token_amount), + }); + + if farm_token_nonce > 0 { + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: farm_token_nonce, + value: rust_biguint!(farm_token_amount), + }); + } + + let b_mock = &mut self.b_mock; + b_mock + .execute_esdt_multi_transfer(caller, &self.farm_wrapper, &payments, |sc| { + let enter_farm_result = sc.enter_farm_on_behalf(managed_address!(user)); + let (out_farm_token, _reward_token) = enter_farm_result.into_tuple(); + assert_eq!( + out_farm_token.token_identifier, + managed_token_id!(FARM_TOKEN_ID) + ); + assert_eq!( + out_farm_token.amount, + managed_biguint!(farming_token_amount + farm_token_amount) + ); + }) + .assert_ok(); + } + + pub fn claim_rewards_on_behalf( + &mut self, + caller: &Address, + farm_token_nonce: u64, + farm_token_amount: u64, + ) -> u64 { + let mut result = 0; + self.b_mock + .execute_esdt_transfer( + caller, + &self.farm_wrapper, + FARM_TOKEN_ID, + farm_token_nonce, + &rust_biguint!(farm_token_amount), + |sc| { + let (out_farm_token, out_reward_token) = + sc.claim_rewards_on_behalf().into_tuple(); + assert_eq!( + out_farm_token.token_identifier, + managed_token_id!(FARM_TOKEN_ID) + ); + assert_eq!(out_farm_token.amount, managed_biguint!(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 update_energy_for_user(&mut self) { let b_mock = &mut self.b_mock; let user_addr = &self.first_user; diff --git a/dex/farm/wasm/Cargo.lock b/dex/farm/wasm/Cargo.lock index c3dea8cdc..18594d924 100644 --- a/dex/farm/wasm/Cargo.lock +++ b/dex/farm/wasm/Cargo.lock @@ -138,6 +138,7 @@ dependencies = [ "multiversx-sc-modules", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -393,6 +394,13 @@ dependencies = [ "permissions_module", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + [[package]] name = "permissions_module" version = "0.0.0" diff --git a/dex/farm/wasm/src/lib.rs b/dex/farm/wasm/src/lib.rs index 525d76239..3902fdb5d 100644 --- a/dex/farm/wasm/src/lib.rs +++ b/dex/farm/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 63 +// Endpoints: 66 // Async Callback: 1 -// Total number of exported functions: 66 +// Total number of exported functions: 69 #![no_std] @@ -63,6 +63,9 @@ multiversx_sc_wasm_adapter::endpoints! { getMinimumFarmingEpoch => minimum_farming_epochs getBurnGasLimit => burn_gas_limit getPairContractManagedAddress => pair_contract_address + enterFarmOnBehalf => enter_farm_on_behalf + claimRewardsOnBehalf => claim_rewards_on_behalf + setPermissionsHubAddress => set_permissions_hub_address collectUndistributedBoostedRewards => collect_undistributed_boosted_rewards getBoostedYieldsRewardsPercentage => boosted_yields_rewards_percentage getAccumulatedRewardsForWeek => accumulated_rewards_for_week diff --git a/dex/permissions-hub/Cargo.toml b/dex/permissions-hub/Cargo.toml new file mode 100644 index 000000000..cf8673610 --- /dev/null +++ b/dex/permissions-hub/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "permissions-hub" +version = "0.0.0" +publish = false +edition = "2021" +authors = ["you"] + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.53.2" + +[dev-dependencies] +num-bigint = "0.4" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.53.2" diff --git a/dex/permissions-hub/meta/Cargo.toml b/dex/permissions-hub/meta/Cargo.toml new file mode 100644 index 000000000..c939cfe71 --- /dev/null +++ b/dex/permissions-hub/meta/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "permissions-hub-meta" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies.permissions-hub] +path = ".." + +[dependencies.multiversx-sc-meta-lib] +version = "0.53.2" +default-features = false diff --git a/dex/permissions-hub/meta/src/main.rs b/dex/permissions-hub/meta/src/main.rs new file mode 100644 index 000000000..984508b6c --- /dev/null +++ b/dex/permissions-hub/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta_lib::cli_main::(); +} diff --git a/dex/permissions-hub/multiversx.json b/dex/permissions-hub/multiversx.json new file mode 100644 index 000000000..736553962 --- /dev/null +++ b/dex/permissions-hub/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/dex/permissions-hub/src/lib.rs b/dex/permissions-hub/src/lib.rs new file mode 100644 index 000000000..9887fe90c --- /dev/null +++ b/dex/permissions-hub/src/lib.rs @@ -0,0 +1,57 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::contract] +pub trait PermissionsHub { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + #[endpoint(whitelist)] + fn whitelist(&self, address_to_whitelist: ManagedAddress) { + let caller = self.blockchain().get_caller(); + self.user_whitelisted_addresses(&caller) + .insert(address_to_whitelist); + } + + #[endpoint(removeWhitelist)] + fn remove_whitelist(&self, address_to_remove: ManagedAddress) { + let caller = self.blockchain().get_caller(); + self.user_whitelisted_addresses(&caller) + .swap_remove(&address_to_remove); + } + + #[only_owner] + #[endpoint(blacklist)] + fn blacklist(&self, address_to_blacklist: ManagedAddress) { + self.blacklisted_addresses().insert(address_to_blacklist); + } + + #[only_owner] + #[endpoint(removeBlacklist)] + fn remove_blacklist(&self, address_to_remove: ManagedAddress) { + self.blacklisted_addresses().swap_remove(&address_to_remove); + } + + #[view(isWhitelisted)] + fn is_whitelisted(&self, user: &ManagedAddress, address_to_check: &ManagedAddress) -> bool { + !self.blacklisted_addresses().contains(address_to_check) + && self + .user_whitelisted_addresses(user) + .contains(address_to_check) + } + + #[storage_mapper("whitelistedAddresses")] + fn user_whitelisted_addresses( + &self, + user: &ManagedAddress, + ) -> UnorderedSetMapper; + + #[view(getBlacklistedAddresses)] + #[storage_mapper("blacklistedAddresses")] + fn blacklisted_addresses(&self) -> UnorderedSetMapper; +} diff --git a/dex/permissions-hub/wasm/Cargo.lock b/dex/permissions-hub/wasm/Cargo.lock new file mode 100644 index 000000000..a5b82e4db --- /dev/null +++ b/dex/permissions-hub/wasm/Cargo.lock @@ -0,0 +1,188 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[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 = "multiversx-sc" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ea89a26f0aacda21437a8ae5ccfbefab99d8191942b3d2eddbcbf84f9866d7" +dependencies = [ + "bitflags", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d7a5a8534e5dc9128cb8f15a65a21dd378e135c6016c7cd1491cd012bc8cb" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffba1dce273ed5b61ee1b90aeea5c8c744617d0f12624f620768c144d83e753" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c17fdf90fafca2f19085ae67b0502d9f71bf8ab1be3c83808eb88e02a8c18b9" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20659915a4377d375c46d7f237e810053a03f7e084fad6362dd5748a7233defb" +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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "permissions-hub" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "permissions-hub-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "permissions-hub", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +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.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unwrap-infallible" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" diff --git a/dex/permissions-hub/wasm/Cargo.toml b/dex/permissions-hub/wasm/Cargo.toml new file mode 100644 index 000000000..dd9a073c3 --- /dev/null +++ b/dex/permissions-hub/wasm/Cargo.toml @@ -0,0 +1,34 @@ +# Code generated by the multiversx-sc build system. DO NOT EDIT. + +# ########################################## +# ############## AUTO-GENERATED ############# +# ########################################## + +[package] +name = "permissions-hub-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 + +[profile.dev] +panic = "abort" + +[dependencies.permissions-hub] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.53.2" + +[workspace] +members = ["."] diff --git a/dex/permissions-hub/wasm/src/lib.rs b/dex/permissions-hub/wasm/src/lib.rs new file mode 100644 index 000000000..afc22eb88 --- /dev/null +++ b/dex/permissions-hub/wasm/src/lib.rs @@ -0,0 +1,26 @@ +// Code generated by the multiversx-sc build system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Upgrade: 1 +// Endpoints: 0 +// Async Callback (empty): 1 +// Total number of exported functions: 3 + +#![no_std] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + permissions_hub + ( + init => init + upgrade => upgrade + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {} From d5aa366ecfa9c5bcee66afbc93075a55c5bb49c8 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Sun, 27 Oct 2024 16:40:24 +0200 Subject: [PATCH 02/11] test fixes --- dex/farm/tests/energy_update_test.rs | 3 +++ dex/farm/tests/total_farm_position_test.rs | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/dex/farm/tests/energy_update_test.rs b/dex/farm/tests/energy_update_test.rs index 1d8f78a98..a15fbc5e2 100644 --- a/dex/farm/tests/energy_update_test.rs +++ b/dex/farm/tests/energy_update_test.rs @@ -10,6 +10,7 @@ fn test_farm_setup() { farm::contract_obj, energy_factory_mock::contract_obj, energy_update::contract_obj, + permissions_hub::contract_obj, ); } @@ -19,6 +20,7 @@ fn test_energy_update() { farm::contract_obj, energy_factory_mock::contract_obj, energy_update::contract_obj, + permissions_hub::contract_obj, ); let first_farm_token_amount = 100_000_000; @@ -39,6 +41,7 @@ fn test_energy_update_no_claim_current_week() { farm::contract_obj, energy_factory_mock::contract_obj, energy_update::contract_obj, + permissions_hub::contract_obj, ); let first_farm_token_amount = 100_000_000; diff --git a/dex/farm/tests/total_farm_position_test.rs b/dex/farm/tests/total_farm_position_test.rs index b7ca1be7b..4bee74227 100644 --- a/dex/farm/tests/total_farm_position_test.rs +++ b/dex/farm/tests/total_farm_position_test.rs @@ -23,6 +23,7 @@ fn total_farm_position_claim_test() { 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); @@ -128,6 +129,7 @@ fn allow_external_claim_rewards_setting_test() { 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); @@ -194,6 +196,7 @@ fn total_farm_position_claim_for_other_test() { 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); @@ -297,6 +300,7 @@ fn farm_total_position_migration_test() { 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); @@ -414,6 +418,7 @@ fn farm_total_position_exit_migration_test() { 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); @@ -494,6 +499,7 @@ fn farm_total_position_on_claim_migration_test() { 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); @@ -584,6 +590,7 @@ fn farm_total_position_on_merge_migration_test() { 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); @@ -678,6 +685,7 @@ fn no_boosted_rewards_penalty_for_no_energy_test() { 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); @@ -758,6 +766,7 @@ fn total_farm_position_owner_change_test() { 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); @@ -982,6 +991,7 @@ fn total_farm_position_through_simple_lock_test() { farm::contract_obj, energy_factory_mock::contract_obj, energy_update::contract_obj, + permissions_hub::contract_obj, ); let rust_zero = rust_biguint!(0); From ce3a489716ccadbb0faaed0ef621561ec3e01cbc Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Mon, 28 Oct 2024 17:20:09 +0200 Subject: [PATCH 03/11] on behalf features for farm with locked rewards --- Cargo.lock | 2 + dex/farm-with-locked-rewards/Cargo.toml | 3 + .../src/external_interaction.rs | 171 ++++++++++++++++++ dex/farm-with-locked-rewards/src/lib.rs | 3 + dex/farm-with-locked-rewards/wasm/Cargo.lock | 9 + dex/farm-with-locked-rewards/wasm/src/lib.rs | 7 +- 6 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 dex/farm-with-locked-rewards/src/external_interaction.rs diff --git a/Cargo.lock b/Cargo.lock index 4f8b88d23..068e52c2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -589,6 +589,7 @@ dependencies = [ "num-bigint", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -768,6 +769,7 @@ dependencies = [ "multiversx-sc-scenario", "num-bigint", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", diff --git a/dex/farm-with-locked-rewards/Cargo.toml b/dex/farm-with-locked-rewards/Cargo.toml index eedd45925..f637b3249 100644 --- a/dex/farm-with-locked-rewards/Cargo.toml +++ b/dex/farm-with-locked-rewards/Cargo.toml @@ -74,6 +74,9 @@ path = "../../locked-asset/energy-factory" [dependencies.energy-query] path = "../../energy-integration/common-modules/energy-query" +[dependencies.permissions-hub] +path = "../permissions-hub" + [dependencies.multiversx-sc] version = "=0.53.2" features = ["esdt-token-payment-legacy-decode"] diff --git a/dex/farm-with-locked-rewards/src/external_interaction.rs b/dex/farm-with-locked-rewards/src/external_interaction.rs new file mode 100644 index 000000000..54ecf7510 --- /dev/null +++ b/dex/farm-with-locked-rewards/src/external_interaction.rs @@ -0,0 +1,171 @@ +multiversx_sc::imports!(); + +use common_structs::FarmTokenAttributes; +use farm::{ + base_functions::{self, ClaimRewardsResultType}, + exit_penalty, EnterFarmResultType, +}; + +use crate::NoMintWrapper; + +#[multiversx_sc::module] +pub trait ExternalInteractionsModule: + rewards::RewardsModule + + config::ConfigModule + + token_send::TokenSendModule + + farm_token::FarmTokenModule + + pausable::PausableModule + + permissions_module::PermissionsModule + + sc_whitelist_module::SCWhitelistModule + + events::EventsModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + base_functions::BaseFunctionsModule + + exit_penalty::ExitPenaltyModule + + locking_module::lock_with_energy_module::LockWithEnergyModule + + farm_base_impl::base_farm_init::BaseFarmInitModule + + farm_base_impl::base_farm_validation::BaseFarmValidationModule + + farm_base_impl::enter_farm::BaseEnterFarmModule + + farm_base_impl::claim_rewards::BaseClaimRewardsModule + + farm_base_impl::compound_rewards::BaseCompoundRewardsModule + + farm_base_impl::exit_farm::BaseExitFarmModule + + 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 + + utils::UtilsModule +{ + #[payable("*")] + #[endpoint(enterFarmOnBehalf)] + fn enter_farm_on_behalf(&self, user: ManagedAddress) -> EnterFarmResultType { + let caller = self.blockchain().get_caller(); + self.require_user_whitelisted(&user, &caller); + + self.check_additional_payments_original_owner(&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); + + let locked_rewards_payment = if boosted_rewards == 0 { + let locked_token_id = self.get_locked_token_id(); + EsdtTokenPayment::new(locked_token_id, 0, boosted_rewards) + } else { + self.lock_virtual( + self.reward_token_id().get(), + boosted_rewards, + user.clone(), + user.clone(), + ) + }; + + self.update_energy_and_progress(&user); + + (new_farm_token, locked_rewards_payment).into() + } + + #[payable("*")] + #[endpoint(claimRewardsOnBehalf)] + fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { + let user = self.check_and_return_original_owner(); + let caller = self.blockchain().get_caller(); + self.require_user_whitelisted(&user, &caller); + + 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, + ) + }; + + (claim_rewards_result.new_farm_token, locked_rewards_payment).into() + } + + fn check_and_return_original_owner(&self) -> ManagedAddress { + let payments = self.call_value().all_esdt_transfers().clone_value(); + let farm_token_mapper = self.farm_token(); + let mut original_owner = ManagedAddress::zero(); + for payment in payments.into_iter() { + let attributes: FarmTokenAttributes = + farm_token_mapper.get_token_attributes(payment.token_nonce); + + if original_owner.is_zero() { + original_owner = attributes.original_owner; + } else { + require!( + original_owner == attributes.original_owner, + "All position must have the same original owner" + ); + } + } + + require!( + !original_owner.is_zero(), + "Original owner could not be identified" + ); + + original_owner + } + + fn check_additional_payments_original_owner(&self, user: &ManagedAddress) { + let payments = self.call_value().all_esdt_transfers().clone_value(); + if payments.len() == 1 { + return; + } + + let farm_token_mapper = self.farm_token(); + let farm_token_id = farm_token_mapper.get_token_id(); + for payment in payments.into_iter() { + if payment.token_identifier != farm_token_id { + continue; + } + + let attributes: FarmTokenAttributes = + farm_token_mapper.get_token_attributes(payment.token_nonce); + + require!( + user == &attributes.original_owner, + "Provided address is not the same as the original owner" + ); + } + } + + fn require_user_whitelisted(&self, user: &ManagedAddress, authorized_address: &ManagedAddress) { + let permissions_hub_address = self.permissions_hub_address().get(); + let is_whitelisted: bool = self + .permissions_hub_proxy(permissions_hub_address) + .is_whitelisted(user, authorized_address) + .execute_on_dest_context(); + + require!(is_whitelisted, "Caller is not whitelisted by the user"); + } + + #[only_owner] + #[endpoint(setPermissionsHubAddress)] + fn set_permissions_hub_address(&self, address: ManagedAddress) { + self.permissions_hub_address().set(&address); + } + + #[proxy] + fn permissions_hub_proxy( + &self, + sc_address: ManagedAddress, + ) -> permissions_hub::Proxy; + + #[storage_mapper("permissionsHubAddress")] + fn permissions_hub_address(&self) -> SingleValueMapper; +} diff --git a/dex/farm-with-locked-rewards/src/lib.rs b/dex/farm-with-locked-rewards/src/lib.rs index 7434704d1..fefbf6275 100644 --- a/dex/farm-with-locked-rewards/src/lib.rs +++ b/dex/farm-with-locked-rewards/src/lib.rs @@ -3,6 +3,8 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); +pub mod external_interaction; + use common_structs::FarmTokenAttributes; use contexts::storage_cache::StorageCache; use core::marker::PhantomData; @@ -32,6 +34,7 @@ pub trait Farm: + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + farm::base_functions::BaseFunctionsModule + farm::exit_penalty::ExitPenaltyModule + + external_interaction::ExternalInteractionsModule + farm_base_impl::base_farm_init::BaseFarmInitModule + farm_base_impl::base_farm_validation::BaseFarmValidationModule + farm_base_impl::enter_farm::BaseEnterFarmModule diff --git a/dex/farm-with-locked-rewards/wasm/Cargo.lock b/dex/farm-with-locked-rewards/wasm/Cargo.lock index 897ce8c8e..aa7396f98 100644 --- a/dex/farm-with-locked-rewards/wasm/Cargo.lock +++ b/dex/farm-with-locked-rewards/wasm/Cargo.lock @@ -138,6 +138,7 @@ dependencies = [ "multiversx-sc-modules", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -182,6 +183,7 @@ dependencies = [ "multiversx-sc", "multiversx-sc-modules", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -423,6 +425,13 @@ dependencies = [ "permissions_module", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + [[package]] name = "permissions_module" version = "0.0.0" diff --git a/dex/farm-with-locked-rewards/wasm/src/lib.rs b/dex/farm-with-locked-rewards/wasm/src/lib.rs index e8aa3099f..6efe13330 100644 --- a/dex/farm-with-locked-rewards/wasm/src/lib.rs +++ b/dex/farm-with-locked-rewards/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 66 +// Endpoints: 69 // Async Callback: 1 -// Total number of exported functions: 69 +// Total number of exported functions: 72 #![no_std] @@ -66,6 +66,9 @@ multiversx_sc_wasm_adapter::endpoints! { getMinimumFarmingEpoch => minimum_farming_epochs getBurnGasLimit => burn_gas_limit getPairContractManagedAddress => pair_contract_address + enterFarmOnBehalf => enter_farm_on_behalf + claimRewardsOnBehalf => claim_rewards_on_behalf + setPermissionsHubAddress => set_permissions_hub_address collectUndistributedBoostedRewards => collect_undistributed_boosted_rewards getBoostedYieldsRewardsPercentage => boosted_yields_rewards_percentage getAccumulatedRewardsForWeek => accumulated_rewards_for_week From 5e4ba5ff20140a3eecb151a3ceef5780f18bd731 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Mon, 28 Oct 2024 17:20:34 +0200 Subject: [PATCH 04/11] on behalf features for farm staking --- farm-staking/farm-staking/Cargo.toml | 3 + .../farm-staking/src/external_interaction.rs | 198 ++++++++++++++++++ farm-staking/farm-staking/src/lib.rs | 2 + farm-staking/farm-staking/wasm/Cargo.lock | 9 + farm-staking/farm-staking/wasm/src/lib.rs | 7 +- 5 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 farm-staking/farm-staking/src/external_interaction.rs diff --git a/farm-staking/farm-staking/Cargo.toml b/farm-staking/farm-staking/Cargo.toml index 8f1817a89..e19231477 100644 --- a/farm-staking/farm-staking/Cargo.toml +++ b/farm-staking/farm-staking/Cargo.toml @@ -77,6 +77,9 @@ path = "../../common/common_structs" [dependencies.common_errors] path = "../../common/common_errors" +[dependencies.permissions-hub] +path = "../../dex/permissions-hub" + [dependencies.multiversx-sc] version = "=0.53.2" features = ["esdt-token-payment-legacy-decode"] diff --git a/farm-staking/farm-staking/src/external_interaction.rs b/farm-staking/farm-staking/src/external_interaction.rs new file mode 100644 index 000000000..081ec07b2 --- /dev/null +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -0,0 +1,198 @@ +multiversx_sc::imports!(); + +use farm::{base_functions::ClaimRewardsResultType, EnterFarmResultType}; + +use crate::{ + base_impl_wrapper::FarmStakingWrapper, claim_only_boosted_staking_rewards, + claim_stake_farm_rewards, compound_stake_farm_rewards, custom_rewards, farm_token_roles, + stake_farm, token_attributes::StakingFarmTokenAttributes, unbond_farm, unstake_farm, +}; + +#[multiversx_sc::module] +pub trait ExternalInteractionsModule: + custom_rewards::CustomRewardsModule + + rewards::RewardsModule + + config::ConfigModule + + events::EventsModule + + token_send::TokenSendModule + + farm_token::FarmTokenModule + + sc_whitelist_module::SCWhitelistModule + + pausable::PausableModule + + permissions_module::PermissionsModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + farm_base_impl::base_farm_init::BaseFarmInitModule + + farm_base_impl::base_farm_validation::BaseFarmValidationModule + + farm_base_impl::enter_farm::BaseEnterFarmModule + + farm_base_impl::claim_rewards::BaseClaimRewardsModule + + farm_base_impl::compound_rewards::BaseCompoundRewardsModule + + farm_base_impl::exit_farm::BaseExitFarmModule + + utils::UtilsModule + + farm_token_roles::FarmTokenRolesModule + + stake_farm::StakeFarmModule + + claim_stake_farm_rewards::ClaimStakeFarmRewardsModule + + compound_stake_farm_rewards::CompoundStakeFarmRewardsModule + + unstake_farm::UnstakeFarmModule + + unbond_farm::UnbondFarmModule + + claim_only_boosted_staking_rewards::ClaimOnlyBoostedStakingRewardsModule + + 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 +{ + #[payable("*")] + #[endpoint(stakeFarmOnBehalf)] + fn stake_farm_on_behalf(&self, user: ManagedAddress) -> EnterFarmResultType { + let caller = self.blockchain().get_caller(); + self.require_user_whitelisted(&user, &caller); + + self.check_additional_payments_original_owner(&user); + + let payments = self.get_non_empty_payments(); + + let boosted_rewards = self.claim_only_boosted_payment(&user); + + let boosted_rewards_payment = + EsdtTokenPayment::new(self.reward_token_id().get(), 0, boosted_rewards); + + let enter_result = self.enter_farm_base::>(user.clone(), payments); + + let new_farm_token = enter_result.new_farm_token.payment.clone(); + self.send_payment_non_zero(&caller, &new_farm_token); + self.send_payment_non_zero(&user, &boosted_rewards_payment); + + self.set_farm_supply_for_current_week(&enter_result.storage_cache.farm_token_supply); + + self.update_energy_and_progress(&user); + + self.emit_enter_farm_event( + &caller, + enter_result.context.farming_token_payment, + enter_result.new_farm_token, + enter_result.created_with_merge, + enter_result.storage_cache, + ); + + (new_farm_token, boosted_rewards_payment).into() + } + + #[payable("*")] + #[endpoint(claimRewardsOnBehalf)] + fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { + let user = self.check_and_return_original_owner(); + let caller = self.blockchain().get_caller(); + self.require_user_whitelisted(&user, &caller); + + let payment = self.call_value().single_esdt(); + let claim_result = self.claim_rewards_base_no_farm_token_mint::>( + user.clone(), + ManagedVec::from_single_item(payment), + ); + + let mut virtual_farm_token = claim_result.new_farm_token.clone(); + + self.set_farm_supply_for_current_week(&claim_result.storage_cache.farm_token_supply); + + self.update_energy_and_progress(&user); + + let new_farm_token_nonce = self.send().esdt_nft_create_compact( + &virtual_farm_token.payment.token_identifier, + &virtual_farm_token.payment.amount, + &virtual_farm_token.attributes, + ); + 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, + claim_result.context, + virtual_farm_token.clone(), + claim_result.rewards.clone(), + claim_result.created_with_merge, + claim_result.storage_cache, + ); + + (virtual_farm_token.payment, claim_result.rewards).into() + } + + fn check_and_return_original_owner(&self) -> ManagedAddress { + let payments = self.call_value().all_esdt_transfers().clone_value(); + let farm_token_mapper = self.farm_token(); + let mut original_owner = ManagedAddress::zero(); + for payment in payments.into_iter() { + let attributes: StakingFarmTokenAttributes = + farm_token_mapper.get_token_attributes(payment.token_nonce); + + if original_owner.is_zero() { + original_owner = attributes.original_owner; + } else { + require!( + original_owner == attributes.original_owner, + "All position must have the same original owner" + ); + } + } + + require!( + !original_owner.is_zero(), + "Original owner could not be identified" + ); + + original_owner + } + + fn check_additional_payments_original_owner(&self, user: &ManagedAddress) { + let payments = self.call_value().all_esdt_transfers().clone_value(); + if payments.len() == 1 { + return; + } + + let farm_token_mapper = self.farm_token(); + let farm_token_id = farm_token_mapper.get_token_id(); + for payment in payments.into_iter() { + if payment.token_identifier != farm_token_id { + continue; + } + + let attributes: StakingFarmTokenAttributes = + farm_token_mapper.get_token_attributes(payment.token_nonce); + + require!( + user == &attributes.original_owner, + "Provided address is not the same as the original owner" + ); + } + } + + fn require_user_whitelisted(&self, user: &ManagedAddress, authorized_address: &ManagedAddress) { + let permissions_hub_address = self.permissions_hub_address().get(); + let is_whitelisted: bool = self + .permissions_hub_proxy(permissions_hub_address) + .is_whitelisted(user, authorized_address) + .execute_on_dest_context(); + + require!(is_whitelisted, "Caller is not whitelisted by the user"); + } + + #[only_owner] + #[endpoint(setPermissionsHubAddress)] + fn set_permissions_hub_address(&self, address: ManagedAddress) { + self.permissions_hub_address().set(&address); + } + + #[proxy] + fn permissions_hub_proxy( + &self, + sc_address: ManagedAddress, + ) -> permissions_hub::Proxy; + + #[storage_mapper("permissionsHubAddress")] + fn permissions_hub_address(&self) -> SingleValueMapper; +} diff --git a/farm-staking/farm-staking/src/lib.rs b/farm-staking/farm-staking/src/lib.rs index f783c7831..3f4f9363b 100644 --- a/farm-staking/farm-staking/src/lib.rs +++ b/farm-staking/farm-staking/src/lib.rs @@ -23,6 +23,7 @@ pub mod stake_farm; pub mod token_attributes; pub mod unbond_farm; pub mod unstake_farm; +pub mod external_interaction; #[multiversx_sc::contract] pub trait FarmStaking: @@ -49,6 +50,7 @@ pub trait FarmStaking: + compound_stake_farm_rewards::CompoundStakeFarmRewardsModule + unstake_farm::UnstakeFarmModule + unbond_farm::UnbondFarmModule + + external_interaction::ExternalInteractionsModule + claim_only_boosted_staking_rewards::ClaimOnlyBoostedStakingRewardsModule + farm_boosted_yields::FarmBoostedYieldsModule + farm_boosted_yields::boosted_yields_factors::BoostedYieldsFactorsModule diff --git a/farm-staking/farm-staking/wasm/Cargo.lock b/farm-staking/farm-staking/wasm/Cargo.lock index 465de9d30..267c19b5d 100644 --- a/farm-staking/farm-staking/wasm/Cargo.lock +++ b/farm-staking/farm-staking/wasm/Cargo.lock @@ -138,6 +138,7 @@ dependencies = [ "multiversx-sc-modules", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -183,6 +184,7 @@ dependencies = [ "multiversx-sc-modules", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -424,6 +426,13 @@ dependencies = [ "permissions_module", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + [[package]] name = "permissions_module" version = "0.0.0" diff --git a/farm-staking/farm-staking/wasm/src/lib.rs b/farm-staking/farm-staking/wasm/src/lib.rs index 9489b1d7e..f2e25d699 100644 --- a/farm-staking/farm-staking/wasm/src/lib.rs +++ b/farm-staking/farm-staking/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 69 +// Endpoints: 72 // Async Callback: 1 -// Total number of exported functions: 72 +// Total number of exported functions: 75 #![no_std] @@ -68,6 +68,9 @@ multiversx_sc_wasm_adapter::endpoints! { unstakeFarm => unstake_farm unstakeFarmThroughProxy => unstake_farm_through_proxy unbondFarm => unbond_farm + stakeFarmOnBehalf => stake_farm_on_behalf + claimRewardsOnBehalf => claim_rewards_on_behalf + setPermissionsHubAddress => set_permissions_hub_address claimBoostedRewards => claim_boosted_rewards collectUndistributedBoostedRewards => collect_undistributed_boosted_rewards getBoostedYieldsRewardsPercentage => boosted_yields_rewards_percentage From 157eb15dd505527d3ae98f984447ba8aebd17c84 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 1 Nov 2024 13:19:53 +0200 Subject: [PATCH 05/11] updated farm tests --- Cargo.lock | 1 + dex/farm/tests/external_interaction_test.rs | 23 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 068e52c2d..72759ef35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,6 +630,7 @@ dependencies = [ "num-bigint", "pair", "pausable", + "permissions-hub", "rewards", "sc_whitelist_module", "simple-lock", diff --git a/dex/farm/tests/external_interaction_test.rs b/dex/farm/tests/external_interaction_test.rs index 990d2d926..6a36ae680 100644 --- a/dex/farm/tests/external_interaction_test.rs +++ b/dex/farm/tests/external_interaction_test.rs @@ -2,12 +2,15 @@ mod farm_setup; +use common_structs::FarmTokenAttributes; use farm::external_interaction::ExternalInteractionsModule; use farm_setup::multi_user_farm_setup::{ MultiUserFarmSetup, BOOSTED_YIELDS_PERCENTAGE, FARMING_TOKEN_ID, FARM_TOKEN_ID, MAX_PERCENTAGE, PER_BLOCK_REWARD_AMOUNT, REWARD_TOKEN_ID, }; -use multiversx_sc_scenario::{imports::TxTokenTransfer, managed_address, rust_biguint}; +use multiversx_sc_scenario::{ + imports::TxTokenTransfer, managed_address, managed_biguint, rust_biguint, DebugApi, +}; #[test] fn test_enter_and_claim_farm_on_behalf() { @@ -57,6 +60,8 @@ fn test_enter_and_claim_farm_on_behalf() { #[test] fn test_multiple_positions_on_behalf() { + DebugApi::dummy(); + let mut farm_setup = MultiUserFarmSetup::new( farm::contract_obj, energy_factory_mock::contract_obj, @@ -142,6 +147,22 @@ fn test_multiple_positions_on_behalf() { REWARD_TOKEN_ID, &rust_biguint!(total_rewards + base_rewards), ); + + let farm_token_attributes: FarmTokenAttributes = FarmTokenAttributes { + reward_per_share: managed_biguint!(150_000_000u64), + entering_epoch: 10u64, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(farm_token_amount * 2), + original_owner: managed_address!(&external_user), + }; + + farm_setup.b_mock.check_nft_balance( + &authorized_address, + FARM_TOKEN_ID, + 5, + &rust_biguint!(farm_token_amount * 2), + Some(&farm_token_attributes), + ); } #[test] From b7ef395c6a19e42bf29e759af9888680a6348c8e Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 1 Nov 2024 13:21:11 +0200 Subject: [PATCH 06/11] farm with locked rewards tests --- .../farm_with_locked_rewards_setup/mod.rs | 130 ++++++++++++++++- .../tests/farm_with_locked_rewards_test.rs | 134 ++++++++++++++++++ 2 files changed, 259 insertions(+), 5 deletions(-) diff --git a/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_setup/mod.rs b/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_setup/mod.rs index 5f6015658..a84387950 100644 --- a/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_setup/mod.rs +++ b/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_setup/mod.rs @@ -8,6 +8,7 @@ use multiversx_sc::{ types::{Address, BigInt, EsdtLocalRole, MultiValueEncoded}, }; use multiversx_sc_scenario::{ + imports::TxTokenTransfer, managed_address, managed_biguint, managed_token_id, rust_biguint, whitebox_legacy::{BlockchainStateWrapper, ContractObjWrapper}, DebugApi, @@ -20,10 +21,11 @@ use energy_factory::{energy::EnergyModule, SimpleLockEnergy}; use energy_query::{Energy, EnergyQueryModule}; use farm_boosted_yields::boosted_yields_factors::BoostedYieldsFactorsModule; use farm_token::FarmTokenModule; -use farm_with_locked_rewards::Farm; +use farm_with_locked_rewards::{external_interaction::ExternalInteractionsModule, Farm}; use locking_module::lock_with_energy_module::LockWithEnergyModule; use multiversx_sc_modules::pause::PauseModule; use pausable::{PausableModule, State}; +use permissions_hub::PermissionsHub; use rewards::RewardsModule; use sc_whitelist_module::SCWhitelistModule; use simple_lock::locked_token::LockedTokenModule; @@ -35,8 +37,9 @@ pub static LEGACY_LOCKED_TOKEN_ID: &[u8] = b"LEGACY-123456"; pub static FARMING_TOKEN_ID: &[u8] = b"LPTOK-123456"; pub static FARM_TOKEN_ID: &[u8] = b"FARM-123456"; const DIV_SAFETY: u64 = 1_000_000_000_000; -const PER_BLOCK_REWARD_AMOUNT: u64 = 1_000; +pub const PER_BLOCK_REWARD_AMOUNT: u64 = 1_000; const FARMING_TOKEN_BALANCE: u64 = 100_000_000; +pub const MAX_PERCENTAGE: u64 = 10_000; // 100% pub const BOOSTED_YIELDS_PERCENTAGE: u64 = 2_500; // 25% pub const USER_REWARDS_BASE_CONST: u64 = 10; pub const USER_REWARDS_ENERGY_CONST: u64 = 3; @@ -57,10 +60,11 @@ pub struct RawFarmTokenAttributes { pub original_owner_bytes: [u8; 32], } -pub struct FarmSetup +pub struct FarmSetup where FarmObjBuilder: 'static + Copy + Fn() -> farm_with_locked_rewards::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, { pub b_mock: BlockchainStateWrapper, pub owner: Address, @@ -72,14 +76,22 @@ where ContractObjWrapper, FarmObjBuilder>, pub energy_factory_wrapper: ContractObjWrapper, EnergyFactoryBuilder>, + pub permissions_hub_wrapper: + ContractObjWrapper, PermissionsHubObjBuilder>, } -impl FarmSetup +impl + FarmSetup where FarmObjBuilder: 'static + Copy + Fn() -> farm_with_locked_rewards::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, { - pub fn new(farm_builder: FarmObjBuilder, energy_factory_builder: EnergyFactoryBuilder) -> Self { + pub fn new( + farm_builder: FarmObjBuilder, + energy_factory_builder: EnergyFactoryBuilder, + permissions_hub_builder: PermissionsHubObjBuilder, + ) -> Self { let rust_zero = rust_biguint!(0); let mut b_mock = BlockchainStateWrapper::new(); let owner = b_mock.create_user_account(&rust_zero); @@ -105,6 +117,19 @@ where "fees collector mock", ); + let permissions_hub_wrapper = b_mock.create_sc_account( + &rust_zero, + Some(&owner), + permissions_hub_builder, + "permissions_hub.wasm", + ); + + b_mock + .execute_tx(&owner, &permissions_hub_wrapper, &rust_zero, |sc| { + sc.init(); + }) + .assert_ok(); + b_mock .execute_tx(&owner, &energy_factory_wrapper, &rust_zero, |sc| { let mut lock_options = MultiValueEncoded::new(); @@ -162,6 +187,9 @@ where sc.set_energy_factory_address(managed_address!( energy_factory_wrapper.address_ref() )); + sc.set_permissions_hub_address(managed_address!( + permissions_hub_wrapper.address_ref() + )); }) .assert_ok(); @@ -227,6 +255,7 @@ where last_farm_token_nonce: 0, farm_wrapper, energy_factory_wrapper, + permissions_hub_wrapper, } } @@ -450,6 +479,97 @@ where result } + pub fn enter_farm_on_behalf( + &mut self, + caller: &Address, + user: &Address, + farming_token_amount: u64, + farm_token_nonce: u64, + farm_token_amount: u64, + ) { + let mut payments = Vec::new(); + payments.push(TxTokenTransfer { + token_identifier: FARMING_TOKEN_ID.to_vec(), + nonce: 0, + value: rust_biguint!(farming_token_amount), + }); + + if farm_token_nonce > 0 { + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: farm_token_nonce, + value: rust_biguint!(farm_token_amount), + }); + } + + let b_mock = &mut self.b_mock; + b_mock + .execute_esdt_multi_transfer(caller, &self.farm_wrapper, &payments, |sc| { + let enter_farm_result = sc.enter_farm_on_behalf(managed_address!(user)); + let (out_farm_token, _reward_token) = enter_farm_result.into_tuple(); + assert_eq!( + out_farm_token.token_identifier, + managed_token_id!(FARM_TOKEN_ID) + ); + assert_eq!( + out_farm_token.amount, + managed_biguint!(farming_token_amount + farm_token_amount) + ); + }) + .assert_ok(); + } + + pub fn claim_rewards_on_behalf( + &mut self, + caller: &Address, + farm_token_nonce: u64, + farm_token_amount: u64, + expected_reward_token_nonce: u64, + ) -> u64 { + let mut result = 0; + self.b_mock + .execute_esdt_transfer( + caller, + &self.farm_wrapper, + FARM_TOKEN_ID, + farm_token_nonce, + &rust_biguint!(farm_token_amount), + |sc| { + let (out_farm_token, out_reward_token) = + sc.claim_rewards_on_behalf().into_tuple(); + assert_eq!( + out_farm_token.token_identifier, + managed_token_id!(FARM_TOKEN_ID) + ); + assert_eq!(out_farm_token.amount, managed_biguint!(farm_token_amount)); + + assert_eq!( + out_reward_token.token_identifier, + managed_token_id!(LOCKED_REWARD_TOKEN_ID) + ); + assert_eq!(out_reward_token.token_nonce, expected_reward_token_nonce); + + result = out_reward_token.amount.to_u64().unwrap(); + }, + ) + .assert_ok(); + + result + } + + pub fn whitelist_address_on_behalf(&mut self, user: &Address, address_to_whitelist: &Address) { + self.b_mock + .execute_tx( + user, + &self.permissions_hub_wrapper, + &rust_biguint!(0), + |sc| { + sc.whitelist(managed_address!(address_to_whitelist)); + }, + ) + .assert_ok(); + } + pub fn check_farm_token_supply(&mut self, expected_farm_token_supply: u64) { let b_mock = &mut self.b_mock; b_mock diff --git a/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_test.rs b/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_test.rs index 649461f18..fea8f4fcd 100644 --- a/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_test.rs +++ b/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_test.rs @@ -2,6 +2,9 @@ use common_structs::FarmTokenAttributes; use farm_with_locked_rewards::Farm; +use farm_with_locked_rewards_setup::{ + FARMING_TOKEN_ID, MAX_PERCENTAGE, PER_BLOCK_REWARD_AMOUNT, REWARD_TOKEN_ID, +}; use multiversx_sc::{codec::Empty, imports::OptionalValue}; use multiversx_sc_scenario::{managed_address, managed_biguint, rust_biguint, DebugApi}; use simple_lock::locked_token::LockedTokenAttributes; @@ -18,6 +21,7 @@ fn farm_with_no_boost_no_proxy_test() { let mut farm_setup = FarmSetup::new( farm_with_locked_rewards::contract_obj, energy_factory::contract_obj, + permissions_hub::contract_obj, ); // first user enter farm @@ -118,6 +122,7 @@ fn farm_with_boosted_yields_no_proxy_test() { let mut farm_setup = FarmSetup::new( farm_with_locked_rewards::contract_obj, energy_factory::contract_obj, + permissions_hub::contract_obj, ); farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -237,6 +242,7 @@ fn total_farm_position_claim_with_locked_rewards_test() { let mut farm_setup = FarmSetup::new( farm_with_locked_rewards::contract_obj, energy_factory::contract_obj, + permissions_hub::contract_obj, ); farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -344,6 +350,7 @@ fn claim_only_boosted_rewards_per_week_test() { let mut farm_setup = FarmSetup::new( farm_with_locked_rewards::contract_obj, energy_factory::contract_obj, + permissions_hub::contract_obj, ); farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -407,6 +414,7 @@ fn claim_rewards_per_week_test() { let mut farm_setup = FarmSetup::new( farm_with_locked_rewards::contract_obj, energy_factory::contract_obj, + permissions_hub::contract_obj, ); farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -473,6 +481,7 @@ fn claim_boosted_rewards_with_zero_position_test() { let mut farm_setup = FarmSetup::new( farm_with_locked_rewards::contract_obj, energy_factory::contract_obj, + permissions_hub::contract_obj, ); farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -544,6 +553,7 @@ fn claim_boosted_rewards_user_energy_not_registered_test() { let mut farm_setup = FarmSetup::new( farm_with_locked_rewards::contract_obj, energy_factory::contract_obj, + permissions_hub::contract_obj, ); farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -575,3 +585,127 @@ fn claim_boosted_rewards_user_energy_not_registered_test() { // Rewards computation is out of scope farm_setup.claim_boosted_rewards_for_user(&first_user, &first_user, 0); } + +#[test] +fn test_multiple_positions_on_behalf() { + DebugApi::dummy(); + + let mut farm_setup = FarmSetup::new( + farm_with_locked_rewards::contract_obj, + energy_factory::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.b_mock.set_esdt_balance( + &authorized_address, + FARMING_TOKEN_ID, + &rust_biguint!(farm_token_amount * 2), + ); + + 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, 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; + let boosted_rewards = total_rewards * BOOSTED_YIELDS_PERCENTAGE / MAX_PERCENTAGE; + + // Only base rewards are given + farm_setup + .b_mock + .check_esdt_balance(&external_user, REWARD_TOKEN_ID, &rust_biguint!(0)); + farm_setup.claim_rewards_on_behalf(&authorized_address, 1, farm_token_amount, 1); + farm_setup + .b_mock + .check_nft_balance::>( + &external_user, + LOCKED_REWARD_TOKEN_ID, + 1, + &rust_biguint!(base_rewards), + None, + ); + + // random tx on end of week 1, to cummulate rewards + farm_setup.b_mock.set_block_epoch(6); + let temp_user = farm_setup.third_user.clone(); + farm_setup.set_user_energy(&external_user, 1_000, 6, 1); + farm_setup.set_user_energy(&temp_user, 1, 6, 1); + farm_setup.last_farm_token_nonce = 2; + farm_setup.enter_farm(&temp_user, 1); + farm_setup.exit_farm(&temp_user, 3, 1); + + // advance 1 week + block_nonce += block_nonce_diff; + farm_setup.b_mock.set_block_nonce(block_nonce); + farm_setup.b_mock.set_block_epoch(10); + farm_setup.set_user_energy(&external_user, 1_000, 10, 1); + + // enter farm again for the same user (with additional payment) + farm_setup.check_farm_token_supply(farm_token_amount); + farm_setup.enter_farm_on_behalf( + &authorized_address, + &external_user, + farm_token_amount, + 2, // nonce 2 as the user already claimed with this position + farm_token_amount, + ); + farm_setup.check_farm_token_supply(farm_token_amount * 2); + farm_setup + .b_mock + .check_nft_balance::>( + &external_user, + LOCKED_REWARD_TOKEN_ID, + 1, + &rust_biguint!(base_rewards + boosted_rewards), + None, + ); + + farm_setup.claim_rewards_on_behalf(&authorized_address, 4, farm_token_amount * 2, 1); + farm_setup.check_farm_token_supply(farm_token_amount * 2); + + farm_setup + .b_mock + .check_nft_balance::>( + &external_user, + LOCKED_REWARD_TOKEN_ID, + 1, + &rust_biguint!(total_rewards + base_rewards), + None, + ); + + let farm_token_attributes: FarmTokenAttributes = FarmTokenAttributes { + reward_per_share: managed_biguint!(150_000_000u64), + entering_epoch: 10u64, + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(farm_token_amount * 2), + original_owner: managed_address!(&external_user), + }; + + farm_setup.b_mock.check_nft_balance( + &authorized_address, + FARM_TOKEN_ID, + 5, + &rust_biguint!(farm_token_amount * 2), + Some(&farm_token_attributes), + ); +} From 899188adbdef539993385a6c9fd2c79dc5b58f52 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 1 Nov 2024 13:22:17 +0200 Subject: [PATCH 07/11] farm staking tests --- .../tests/farm_staking_energy_test.rs | 203 +++++++++++++++--- .../tests/farm_staking_setup/mod.rs | 125 ++++++++++- .../farm-staking/tests/farm_staking_test.rs | 73 +++++-- 3 files changed, 353 insertions(+), 48 deletions(-) diff --git a/farm-staking/farm-staking/tests/farm_staking_energy_test.rs b/farm-staking/farm-staking/tests/farm_staking_energy_test.rs index a4046f03e..e1a885355 100644 --- a/farm-staking/farm-staking/tests/farm_staking_energy_test.rs +++ b/farm-staking/farm-staking/tests/farm_staking_energy_test.rs @@ -18,8 +18,11 @@ use multiversx_sc_scenario::{ #[test] fn farm_staking_with_energy_setup_test() { - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); fs_setup.set_boosted_yields_factors(); fs_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -28,8 +31,11 @@ fn farm_staking_with_energy_setup_test() { #[test] fn farm_staking_boosted_rewards_no_energy_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = fs_setup.user_address.clone(); @@ -74,8 +80,11 @@ fn farm_staking_boosted_rewards_no_energy_test() { #[test] fn farm_staking_other_user_enter_negative_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = fs_setup.user_address.clone(); let rand_user = fs_setup.b_mock.create_user_account(&rust_biguint!(0)); @@ -120,8 +129,11 @@ fn farm_staking_other_user_enter_negative_test() { #[test] fn farm_staking_boosted_rewards_with_energy_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = fs_setup.user_address.clone(); let user_address2 = fs_setup.user_address2.clone(); @@ -345,8 +357,11 @@ fn farm_staking_boosted_rewards_with_energy_test() { #[test] fn farm_staking_partial_position_handling_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = fs_setup.user_address.clone(); @@ -507,8 +522,11 @@ fn farm_staking_partial_position_handling_test() { #[test] fn farm_staking_claim_boosted_rewards_for_user_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = fs_setup.user_address.clone(); @@ -621,8 +639,11 @@ fn farm_staking_claim_boosted_rewards_for_user_test() { #[test] fn farm_staking_full_position_boosted_rewards_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = fs_setup.user_address.clone(); @@ -736,8 +757,11 @@ fn farm_staking_full_position_boosted_rewards_test() { #[test] fn position_owner_change_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let first_user = fs_setup.user_address.clone(); let second_user = fs_setup.user_address2.clone(); @@ -1028,8 +1052,11 @@ fn position_owner_change_test() { #[test] fn farm_staking_farm_position_migration_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user = fs_setup.user_address.clone(); @@ -1122,8 +1149,11 @@ fn farm_staking_farm_position_migration_test() { #[test] fn boosted_rewards_config_change_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let first_user = fs_setup.user_address.clone(); let second_user = fs_setup.user_address2.clone(); @@ -1432,8 +1462,11 @@ fn boosted_rewards_config_change_test() { #[test] fn claim_only_boosted_rewards_per_week_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); fs_setup.set_boosted_yields_factors(); fs_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -1527,8 +1560,11 @@ fn claim_only_boosted_rewards_per_week_test() { #[test] fn claim_rewards_per_week_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); fs_setup.set_boosted_yields_factors(); fs_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -1621,8 +1657,11 @@ fn claim_rewards_per_week_test() { #[test] fn claim_boosted_rewards_with_zero_position_test() { DebugApi::dummy(); - let mut fs_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); fs_setup.set_boosted_yields_factors(); fs_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); @@ -1717,3 +1756,115 @@ fn claim_boosted_rewards_with_zero_position_test() { Some(&expected_attributes), ); } + +#[test] +fn test_multiple_positions_on_behalf() { + DebugApi::dummy(); + + let mut fs_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); + + fs_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + fs_setup.set_boosted_yields_factors(); + let mut block_nonce = 0u64; + fs_setup.b_mock.set_block_nonce(block_nonce); + + // new external user + let external_user = fs_setup.b_mock.create_user_account(&rust_biguint!(0)); + fs_setup.set_user_energy(&external_user, 1_000, 1, 1); + + // authorized address + let farm_token_amount = 100_000_000; + let authorized_address = fs_setup.user_address.clone(); + fs_setup.b_mock.set_esdt_balance( + &authorized_address, + FARMING_TOKEN_ID, + &rust_biguint!(farm_token_amount * 2), + ); + + fs_setup.whitelist_address_on_behalf(&external_user, &authorized_address); + + fs_setup.check_farm_token_supply(0); + fs_setup.stake_farm_on_behalf(&authorized_address, &external_user, farm_token_amount, 0, 0); + fs_setup.check_farm_token_supply(farm_token_amount); + + let block_nonce_diff = 10u64; + block_nonce += block_nonce_diff; + fs_setup.b_mock.set_block_nonce(block_nonce); + + let base_rewards = 30u64; + let boosted_rewards = 10u64; + let total_rewards = base_rewards + boosted_rewards; + + // Only base rewards are given + fs_setup + .b_mock + .check_esdt_balance(&external_user, REWARD_TOKEN_ID, &rust_biguint!(0)); + fs_setup.claim_rewards_on_behalf(&authorized_address, 1, farm_token_amount); + fs_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(base_rewards), + ); + + // random tx on end of week 1, to cummulate rewards + fs_setup.b_mock.set_block_epoch(6); + let temp_user = fs_setup.b_mock.create_user_account(&rust_biguint!(0)); + fs_setup.b_mock.set_esdt_balance( + &temp_user, + FARMING_TOKEN_ID, + &rust_biguint!(USER_TOTAL_RIDE_TOKENS), + ); + fs_setup.set_user_energy(&external_user, 1_000, 6, 1); + fs_setup.set_user_energy(&temp_user, 1, 6, 1); + fs_setup.stake_farm(&temp_user, 10, &[], 3, 300_000u64, 0); + fs_setup.unstake_farm_no_checks(&temp_user, 10, 3); + + // advance 1 week + block_nonce += block_nonce_diff; + fs_setup.b_mock.set_block_nonce(block_nonce); + fs_setup.b_mock.set_block_epoch(10); + fs_setup.set_user_energy(&external_user, 1_000, 10, 1); + + // enter farm again for the same user (with additional payment) + fs_setup.check_farm_token_supply(farm_token_amount); + fs_setup.stake_farm_on_behalf( + &authorized_address, + &external_user, + farm_token_amount, + 2, // nonce 2 as the user already claimed with this position + farm_token_amount, + ); + fs_setup.check_farm_token_supply(farm_token_amount * 2); + fs_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(base_rewards + boosted_rewards), + ); + + fs_setup.claim_rewards_on_behalf(&authorized_address, 5, farm_token_amount * 2); + fs_setup.check_farm_token_supply(farm_token_amount * 2); + fs_setup.b_mock.check_esdt_balance( + &external_user, + REWARD_TOKEN_ID, + &rust_biguint!(total_rewards + base_rewards), + ); + + let farm_token_attributes: StakingFarmTokenAttributes = StakingFarmTokenAttributes { + reward_per_share: managed_biguint!(600_000u64), + compounded_reward: managed_biguint!(0), + current_farm_amount: managed_biguint!(farm_token_amount * 2), + original_owner: managed_address!(&external_user), + }; + + fs_setup.b_mock.check_nft_balance( + &authorized_address, + FARM_TOKEN_ID, + 6, + &rust_biguint!(farm_token_amount * 2), + Some(&farm_token_attributes), + ); +} diff --git a/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs b/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs index a27b2d0d0..bf732da74 100644 --- a/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs +++ b/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs @@ -1,5 +1,6 @@ #![allow(deprecated)] +use external_interaction::ExternalInteractionsModule; use farm_staking::claim_only_boosted_staking_rewards::ClaimOnlyBoostedStakingRewardsModule; use farm_staking::compound_stake_farm_rewards::CompoundStakeFarmRewardsModule; use multiversx_sc::codec::multi_types::OptionalValue; @@ -25,6 +26,7 @@ use farm_staking::unstake_farm::UnstakeFarmModule; use farm_staking::*; use farm_token::FarmTokenModule; use pausable::{PausableModule, State}; +use permissions_hub::PermissionsHub; use rewards::RewardsModule; pub static REWARD_TOKEN_ID: &[u8] = b"RIDE-abcdef"; // reward token ID @@ -38,6 +40,7 @@ pub const TOTAL_REWARDS_AMOUNT: u64 = 1_000_000_000_000; pub const USER_TOTAL_RIDE_TOKENS: u64 = 5_000_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; @@ -52,10 +55,11 @@ pub struct NonceAmountPair { pub amount: u64, } -pub struct FarmStakingSetup +pub struct FarmStakingSetup where FarmObjBuilder: 'static + Copy + Fn() -> farm_staking::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, { pub b_mock: BlockchainStateWrapper, pub owner_address: Address, @@ -64,14 +68,22 @@ where pub farm_wrapper: ContractObjWrapper, FarmObjBuilder>, pub energy_factory_wrapper: ContractObjWrapper, EnergyFactoryBuilder>, + pub permissions_hub_wrapper: + ContractObjWrapper, PermissionsHubObjBuilder>, } -impl FarmStakingSetup +impl + FarmStakingSetup where FarmObjBuilder: 'static + Copy + Fn() -> farm_staking::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, { - pub fn new(farm_builder: FarmObjBuilder, energy_factory_builder: EnergyFactoryBuilder) -> Self { + pub fn new( + farm_builder: FarmObjBuilder, + energy_factory_builder: EnergyFactoryBuilder, + permissions_hub_builder: PermissionsHubObjBuilder, + ) -> Self { let rust_zero = rust_biguint!(0u64); let mut b_mock = BlockchainStateWrapper::new(); let owner_addr = b_mock.create_user_account(&rust_zero); @@ -85,6 +97,19 @@ where "energy_factory.wasm", ); + let permissions_hub_wrapper = b_mock.create_sc_account( + &rust_zero, + Some(&owner_addr), + permissions_hub_builder, + "permissions_hub.wasm", + ); + + b_mock + .execute_tx(&owner_addr, &permissions_hub_wrapper, &rust_zero, |sc| { + sc.init(); + }) + .assert_ok(); + // init farm contract b_mock @@ -112,6 +137,10 @@ where sc.energy_factory_address() .set(managed_address!(energy_factory_wrapper.address_ref())); + + sc.set_permissions_hub_address(managed_address!( + permissions_hub_wrapper.address_ref() + )); }) .assert_ok(); @@ -167,6 +196,7 @@ where user_address2: user_addr2, farm_wrapper, energy_factory_wrapper, + permissions_hub_wrapper, } } @@ -566,6 +596,95 @@ where ); } + pub fn stake_farm_on_behalf( + &mut self, + caller: &Address, + user: &Address, + farming_token_amount: u64, + farm_token_nonce: u64, + farm_token_amount: u64, + ) { + let mut payments = Vec::new(); + payments.push(TxTokenTransfer { + token_identifier: FARMING_TOKEN_ID.to_vec(), + nonce: 0, + value: rust_biguint!(farming_token_amount), + }); + + if farm_token_nonce > 0 { + payments.push(TxTokenTransfer { + token_identifier: FARM_TOKEN_ID.to_vec(), + nonce: farm_token_nonce, + value: rust_biguint!(farm_token_amount), + }); + } + + let b_mock = &mut self.b_mock; + b_mock + .execute_esdt_multi_transfer(caller, &self.farm_wrapper, &payments, |sc| { + let stake_farm_result = sc.stake_farm_on_behalf(managed_address!(user)); + let (out_farm_token, _reward_token) = stake_farm_result.into_tuple(); + assert_eq!( + out_farm_token.token_identifier, + managed_token_id!(FARM_TOKEN_ID) + ); + assert_eq!( + out_farm_token.amount, + managed_biguint!(farming_token_amount + farm_token_amount) + ); + }) + .assert_ok(); + } + + pub fn claim_rewards_on_behalf( + &mut self, + caller: &Address, + farm_token_nonce: u64, + farm_token_amount: u64, + ) -> u64 { + let mut result = 0; + self.b_mock + .execute_esdt_transfer( + caller, + &self.farm_wrapper, + FARM_TOKEN_ID, + farm_token_nonce, + &rust_biguint!(farm_token_amount), + |sc| { + let (out_farm_token, out_reward_token) = + sc.claim_rewards_on_behalf().into_tuple(); + assert_eq!( + out_farm_token.token_identifier, + managed_token_id!(FARM_TOKEN_ID) + ); + assert_eq!(out_farm_token.amount, managed_biguint!(farm_token_amount)); + + assert_eq!( + out_reward_token.token_identifier, + managed_token_id!(REWARD_TOKEN_ID) + ); + + result = out_reward_token.amount.to_u64().unwrap(); + }, + ) + .assert_ok(); + + result + } + + pub fn whitelist_address_on_behalf(&mut self, user: &Address, address_to_whitelist: &Address) { + self.b_mock + .execute_tx( + user, + &self.permissions_hub_wrapper, + &rust_biguint!(0), + |sc| { + sc.whitelist(managed_address!(address_to_whitelist)); + }, + ) + .assert_ok(); + } + pub fn check_farm_token_supply(&mut self, expected_farm_token_supply: u64) { self.b_mock .execute_query(&self.farm_wrapper, |sc| { diff --git a/farm-staking/farm-staking/tests/farm_staking_test.rs b/farm-staking/farm-staking/tests/farm_staking_test.rs index 26a9d065b..24ab2bef7 100644 --- a/farm-staking/farm-staking/tests/farm_staking_test.rs +++ b/farm-staking/farm-staking/tests/farm_staking_test.rs @@ -11,14 +11,21 @@ use farm_staking_setup::*; #[test] fn test_farm_setup() { - let _ = FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let _ = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); } #[test] fn test_enter_farm() { DebugApi::dummy(); - let mut farm_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut farm_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = farm_setup.user_address.clone(); @@ -38,8 +45,11 @@ fn test_enter_farm() { #[test] fn test_unstake_farm() { DebugApi::dummy(); - let mut farm_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut farm_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = farm_setup.user_address.clone(); @@ -90,8 +100,11 @@ fn test_unstake_farm() { #[test] fn test_claim_rewards() { DebugApi::dummy(); - let mut farm_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut farm_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = farm_setup.user_address.clone(); @@ -128,15 +141,21 @@ fn test_claim_rewards() { farm_setup.check_farm_token_supply(farm_in_amount); } -fn steps_enter_farm_twice( +fn steps_enter_farm_twice( farm_builder: FarmObjBuilder, energy_factory_builder: EnergyFactoryBuilder, -) -> FarmStakingSetup + permissions_hub_builder: PermissionsHubObjBuilder, +) -> FarmStakingSetup where FarmObjBuilder: 'static + Copy + Fn() -> farm_staking::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, { - let mut farm_setup = FarmStakingSetup::new(farm_builder, energy_factory_builder); + let mut farm_setup = FarmStakingSetup::new( + farm_builder, + energy_factory_builder, + permissions_hub_builder, + ); let user_address = farm_setup.user_address.clone(); @@ -187,14 +206,21 @@ where #[test] fn test_enter_farm_twice() { DebugApi::dummy(); - let _ = steps_enter_farm_twice(farm_staking::contract_obj, energy_factory::contract_obj); + let _ = steps_enter_farm_twice( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); } #[test] fn test_exit_farm_after_enter_twice() { DebugApi::dummy(); - let mut farm_setup = - steps_enter_farm_twice(farm_staking::contract_obj, energy_factory::contract_obj); + let mut farm_setup = steps_enter_farm_twice( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = farm_setup.user_address.clone(); @@ -227,8 +253,11 @@ fn test_exit_farm_after_enter_twice() { #[test] fn test_unbond() { DebugApi::dummy(); - let mut farm_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut farm_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = farm_setup.user_address.clone(); @@ -288,8 +317,11 @@ fn test_unbond() { #[test] fn test_withdraw_rewards() { DebugApi::dummy(); - let mut farm_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut farm_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let initial_rewards_capacity = 1_000_000_000_000u64; farm_setup.check_rewards_capacity(initial_rewards_capacity); @@ -304,8 +336,11 @@ fn test_withdraw_rewards() { #[test] fn test_withdraw_after_produced_rewards() { DebugApi::dummy(); - let mut farm_setup = - FarmStakingSetup::new(farm_staking::contract_obj, energy_factory::contract_obj); + let mut farm_setup = FarmStakingSetup::new( + farm_staking::contract_obj, + energy_factory::contract_obj, + permissions_hub::contract_obj, + ); let user_address = farm_setup.user_address.clone(); From dcc9e99170f543fa14dc3ca101b42f0382183140 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 1 Nov 2024 13:23:22 +0200 Subject: [PATCH 08/11] farm staking proxy on behalf features + unit test --- farm-staking/farm-staking-proxy/Cargo.toml | 3 + farm-staking/farm-staking-proxy/src/lib.rs | 1 + .../src/proxy_actions/external_interaction.rs | 243 ++++++++++++++++++ .../src/proxy_actions/mod.rs | 1 + .../tests/staking_farm_with_lp.rs | 183 +++++++++++++ .../mod.rs | 196 +++++++++++++- .../farm-staking-proxy/wasm/Cargo.lock | 11 + .../farm-staking-proxy/wasm/src/lib.rs | 7 +- 8 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs diff --git a/farm-staking/farm-staking-proxy/Cargo.toml b/farm-staking/farm-staking-proxy/Cargo.toml index 34ee079f9..0da624de0 100644 --- a/farm-staking/farm-staking-proxy/Cargo.toml +++ b/farm-staking/farm-staking-proxy/Cargo.toml @@ -63,6 +63,9 @@ path = "../../common/modules/sc_whitelist_module" [dependencies.energy-query] path = "../../energy-integration/common-modules/energy-query" +[dependencies.permissions-hub] +path = "../../dex/permissions-hub" + [dev-dependencies] num-bigint = "0.4.2" diff --git a/farm-staking/farm-staking-proxy/src/lib.rs b/farm-staking/farm-staking-proxy/src/lib.rs index 7bfc66bab..4840bd965 100644 --- a/farm-staking/farm-staking-proxy/src/lib.rs +++ b/farm-staking/farm-staking-proxy/src/lib.rs @@ -21,6 +21,7 @@ pub trait FarmStakingProxy: + proxy_actions::stake::ProxyStakeModule + proxy_actions::claim::ProxyClaimModule + proxy_actions::unstake::ProxyUnstakeModule + + proxy_actions::external_interaction::ProxyExternalInteractionsModule { #[init] fn init( 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 new file mode 100644 index 000000000..1f1f4ea0e --- /dev/null +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs @@ -0,0 +1,243 @@ +use common_structs::FarmTokenAttributes; + +use crate::{ + dual_yield_token::DualYieldTokenAttributes, + result_types::{ClaimDualYieldResult, StakeProxyResult}, +}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ProxyExternalInteractionsModule: + crate::dual_yield_token::DualYieldTokenModule + + crate::external_contracts_interactions::ExternalContractsInteractionsModule + + crate::lp_farm_token::LpFarmTokenModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + utils::UtilsModule + + token_send::TokenSendModule + + energy_query::EnergyQueryModule + + sc_whitelist_module::SCWhitelistModule +{ + #[payable("*")] + #[endpoint(stakeFarmOnBehalf)] + fn stake_farm_on_behalf(&self, original_owner: ManagedAddress) -> StakeProxyResult { + let caller = self.blockchain().get_caller(); + self.require_user_whitelisted(&original_owner, &caller); + + let payments = self.get_non_empty_payments(); + let lp_farm_token_payment = payments.get(0); + let additional_payments = payments.slice(1, payments.len()).unwrap_or_default(); + + self.check_stake_farm_payments( + &original_owner, + &lp_farm_token_payment, + &additional_payments, + ); + let lp_farm_token_id = self.lp_farm_token_id().get(); + require!( + lp_farm_token_payment.token_identifier == lp_farm_token_id, + "Invalid first payment" + ); + + let dual_yield_token_mapper = self.dual_yield_token(); + let staking_farm_token_id = self.staking_farm_token_id().get(); + let mut additional_staking_farm_tokens = ManagedVec::new(); + let mut additional_lp_farm_tokens = ManagedVec::new(); + for p in &additional_payments { + let attributes: DualYieldTokenAttributes = + self.get_attributes_as_part_of_fixed_supply(&p, &dual_yield_token_mapper); + + additional_staking_farm_tokens.push(EsdtTokenPayment::new( + staking_farm_token_id.clone(), + attributes.staking_farm_token_nonce, + attributes.staking_farm_token_amount, + )); + + additional_lp_farm_tokens.push(EsdtTokenPayment::new( + lp_farm_token_payment.token_identifier.clone(), + attributes.lp_farm_token_nonce, + attributes.lp_farm_token_amount, + )); + + dual_yield_token_mapper.nft_burn(p.token_nonce, &p.amount); + } + + let lp_tokens_in_farm = self.get_lp_tokens_in_farm_position( + lp_farm_token_payment.token_nonce, + &lp_farm_token_payment.amount, + ); + let staking_token_amount = self.get_lp_tokens_safe_price(lp_tokens_in_farm); + let staking_farm_enter_result = self.staking_farm_enter( + original_owner.clone(), + staking_token_amount, + additional_staking_farm_tokens, + ); + let received_staking_farm_token = staking_farm_enter_result.received_staking_farm_token; + + let (merged_lp_farm_tokens, lp_farm_boosted_rewards) = self + .merge_lp_farm_tokens( + original_owner.clone(), + lp_farm_token_payment, + additional_lp_farm_tokens, + ) + .into_tuple(); + + let new_attributes = DualYieldTokenAttributes { + lp_farm_token_nonce: merged_lp_farm_tokens.token_nonce, + lp_farm_token_amount: merged_lp_farm_tokens.amount, + staking_farm_token_nonce: received_staking_farm_token.token_nonce, + staking_farm_token_amount: received_staking_farm_token.amount, + }; + let new_dual_yield_tokens = + self.create_dual_yield_tokens(&dual_yield_token_mapper, &new_attributes); + let output_payments = StakeProxyResult { + dual_yield_tokens: new_dual_yield_tokens, + staking_boosted_rewards: staking_farm_enter_result.boosted_rewards, + lp_farm_boosted_rewards, + }; + + self.send_payment_non_zero(&original_owner, &output_payments.lp_farm_boosted_rewards); + self.send_payment_non_zero(&original_owner, &output_payments.staking_boosted_rewards); + self.send_payment_non_zero(&caller, &output_payments.dual_yield_tokens); + + output_payments + } + + #[payable("*")] + #[endpoint(claimDualYieldOnBehalf)] + fn claim_dual_yield_on_behalf(&self) -> ClaimDualYieldResult { + let caller = self.blockchain().get_caller(); + + let payment = self.call_value().single_esdt(); + let dual_yield_token_mapper = self.dual_yield_token(); + dual_yield_token_mapper.require_same_token(&payment.token_identifier); + + let attributes: DualYieldTokenAttributes = + self.get_attributes_as_part_of_fixed_supply(&payment, &dual_yield_token_mapper); + + let original_owner = self.get_farm_position_original_owner(attributes.lp_farm_token_nonce); + self.require_user_whitelisted(&original_owner, &caller); + + let lp_tokens_in_position = self.get_lp_tokens_in_farm_position( + attributes.lp_farm_token_nonce, + &attributes.lp_farm_token_amount, + ); + let new_staking_farm_value = self.get_lp_tokens_safe_price(lp_tokens_in_position); + + let staking_farm_token_id = self.staking_farm_token_id().get(); + let lp_farm_token_id = self.lp_farm_token_id().get(); + let lp_farm_claim_rewards_result = self.lp_farm_claim_rewards( + original_owner.clone(), + lp_farm_token_id, + attributes.lp_farm_token_nonce, + attributes.lp_farm_token_amount, + ); + let staking_farm_claim_rewards_result = self.staking_farm_claim_rewards( + original_owner.clone(), + staking_farm_token_id, + attributes.staking_farm_token_nonce, + attributes.staking_farm_token_amount, + new_staking_farm_value, + ); + + let new_lp_farm_tokens = lp_farm_claim_rewards_result.new_lp_farm_tokens; + let new_staking_farm_tokens = staking_farm_claim_rewards_result.new_staking_farm_tokens; + let new_attributes = DualYieldTokenAttributes { + lp_farm_token_nonce: new_lp_farm_tokens.token_nonce, + lp_farm_token_amount: new_lp_farm_tokens.amount, + staking_farm_token_nonce: new_staking_farm_tokens.token_nonce, + staking_farm_token_amount: new_staking_farm_tokens.amount, + }; + + let lp_farm_rewards = lp_farm_claim_rewards_result.lp_farm_rewards; + let staking_farm_rewards = staking_farm_claim_rewards_result.staking_farm_rewards; + let new_dual_yield_attributes = new_attributes; + + let new_dual_yield_tokens = + self.create_dual_yield_tokens(&dual_yield_token_mapper, &new_dual_yield_attributes); + let claim_result = ClaimDualYieldResult { + lp_farm_rewards, + staking_farm_rewards, + new_dual_yield_tokens, + }; + + self.send_payment_non_zero(&original_owner, &claim_result.lp_farm_rewards); + self.send_payment_non_zero(&original_owner, &claim_result.staking_farm_rewards); + self.send_payment_non_zero(&caller, &claim_result.new_dual_yield_tokens); + + dual_yield_token_mapper.nft_burn(payment.token_nonce, &payment.amount); + + claim_result + } + + fn check_stake_farm_payments( + &self, + original_owner: &ManagedAddress, + first_payment: &EsdtTokenPayment, + additional_payments: &ManagedVec, + ) { + let dual_yield_token_mapper = self.dual_yield_token(); + let dual_yield_token_id = dual_yield_token_mapper.get_token_id(); + let lp_farm_token_id = self.lp_farm_token_id().get(); + + require!( + first_payment.token_identifier == lp_farm_token_id, + "Invalid first payment" + ); + require!( + &self.get_farm_position_original_owner(first_payment.token_nonce) == original_owner, + "Provided address is not the same as the original owner" + ); + + for payment in additional_payments.into_iter() { + if payment.token_identifier != dual_yield_token_id { + sc_panic!("Wrong additional payments"); + } + + let attributes: DualYieldTokenAttributes = + dual_yield_token_mapper.get_token_attributes(payment.token_nonce); + require!( + &self.get_farm_position_original_owner(attributes.lp_farm_token_nonce) + == original_owner, + "Provided address is not the same as the original owner" + ); + } + } + + fn get_farm_position_original_owner(&self, farm_token_nonce: u64) -> ManagedAddress { + let lp_farm_token_id = self.lp_farm_token_id().get(); + let attributes = self + .blockchain() + .get_token_attributes::>( + &lp_farm_token_id, + farm_token_nonce, + ); + + attributes.original_owner + } + + fn require_user_whitelisted(&self, user: &ManagedAddress, authorized_address: &ManagedAddress) { + let permissions_hub_address = self.permissions_hub_address().get(); + let is_whitelisted: bool = self + .permissions_hub_proxy(permissions_hub_address) + .is_whitelisted(user, authorized_address) + .execute_on_dest_context(); + + require!(is_whitelisted, "Caller is not whitelisted by the user"); + } + + #[only_owner] + #[endpoint(setPermissionsHubAddress)] + fn set_permissions_hub_address(&self, address: ManagedAddress) { + self.permissions_hub_address().set(&address); + } + + #[proxy] + fn permissions_hub_proxy( + &self, + sc_address: ManagedAddress, + ) -> permissions_hub::Proxy; + + #[storage_mapper("permissionsHubAddress")] + fn permissions_hub_address(&self) -> SingleValueMapper; +} diff --git a/farm-staking/farm-staking-proxy/src/proxy_actions/mod.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/mod.rs index 3270b0f79..88511f2ac 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/mod.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/mod.rs @@ -1,3 +1,4 @@ pub mod claim; +pub mod external_interaction; pub mod stake; pub mod unstake; 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 50b62cb93..9a790bb19 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 @@ -31,6 +31,7 @@ fn test_all_setup() { 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, ); @@ -42,6 +43,7 @@ fn test_stake_farm_proxy() { 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, ); @@ -57,6 +59,7 @@ fn test_claim_rewards_farm_proxy_full() { 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, ); @@ -85,6 +88,7 @@ fn test_claim_rewards_farm_proxy_half() { 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, ); @@ -113,6 +117,7 @@ fn test_claim_rewards_farm_proxy_twice() { 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, ); @@ -156,6 +161,7 @@ fn test_unstake_through_proxy_no_claim() { 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, ); @@ -187,6 +193,7 @@ fn unstake_through_proxy_after_claim() { 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, ); @@ -228,6 +235,7 @@ fn unstake_partial_position_test() { 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, ); @@ -344,6 +352,7 @@ fn unbond_test() { 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, ); @@ -389,6 +398,7 @@ fn farm_staking_compound_rewards_and_unstake_test() { 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, ); @@ -420,6 +430,7 @@ fn test_stake_farm_through_proxy_with_merging() { 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, ); @@ -496,6 +507,7 @@ fn test_farm_stake_proxy_merging_boosted_rewards() { 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, ); @@ -641,6 +653,7 @@ fn original_caller_negative_test() { 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, ); @@ -683,6 +696,7 @@ fn claim_for_others_positive_test() { 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, ); @@ -879,6 +893,7 @@ fn stake_farm_through_proxy_migration_test() { 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, ); @@ -1013,6 +1028,7 @@ fn total_farm_position_after_claim_and_exit_metastaking_test() { 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, ); @@ -1216,3 +1232,170 @@ fn total_farm_position_after_claim_and_exit_metastaking_test() { // Total farm position should be 0 after full unstake setup.check_user_total_staking_farm_position(&user_address, 0); } + +#[test] +fn test_multiple_positions_on_behalf() { + 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)); + let temp_user = setup + .b_mock + .create_user_account(&rust_biguint!(farm_amount)); + 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)); + setup + .b_mock + .set_esdt_balance(&temp_user, LP_TOKEN_ID, &rust_biguint!(1)); + + let mut 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, + ); + setup.check_user_total_staking_farm_position(&user_address, farm_amount); + + let block_nonce_diff = 100; + block_nonce += block_nonce_diff; + + setup.b_mock.set_block_nonce(block_nonce); + + // Only base rewards are given + setup + .b_mock + .check_esdt_balance(&user_address, STAKING_REWARD_TOKEN_ID, &rust_biguint!(0)); + setup.claim_rewards_on_behalf(&authorized_address, 1, farm_amount); + setup.b_mock.check_esdt_balance( + &user_address, + STAKING_REWARD_TOKEN_ID, + &rust_biguint!(14u64), + ); + + // User total farm position should still be the same + setup.check_user_total_staking_farm_position(&user_address, farm_amount); + + // random tx on end of week 1, to cummulate rewards + setup.b_mock.set_block_epoch(6); + setup.set_user_energy(&user_address, 1_000, 6, 1); + setup.set_user_energy(&temp_user, 1, 6, 1); + let temp_user_farm_token_nonce = setup.enter_lp_farm(&temp_user, 1); + setup.exit_lp_farm(&temp_user, temp_user_farm_token_nonce, 1); + + // advance 1 week + block_nonce += block_nonce_diff; + setup.b_mock.set_block_nonce(block_nonce); + setup.b_mock.set_block_epoch(10); + setup.set_user_energy(&user_address, 1_000, 10, 1); + + // enter farm again for the same user (with additional payment) + setup.stake_farm_on_behalf( + &authorized_address, + &user_address, + farm_token_nonce, + farm_amount, + 2, // nonce 2 as the user already claimed with this position + farm_amount, + 3, + farm_amount * 2, + ); + setup.b_mock.check_esdt_balance( + &user_address, + STAKING_REWARD_TOKEN_ID, + &rust_biguint!(14u64 + 4u64), + ); + + setup.check_user_total_staking_farm_position(&user_address, farm_amount * 2); + setup.claim_rewards_on_behalf(&authorized_address, 3, farm_amount * 2); + setup.check_user_total_staking_farm_position(&user_address, farm_amount * 2); + + // Check reward token balances + setup.b_mock.check_esdt_balance( + &user_address, + STAKING_REWARD_TOKEN_ID, + &rust_biguint!(693), // actual amount computation out of scope for this unit test + ); + setup.b_mock.check_esdt_balance( + &authorized_address, + STAKING_REWARD_TOKEN_ID, + &rust_biguint!(0), // should always be 0 + ); + + let dual_yield_token_attributes: DualYieldTokenAttributes = + DualYieldTokenAttributes { + lp_farm_token_nonce: 6, + lp_farm_token_amount: managed_biguint!(farm_amount * 2u64), + staking_farm_token_nonce: 4, + staking_farm_token_amount: managed_biguint!(farm_amount * 2u64), + }; + + setup.b_mock.check_nft_balance( + &authorized_address, + DUAL_YIELD_TOKEN_ID, + 4, + &rust_biguint!(farm_amount * 2u64), + Some(&dual_yield_token_attributes), + ); +} diff --git a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs index aba5f5713..713d43a3b 100644 --- a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs +++ b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs @@ -1,5 +1,6 @@ #![allow(deprecated)] +use common_structs::FarmTokenAttributes; use config::ConfigModule; use energy_factory::energy::EnergyModule; use energy_query::Energy; @@ -21,12 +22,16 @@ use farm_staking::{ token_attributes::UnbondSftAttributes, unbond_farm::UnbondFarmModule, unstake_farm::UnstakeFarmModule, }; -use farm_staking_proxy::dual_yield_token::DualYieldTokenAttributes; use farm_staking_proxy::proxy_actions::claim::ProxyClaimModule; +use farm_staking_proxy::{ + dual_yield_token::DualYieldTokenAttributes, + proxy_actions::external_interaction::ProxyExternalInteractionsModule, +}; use farm_staking_proxy::proxy_actions::stake::ProxyStakeModule; use farm_staking_proxy::proxy_actions::unstake::ProxyUnstakeModule; +use permissions_hub::PermissionsHub; use sc_whitelist_module::SCWhitelistModule; use crate::{ @@ -46,12 +51,14 @@ pub struct FarmStakingSetup< PairObjBuilder, FarmObjBuilder, EnergyFactoryBuilder, + PermissionsHubObjBuilder, StakingContractObjBuilder, ProxyContractObjBuilder, > where PairObjBuilder: 'static + Copy + Fn() -> pair::ContractObj, FarmObjBuilder: 'static + Copy + Fn() -> farm_with_locked_rewards::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, StakingContractObjBuilder: 'static + Copy + Fn() -> farm_staking::ContractObj, ProxyContractObjBuilder: 'static + Copy + Fn() -> farm_staking_proxy::ContractObj, { @@ -63,6 +70,8 @@ pub struct FarmStakingSetup< ContractObjWrapper, FarmObjBuilder>, pub energy_factory_wrapper: ContractObjWrapper, EnergyFactoryBuilder>, + pub permissions_hub_wrapper: + ContractObjWrapper, PermissionsHubObjBuilder>, pub staking_farm_wrapper: ContractObjWrapper, StakingContractObjBuilder>, pub proxy_wrapper: @@ -73,6 +82,7 @@ impl< PairObjBuilder, FarmObjBuilder, EnergyFactoryBuilder, + PermissionsHubObjBuilder, StakingContractObjBuilder, ProxyContractObjBuilder, > @@ -80,6 +90,7 @@ impl< PairObjBuilder, FarmObjBuilder, EnergyFactoryBuilder, + PermissionsHubObjBuilder, StakingContractObjBuilder, ProxyContractObjBuilder, > @@ -87,6 +98,7 @@ where PairObjBuilder: 'static + Copy + Fn() -> pair::ContractObj, FarmObjBuilder: 'static + Copy + Fn() -> farm_with_locked_rewards::ContractObj, EnergyFactoryBuilder: 'static + Copy + Fn() -> energy_factory::ContractObj, + PermissionsHubObjBuilder: 'static + Copy + Fn() -> permissions_hub::ContractObj, StakingContractObjBuilder: 'static + Copy + Fn() -> farm_staking::ContractObj, ProxyContractObjBuilder: 'static + Copy + Fn() -> farm_staking_proxy::ContractObj, { @@ -94,6 +106,7 @@ where pair_builder: PairObjBuilder, lp_farm_builder: FarmObjBuilder, energy_factory_builder: EnergyFactoryBuilder, + permissions_hub_builder: PermissionsHubObjBuilder, staking_farm_builder: StakingContractObjBuilder, proxy_builder: ProxyContractObjBuilder, ) -> Self { @@ -135,6 +148,27 @@ where &staking_farm_wrapper, ); + let permissions_hub_wrapper = b_mock.create_sc_account( + &rust_zero, + Some(&owner_addr), + permissions_hub_builder, + "permissions_hub.wasm", + ); + + b_mock + .execute_tx(&owner_addr, &proxy_wrapper, &rust_zero, |sc| { + sc.set_permissions_hub_address(managed_address!( + permissions_hub_wrapper.address_ref() + )); + }) + .assert_ok(); + + b_mock + .execute_tx(&owner_addr, &permissions_hub_wrapper, &rust_zero, |sc| { + sc.init(); + }) + .assert_ok(); + b_mock .execute_tx(&owner_addr, &lp_farm_wrapper, &rust_zero, |sc| { sc.add_sc_address_to_whitelist(managed_address!(proxy_wrapper.address_ref())); @@ -153,6 +187,7 @@ where pair_wrapper, lp_farm_wrapper, energy_factory_wrapper, + permissions_hub_wrapper, staking_farm_wrapper, proxy_wrapper, } @@ -603,6 +638,81 @@ where ) } + pub fn stake_farm_on_behalf( + &mut self, + caller: &Address, + user: &Address, + lp_farm_token_nonce: u64, + lp_farm_token_amount: u64, + additional_dual_yield_token_nonce: u64, + additional_dual_yield_token_amount: u64, + expected_dual_yield_token_nonce: u64, + expected_dual_yield_token_amount: u64, + ) { + let mut payments = Vec::new(); + payments.push(TxTokenTransfer { + token_identifier: LP_FARM_TOKEN_ID.to_vec(), + nonce: lp_farm_token_nonce, + value: rust_biguint!(lp_farm_token_amount), + }); + + if additional_dual_yield_token_nonce > 0 { + payments.push(TxTokenTransfer { + token_identifier: DUAL_YIELD_TOKEN_ID.to_vec(), + nonce: additional_dual_yield_token_nonce, + value: rust_biguint!(additional_dual_yield_token_amount), + }); + } + + let b_mock = &mut self.b_mock; + b_mock + .execute_esdt_multi_transfer(caller, &self.proxy_wrapper, &payments, |sc| { + let stake_farm_result = sc.stake_farm_on_behalf(managed_address!(user)); + assert_eq!( + stake_farm_result.dual_yield_tokens.token_nonce, + expected_dual_yield_token_nonce + ); + assert_eq!( + stake_farm_result.dual_yield_tokens.amount, + managed_biguint!(expected_dual_yield_token_amount) + ); + }) + .assert_ok(); + } + + pub fn claim_rewards_on_behalf( + &mut self, + caller: &Address, + dual_yield_token_nonce: u64, + dual_yield_token_amount: u64, + ) { + self.b_mock + .execute_esdt_transfer( + caller, + &self.proxy_wrapper, + DUAL_YIELD_TOKEN_ID, + dual_yield_token_nonce, + &rust_biguint!(dual_yield_token_amount), + |sc| { + let _claim_dual_yield_result = sc.claim_dual_yield_on_behalf(); + }, + ) + .assert_ok(); + } + + pub fn whitelist_address_on_behalf(&mut self, user: &Address, address_to_whitelist: &Address) { + self.b_mock + .execute_tx( + user, + &self.permissions_hub_wrapper, + &rust_biguint!(0), + |sc| { + sc.whitelist(managed_address!(address_to_whitelist)); + }, + ) + .assert_ok(); + } + pub fn set_user_energy( &mut self, user: &Address, @@ -700,6 +810,90 @@ where .assert_ok(); } + 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, + LP_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, + LP_FARM_TOKEN_ID, + nonce, + &rust_biguint!(0), + None, + ); + + self.b_mock.set_nft_balance( + sender, + LP_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, + LP_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, + LP_FARM_TOKEN_ID, + nonce, + &rust_biguint!(0), + None, + ); + + self.b_mock.check_nft_balance( + receiver, + LP_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), + }), + ); + } + pub fn check_user_total_staking_farm_position( &mut self, user_addr: &Address, diff --git a/farm-staking/farm-staking-proxy/wasm/Cargo.lock b/farm-staking/farm-staking-proxy/wasm/Cargo.lock index 1f79bbe16..7fe70065c 100644 --- a/farm-staking/farm-staking-proxy/wasm/Cargo.lock +++ b/farm-staking/farm-staking-proxy/wasm/Cargo.lock @@ -138,6 +138,7 @@ dependencies = [ "multiversx-sc-modules", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -183,6 +184,7 @@ dependencies = [ "multiversx-sc-modules", "pair", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -209,6 +211,7 @@ dependencies = [ "multiversx-sc-modules", "pair", "pausable", + "permissions-hub", "rewards", "sc_whitelist_module", "token_send", @@ -245,6 +248,7 @@ dependencies = [ "multiversx-sc", "multiversx-sc-modules", "pausable", + "permissions-hub", "permissions_module", "rewards", "sc_whitelist_module", @@ -484,6 +488,13 @@ dependencies = [ "permissions_module", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + [[package]] name = "permissions_module" version = "0.0.0" diff --git a/farm-staking/farm-staking-proxy/wasm/src/lib.rs b/farm-staking/farm-staking-proxy/wasm/src/lib.rs index 3f9abd0a4..3632ff09e 100644 --- a/farm-staking/farm-staking-proxy/wasm/src/lib.rs +++ b/farm-staking/farm-staking-proxy/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 17 +// Endpoints: 20 // Async Callback: 1 -// Total number of exported functions: 20 +// Total number of exported functions: 23 #![no_std] @@ -37,6 +37,9 @@ multiversx_sc_wasm_adapter::endpoints! { stakeFarmTokens => stake_farm_tokens claimDualYield => claim_dual_yield_endpoint unstakeFarmTokens => unstake_farm_tokens + stakeFarmOnBehalf => stake_farm_on_behalf + claimDualYieldOnBehalf => claim_dual_yield_on_behalf + setPermissionsHubAddress => set_permissions_hub_address ) } From e825a19b082668b4c0a037fe9c76583f51ac2ed7 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 1 Nov 2024 13:27:38 +0200 Subject: [PATCH 09/11] clippy fix --- .../staking_farm_with_lp_staking_contract_interactions/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs index 713d43a3b..b1a056345 100644 --- a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs +++ b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_interactions/mod.rs @@ -638,6 +638,7 @@ where ) } + #[allow(clippy::too_many_arguments)] pub fn stake_farm_on_behalf( &mut self, caller: &Address, From 5ef4b70685c2a400fda9837de765fd270eadc3ce Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Sun, 3 Nov 2024 02:28:40 +0200 Subject: [PATCH 10/11] code refactor --- .../src/proxy_actions/claim.rs | 65 +++---- .../src/proxy_actions/external_interaction.rs | 180 ++++-------------- .../src/proxy_actions/stake.rs | 17 +- .../farm-staking/src/external_interaction.rs | 38 ++-- 4 files changed, 96 insertions(+), 204 deletions(-) diff --git a/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs index 01df77c36..d97eb263a 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs @@ -25,43 +25,26 @@ pub trait ProxyClaimModule: &self, opt_orig_caller: OptionalValue, ) -> ClaimDualYieldResult { - let payment = self.call_value().single_esdt(); - let dual_yield_token_mapper = self.dual_yield_token(); - dual_yield_token_mapper.require_same_token(&payment.token_identifier); - let caller = self.blockchain().get_caller(); - let attributes: DualYieldTokenAttributes = - self.get_attributes_as_part_of_fixed_supply(&payment, &dual_yield_token_mapper); - let internal_claim_result = self.claim_dual_yield( - &caller, - opt_orig_caller, - attributes.staking_farm_token_amount.clone(), - attributes, - ); + let orig_caller = self.get_orig_caller_from_opt(&caller, opt_orig_caller); - let new_dual_yield_tokens = self.create_dual_yield_tokens( - &dual_yield_token_mapper, - &internal_claim_result.new_dual_yield_attributes, - ); - let claim_result = ClaimDualYieldResult { - lp_farm_rewards: internal_claim_result.lp_farm_rewards, - staking_farm_rewards: internal_claim_result.staking_farm_rewards, - new_dual_yield_tokens, - }; + let payment = self.call_value().single_esdt(); - dual_yield_token_mapper.nft_burn(payment.token_nonce, &payment.amount); + let claim_result = self.claim_dual_yield_common(orig_caller, &payment); claim_result.send_and_return(self, &caller) } - fn claim_dual_yield( + fn claim_dual_yield_common( &self, - caller: &ManagedAddress, - opt_orig_caller: OptionalValue, - staking_claim_amount: BigUint, - attributes: DualYieldTokenAttributes, - ) -> InternalClaimResult { - let orig_caller = self.get_orig_caller_from_opt(caller, opt_orig_caller); + orig_caller: ManagedAddress, + payment: &EsdtTokenPayment, + ) -> ClaimDualYieldResult { + let dual_yield_token_mapper = self.dual_yield_token(); + dual_yield_token_mapper.require_same_token(&payment.token_identifier); + + let attributes: DualYieldTokenAttributes = + self.get_attributes_as_part_of_fixed_supply(&payment, &dual_yield_token_mapper); let lp_tokens_in_position = self.get_lp_tokens_in_farm_position( attributes.lp_farm_token_nonce, @@ -81,23 +64,33 @@ pub trait ProxyClaimModule: orig_caller, staking_farm_token_id, attributes.staking_farm_token_nonce, - staking_claim_amount, + attributes.staking_farm_token_amount, new_staking_farm_value, ); let new_lp_farm_tokens = lp_farm_claim_rewards_result.new_lp_farm_tokens; let new_staking_farm_tokens = staking_farm_claim_rewards_result.new_staking_farm_tokens; - let new_attributes = DualYieldTokenAttributes { + let new_dual_yield_attributes = DualYieldTokenAttributes { lp_farm_token_nonce: new_lp_farm_tokens.token_nonce, lp_farm_token_amount: new_lp_farm_tokens.amount, staking_farm_token_nonce: new_staking_farm_tokens.token_nonce, staking_farm_token_amount: new_staking_farm_tokens.amount, }; - InternalClaimResult { - lp_farm_rewards: lp_farm_claim_rewards_result.lp_farm_rewards, - staking_farm_rewards: staking_farm_claim_rewards_result.staking_farm_rewards, - new_dual_yield_attributes: new_attributes, - } + let new_dual_yield_tokens = + self.create_dual_yield_tokens(&dual_yield_token_mapper, &new_dual_yield_attributes); + + let lp_farm_rewards = lp_farm_claim_rewards_result.lp_farm_rewards; + let staking_farm_rewards = staking_farm_claim_rewards_result.staking_farm_rewards; + + let claim_result = ClaimDualYieldResult { + lp_farm_rewards, + staking_farm_rewards, + new_dual_yield_tokens, + }; + + dual_yield_token_mapper.nft_burn(payment.token_nonce, &payment.amount); + + claim_result } } 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 1f1f4ea0e..218a42036 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,3 +1,5 @@ +multiversx_sc::imports!(); + use common_structs::FarmTokenAttributes; use crate::{ @@ -5,13 +7,13 @@ use crate::{ result_types::{ClaimDualYieldResult, StakeProxyResult}, }; -multiversx_sc::imports!(); - #[multiversx_sc::module] pub trait ProxyExternalInteractionsModule: crate::dual_yield_token::DualYieldTokenModule + crate::external_contracts_interactions::ExternalContractsInteractionsModule + crate::lp_farm_token::LpFarmTokenModule + + crate::proxy_actions::stake::ProxyStakeModule + + crate::proxy_actions::claim::ProxyClaimModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + utils::UtilsModule + token_send::TokenSendModule @@ -25,76 +27,9 @@ pub trait ProxyExternalInteractionsModule: self.require_user_whitelisted(&original_owner, &caller); let payments = self.get_non_empty_payments(); - let lp_farm_token_payment = payments.get(0); - let additional_payments = payments.slice(1, payments.len()).unwrap_or_default(); - - self.check_stake_farm_payments( - &original_owner, - &lp_farm_token_payment, - &additional_payments, - ); - let lp_farm_token_id = self.lp_farm_token_id().get(); - require!( - lp_farm_token_payment.token_identifier == lp_farm_token_id, - "Invalid first payment" - ); - - let dual_yield_token_mapper = self.dual_yield_token(); - let staking_farm_token_id = self.staking_farm_token_id().get(); - let mut additional_staking_farm_tokens = ManagedVec::new(); - let mut additional_lp_farm_tokens = ManagedVec::new(); - for p in &additional_payments { - let attributes: DualYieldTokenAttributes = - self.get_attributes_as_part_of_fixed_supply(&p, &dual_yield_token_mapper); - - additional_staking_farm_tokens.push(EsdtTokenPayment::new( - staking_farm_token_id.clone(), - attributes.staking_farm_token_nonce, - attributes.staking_farm_token_amount, - )); - - additional_lp_farm_tokens.push(EsdtTokenPayment::new( - lp_farm_token_payment.token_identifier.clone(), - attributes.lp_farm_token_nonce, - attributes.lp_farm_token_amount, - )); - - dual_yield_token_mapper.nft_burn(p.token_nonce, &p.amount); - } + self.check_stake_farm_payments(&original_owner, &payments); - let lp_tokens_in_farm = self.get_lp_tokens_in_farm_position( - lp_farm_token_payment.token_nonce, - &lp_farm_token_payment.amount, - ); - let staking_token_amount = self.get_lp_tokens_safe_price(lp_tokens_in_farm); - let staking_farm_enter_result = self.staking_farm_enter( - original_owner.clone(), - staking_token_amount, - additional_staking_farm_tokens, - ); - let received_staking_farm_token = staking_farm_enter_result.received_staking_farm_token; - - let (merged_lp_farm_tokens, lp_farm_boosted_rewards) = self - .merge_lp_farm_tokens( - original_owner.clone(), - lp_farm_token_payment, - additional_lp_farm_tokens, - ) - .into_tuple(); - - let new_attributes = DualYieldTokenAttributes { - lp_farm_token_nonce: merged_lp_farm_tokens.token_nonce, - lp_farm_token_amount: merged_lp_farm_tokens.amount, - staking_farm_token_nonce: received_staking_farm_token.token_nonce, - staking_farm_token_amount: received_staking_farm_token.amount, - }; - let new_dual_yield_tokens = - self.create_dual_yield_tokens(&dual_yield_token_mapper, &new_attributes); - let output_payments = StakeProxyResult { - dual_yield_tokens: new_dual_yield_tokens, - staking_boosted_rewards: staking_farm_enter_result.boosted_rewards, - lp_farm_boosted_rewards, - }; + let output_payments = self.stake_farm_tokens_common(original_owner.clone(), payments); self.send_payment_non_zero(&original_owner, &output_payments.lp_farm_boosted_rewards); self.send_payment_non_zero(&original_owner, &output_payments.staking_boosted_rewards); @@ -106,113 +41,78 @@ pub trait ProxyExternalInteractionsModule: #[payable("*")] #[endpoint(claimDualYieldOnBehalf)] fn claim_dual_yield_on_behalf(&self) -> ClaimDualYieldResult { - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt(); - let dual_yield_token_mapper = self.dual_yield_token(); - dual_yield_token_mapper.require_same_token(&payment.token_identifier); - - let attributes: DualYieldTokenAttributes = - self.get_attributes_as_part_of_fixed_supply(&payment, &dual_yield_token_mapper); - let original_owner = self.get_farm_position_original_owner(attributes.lp_farm_token_nonce); + let caller = self.blockchain().get_caller(); + let original_owner = self.get_underlying_farm_position_original_owner(&payment); self.require_user_whitelisted(&original_owner, &caller); - let lp_tokens_in_position = self.get_lp_tokens_in_farm_position( - attributes.lp_farm_token_nonce, - &attributes.lp_farm_token_amount, - ); - let new_staking_farm_value = self.get_lp_tokens_safe_price(lp_tokens_in_position); - - let staking_farm_token_id = self.staking_farm_token_id().get(); - let lp_farm_token_id = self.lp_farm_token_id().get(); - let lp_farm_claim_rewards_result = self.lp_farm_claim_rewards( - original_owner.clone(), - lp_farm_token_id, - attributes.lp_farm_token_nonce, - attributes.lp_farm_token_amount, - ); - let staking_farm_claim_rewards_result = self.staking_farm_claim_rewards( - original_owner.clone(), - staking_farm_token_id, - attributes.staking_farm_token_nonce, - attributes.staking_farm_token_amount, - new_staking_farm_value, - ); - - let new_lp_farm_tokens = lp_farm_claim_rewards_result.new_lp_farm_tokens; - let new_staking_farm_tokens = staking_farm_claim_rewards_result.new_staking_farm_tokens; - let new_attributes = DualYieldTokenAttributes { - lp_farm_token_nonce: new_lp_farm_tokens.token_nonce, - lp_farm_token_amount: new_lp_farm_tokens.amount, - staking_farm_token_nonce: new_staking_farm_tokens.token_nonce, - staking_farm_token_amount: new_staking_farm_tokens.amount, - }; - - let lp_farm_rewards = lp_farm_claim_rewards_result.lp_farm_rewards; - let staking_farm_rewards = staking_farm_claim_rewards_result.staking_farm_rewards; - let new_dual_yield_attributes = new_attributes; - - let new_dual_yield_tokens = - self.create_dual_yield_tokens(&dual_yield_token_mapper, &new_dual_yield_attributes); - let claim_result = ClaimDualYieldResult { - lp_farm_rewards, - staking_farm_rewards, - new_dual_yield_tokens, - }; + let claim_result = self.claim_dual_yield_common(original_owner.clone(), &payment); self.send_payment_non_zero(&original_owner, &claim_result.lp_farm_rewards); self.send_payment_non_zero(&original_owner, &claim_result.staking_farm_rewards); self.send_payment_non_zero(&caller, &claim_result.new_dual_yield_tokens); - dual_yield_token_mapper.nft_burn(payment.token_nonce, &payment.amount); - claim_result } fn check_stake_farm_payments( &self, original_owner: &ManagedAddress, - first_payment: &EsdtTokenPayment, - additional_payments: &ManagedVec, + payments: &ManagedVec, ) { - let dual_yield_token_mapper = self.dual_yield_token(); - let dual_yield_token_id = dual_yield_token_mapper.get_token_id(); - let lp_farm_token_id = self.lp_farm_token_id().get(); + let lp_farm_token_payment = payments.get(0); + let additional_payments = payments.slice(1, payments.len()).unwrap_or_default(); + let lp_farm_token_id = self.lp_farm_token_id().get(); require!( - first_payment.token_identifier == lp_farm_token_id, + lp_farm_token_payment.token_identifier == lp_farm_token_id, "Invalid first payment" ); + + let attributes = self + .blockchain() + .get_token_attributes::>( + &lp_farm_token_payment.token_identifier, + lp_farm_token_payment.token_nonce, + ); + require!( - &self.get_farm_position_original_owner(first_payment.token_nonce) == original_owner, + &attributes.original_owner == original_owner, "Provided address is not the same as the original owner" ); for payment in additional_payments.into_iter() { - if payment.token_identifier != dual_yield_token_id { - sc_panic!("Wrong additional payments"); - } - - let attributes: DualYieldTokenAttributes = - dual_yield_token_mapper.get_token_attributes(payment.token_nonce); require!( - &self.get_farm_position_original_owner(attributes.lp_farm_token_nonce) - == original_owner, + &self.get_underlying_farm_position_original_owner(&payment) == original_owner, "Provided address is not the same as the original owner" ); } } - fn get_farm_position_original_owner(&self, farm_token_nonce: u64) -> ManagedAddress { + fn get_underlying_farm_position_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 = + 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 .blockchain() .get_token_attributes::>( &lp_farm_token_id, - farm_token_nonce, + attributes.lp_farm_token_nonce, ); + require!( + attributes.original_owner != ManagedAddress::zero(), + "Invalid original owner" + ); + attributes.original_owner } diff --git a/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs index b32f62b76..26e9b2af7 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs @@ -22,6 +22,17 @@ pub trait ProxyStakeModule: let caller = self.blockchain().get_caller(); let orig_caller = self.get_orig_caller_from_opt(&caller, opt_orig_caller); let payments = self.get_non_empty_payments(); + + let output_payments = self.stake_farm_tokens_common(orig_caller, payments); + + output_payments.send_and_return(self, &caller) + } + + fn stake_farm_tokens_common( + &self, + original_caller: ManagedAddress, + payments: ManagedVec, + ) -> StakeProxyResult { let lp_farm_token_payment = payments.get(0); let additional_payments = payments.slice(1, payments.len()).unwrap_or_default(); @@ -62,7 +73,7 @@ pub trait ProxyStakeModule: ); let staking_token_amount = self.get_lp_tokens_safe_price(lp_tokens_in_farm); let staking_farm_enter_result = self.staking_farm_enter( - orig_caller.clone(), + original_caller.clone(), staking_token_amount, additional_staking_farm_tokens, ); @@ -70,7 +81,7 @@ pub trait ProxyStakeModule: let (merged_lp_farm_tokens, lp_farm_boosted_rewards) = self .merge_lp_farm_tokens( - orig_caller, + original_caller, lp_farm_token_payment, additional_lp_farm_tokens, ) @@ -90,6 +101,6 @@ pub trait ProxyStakeModule: lp_farm_boosted_rewards, }; - output_payments.send_and_return(self, &caller) + output_payments } } diff --git a/farm-staking/farm-staking/src/external_interaction.rs b/farm-staking/farm-staking/src/external_interaction.rs index 081ec07b2..46178dd66 100644 --- a/farm-staking/farm-staking/src/external_interaction.rs +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -50,12 +50,10 @@ pub trait ExternalInteractionsModule: let caller = self.blockchain().get_caller(); self.require_user_whitelisted(&user, &caller); - self.check_additional_payments_original_owner(&user); - let payments = self.get_non_empty_payments(); + self.check_additional_payments_original_owner(&user, &payments); let boosted_rewards = self.claim_only_boosted_payment(&user); - let boosted_rewards_payment = EsdtTokenPayment::new(self.reward_token_id().get(), 0, boosted_rewards); @@ -83,11 +81,11 @@ pub trait ExternalInteractionsModule: #[payable("*")] #[endpoint(claimRewardsOnBehalf)] fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { - let user = self.check_and_return_original_owner(); + let payment = self.call_value().single_esdt(); + let user = self.check_and_return_original_owner(&payment); let caller = self.blockchain().get_caller(); self.require_user_whitelisted(&user, &caller); - let payment = self.call_value().single_esdt(); let claim_result = self.claim_rewards_base_no_farm_token_mint::>( user.clone(), ManagedVec::from_single_item(payment), @@ -122,34 +120,24 @@ pub trait ExternalInteractionsModule: (virtual_farm_token.payment, claim_result.rewards).into() } - fn check_and_return_original_owner(&self) -> ManagedAddress { - let payments = self.call_value().all_esdt_transfers().clone_value(); + fn check_and_return_original_owner(&self, payment: &EsdtTokenPayment) -> ManagedAddress { let farm_token_mapper = self.farm_token(); - let mut original_owner = ManagedAddress::zero(); - for payment in payments.into_iter() { - let attributes: StakingFarmTokenAttributes = - farm_token_mapper.get_token_attributes(payment.token_nonce); - - if original_owner.is_zero() { - original_owner = attributes.original_owner; - } else { - require!( - original_owner == attributes.original_owner, - "All position must have the same original owner" - ); - } - } + let attributes: StakingFarmTokenAttributes = + farm_token_mapper.get_token_attributes(payment.token_nonce); require!( - !original_owner.is_zero(), + !attributes.original_owner.is_zero(), "Original owner could not be identified" ); - original_owner + attributes.original_owner } - fn check_additional_payments_original_owner(&self, user: &ManagedAddress) { - let payments = self.call_value().all_esdt_transfers().clone_value(); + fn check_additional_payments_original_owner( + &self, + user: &ManagedAddress, + payments: &ManagedVec, + ) { if payments.len() == 1 { return; } From 17b96012ba35e3dc57a996fabbd2176fd5903b5e Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Sun, 3 Nov 2024 02:35:31 +0200 Subject: [PATCH 11/11] clippy fixes --- farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs | 4 ++-- .../src/proxy_actions/external_interaction.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs index d97eb263a..057c59206 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs @@ -30,7 +30,7 @@ pub trait ProxyClaimModule: let payment = self.call_value().single_esdt(); - let claim_result = self.claim_dual_yield_common(orig_caller, &payment); + let claim_result = self.claim_dual_yield_common(orig_caller, payment); claim_result.send_and_return(self, &caller) } @@ -38,7 +38,7 @@ pub trait ProxyClaimModule: fn claim_dual_yield_common( &self, orig_caller: ManagedAddress, - payment: &EsdtTokenPayment, + payment: EsdtTokenPayment, ) -> ClaimDualYieldResult { let dual_yield_token_mapper = self.dual_yield_token(); dual_yield_token_mapper.require_same_token(&payment.token_identifier); 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 218a42036..12d8b257d 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 @@ -47,7 +47,7 @@ pub trait ProxyExternalInteractionsModule: let original_owner = self.get_underlying_farm_position_original_owner(&payment); self.require_user_whitelisted(&original_owner, &caller); - let claim_result = self.claim_dual_yield_common(original_owner.clone(), &payment); + let claim_result = self.claim_dual_yield_common(original_owner.clone(), payment); self.send_payment_non_zero(&original_owner, &claim_result.lp_farm_rewards); self.send_payment_non_zero(&original_owner, &claim_result.staking_farm_rewards); @@ -98,7 +98,7 @@ pub trait ProxyExternalInteractionsModule: dual_yield_token_mapper.require_same_token(&payment.token_identifier); let attributes: DualYieldTokenAttributes = - self.get_attributes_as_part_of_fixed_supply(&payment, &dual_yield_token_mapper); + 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