From 3ae33363a56f57459b58a17fe3ea9b5d6505d0c6 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Sun, 27 Oct 2024 16:34:30 +0200 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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 From 541392dcc4f8e66f697f685bb1b2201e2587643a Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Wed, 20 Nov 2024 02:37:21 +0000 Subject: [PATCH 12/20] farm and farm staking refactor --- common/common_structs/src/farm_types.rs | 7 ++ common/modules/utils/src/lib.rs | 56 +++++++++++++++- .../src/external_interaction.rs | 64 ++++-------------- .../farm_with_locked_rewards_setup/mod.rs | 4 +- dex/farm/src/external_interaction.rs | 65 ++++--------------- .../tests/farm_setup/multi_user_farm_setup.rs | 10 ++- dex/permissions-hub/src/lib.rs | 24 +++++-- dex/permissions-hub/wasm/src/lib.rs | 10 ++- .../src/proxy_actions/claim.rs | 10 ++- .../farm-staking/src/external_interaction.rs | 56 ++++------------ .../farm-staking/src/token_attributes.rs | 4 ++ .../tests/farm_staking_setup/mod.rs | 4 +- 12 files changed, 148 insertions(+), 166 deletions(-) diff --git a/common/common_structs/src/farm_types.rs b/common/common_structs/src/farm_types.rs index d7d85e20b..676b26e36 100644 --- a/common/common_structs/src/farm_types.rs +++ b/common/common_structs/src/farm_types.rs @@ -80,6 +80,8 @@ pub trait FarmToken { fn get_compounded_rewards(&self) -> BigUint; fn get_initial_farming_tokens(&self) -> BigUint; + + fn get_original_owner(&self) -> ManagedAddress; } impl FarmToken for FarmTokenAttributes { @@ -97,4 +99,9 @@ impl FarmToken for FarmTokenAttributes { fn get_initial_farming_tokens(&self) -> BigUint { &self.current_farm_amount - &self.compounded_reward } + + #[inline] + fn get_original_owner(&self) -> ManagedAddress { + self.original_owner.clone() + } } diff --git a/common/modules/utils/src/lib.rs b/common/modules/utils/src/lib.rs index 990b88329..861061b28 100644 --- a/common/modules/utils/src/lib.rs +++ b/common/modules/utils/src/lib.rs @@ -2,7 +2,7 @@ multiversx_sc::imports!(); -use common_structs::{PaymentAttributesPair, PaymentsVec}; +use common_structs::{FarmToken, PaymentAttributesPair, PaymentsVec}; use fixed_supply_token::FixedSupplyToken; use mergeable::Mergeable; @@ -116,6 +116,60 @@ pub trait UtilsModule { } } + fn check_and_return_original_owner + TopDecode>( + &self, + payments: &PaymentsVec, + farm_token_mapper: &NonFungibleTokenMapper, + ) -> ManagedAddress { + let mut original_owner = ManagedAddress::zero(); + for payment in payments.iter() { + let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); + let payment_original_owner = attributes.get_original_owner(); + + if original_owner.is_zero() { + original_owner = payment_original_owner; + } else { + require!( + original_owner == payment_original_owner, + "All position must have the same original owner" + ); + } + } + + require!( + !original_owner.is_zero(), + "Original owner could not be identified" + ); + + original_owner + } + + fn check_additional_payments_original_owner + TopDecode>( + &self, + user: &ManagedAddress, + payments: &PaymentsVec, + farm_token_mapper: &NonFungibleTokenMapper, + ) { + if payments.len() == 1 { + return; + } + + 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: T = farm_token_mapper.get_token_attributes(payment.token_nonce); + let payment_original_owner = attributes.get_original_owner(); + + require!( + user == &payment_original_owner, + "Provided address is not the same as the original owner" + ); + } + } + fn require_valid_token_id(&self, token_id: &TokenIdentifier) { require!(token_id.is_valid_esdt_identifier(), "Invalid token ID"); } diff --git a/dex/farm-with-locked-rewards/src/external_interaction.rs b/dex/farm-with-locked-rewards/src/external_interaction.rs index 54ecf7510..3fd724465 100644 --- a/dex/farm-with-locked-rewards/src/external_interaction.rs +++ b/dex/farm-with-locked-rewards/src/external_interaction.rs @@ -45,7 +45,13 @@ 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(); + let farm_token_mapper = self.farm_token(); + self.check_additional_payments_original_owner::>( + &user, + &payments, + &farm_token_mapper, + ); let boosted_rewards = self.claim_only_boosted_payment(&user); let new_farm_token = self.enter_farm::>(user.clone()); @@ -71,8 +77,13 @@ pub trait ExternalInteractionsModule: #[payable("*")] #[endpoint(claimRewardsOnBehalf)] fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { - let user = self.check_and_return_original_owner(); + let payments = self.get_non_empty_payments(); + let farm_token_mapper = self.farm_token(); let caller = self.blockchain().get_caller(); + let user = self.check_and_return_original_owner::>( + &payments, + &farm_token_mapper, + ); self.require_user_whitelisted(&user, &caller); let claim_rewards_result = self.claim_rewards::>(user.clone()); @@ -95,55 +106,6 @@ pub trait ExternalInteractionsModule: (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 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 a84387950..6be3e313d 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 @@ -564,7 +564,9 @@ where &self.permissions_hub_wrapper, &rust_biguint!(0), |sc| { - sc.whitelist(managed_address!(address_to_whitelist)); + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); }, ) .assert_ok(); diff --git a/dex/farm/src/external_interaction.rs b/dex/farm/src/external_interaction.rs index 12a0b8366..66b594ed6 100644 --- a/dex/farm/src/external_interaction.rs +++ b/dex/farm/src/external_interaction.rs @@ -43,7 +43,13 @@ 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(); + let farm_token_mapper = self.farm_token(); + self.check_additional_payments_original_owner::>( + &user, + &payments, + &farm_token_mapper, + ); let boosted_rewards = self.claim_only_boosted_payment(&user); @@ -62,8 +68,14 @@ pub trait ExternalInteractionsModule: #[payable("*")] #[endpoint(claimRewardsOnBehalf)] fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { - let user = self.check_and_return_original_owner(); + let payments = self.get_non_empty_payments(); + let farm_token_mapper = self.farm_token(); + let caller = self.blockchain().get_caller(); + let user = self.check_and_return_original_owner::>( + &payments, + &farm_token_mapper, + ); self.require_user_whitelisted(&user, &caller); let claim_rewards_result = self.claim_rewards::>(user.clone()); @@ -74,55 +86,6 @@ pub trait ExternalInteractionsModule: 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 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 b19d14aa9..40010f0ea 100644 --- a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs +++ b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs @@ -657,7 +657,9 @@ where &self.permissions_hub_wrapper, &rust_biguint!(0), |sc| { - sc.whitelist(managed_address!(address_to_whitelist)); + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); }, ) .assert_ok(); @@ -666,7 +668,7 @@ where pub fn remove_whitelist_address_on_behalf( &mut self, user: &Address, - address_to_whitelist: &Address, + address_to_remove: &Address, ) { self.b_mock .execute_tx( @@ -674,7 +676,9 @@ where &self.permissions_hub_wrapper, &rust_biguint!(0), |sc| { - sc.remove_whitelist(managed_address!(address_to_whitelist)); + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_remove)); + sc.remove_whitelist(addresses); }, ) .assert_ok(); diff --git a/dex/permissions-hub/src/lib.rs b/dex/permissions-hub/src/lib.rs index 9887fe90c..7dfe4392d 100644 --- a/dex/permissions-hub/src/lib.rs +++ b/dex/permissions-hub/src/lib.rs @@ -11,18 +11,28 @@ pub trait PermissionsHub { #[upgrade] fn upgrade(&self) {} - #[endpoint(whitelist)] - fn whitelist(&self, address_to_whitelist: ManagedAddress) { + #[endpoint] + fn whitelist(&self, addresses_to_whitelist: MultiValueEncoded) { let caller = self.blockchain().get_caller(); - self.user_whitelisted_addresses(&caller) - .insert(address_to_whitelist); + for address_to_whitelist in addresses_to_whitelist.into_iter() { + require!( + self.user_whitelisted_addresses(&caller) + .insert(address_to_whitelist), + "Address is already whitelisted" + ); + } } #[endpoint(removeWhitelist)] - fn remove_whitelist(&self, address_to_remove: ManagedAddress) { + fn remove_whitelist(&self, addresses_to_remove: MultiValueEncoded) { let caller = self.blockchain().get_caller(); - self.user_whitelisted_addresses(&caller) - .swap_remove(&address_to_remove); + for address_to_remove in addresses_to_remove.into_iter() { + require!( + self.user_whitelisted_addresses(&caller) + .swap_remove(&address_to_remove), + "Address is not whitelisted" + ); + } } #[only_owner] diff --git a/dex/permissions-hub/wasm/src/lib.rs b/dex/permissions-hub/wasm/src/lib.rs index afc22eb88..4ea948115 100644 --- a/dex/permissions-hub/wasm/src/lib.rs +++ b/dex/permissions-hub/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 0 +// Endpoints: 6 // Async Callback (empty): 1 -// Total number of exported functions: 3 +// Total number of exported functions: 9 #![no_std] @@ -20,6 +20,12 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade + whitelist => whitelist + removeWhitelist => remove_whitelist + blacklist => blacklist + removeBlacklist => remove_blacklist + isWhitelisted => is_whitelisted + getBlacklistedAddresses => blacklisted_addresses ) } 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 057c59206..a3a40a5de 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs @@ -83,14 +83,12 @@ pub trait ProxyClaimModule: 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 { + dual_yield_token_mapper.nft_burn(payment.token_nonce, &payment.amount); + + 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/src/external_interaction.rs b/farm-staking/farm-staking/src/external_interaction.rs index 46178dd66..b870f5fa3 100644 --- a/farm-staking/farm-staking/src/external_interaction.rs +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -51,7 +51,12 @@ pub trait ExternalInteractionsModule: self.require_user_whitelisted(&user, &caller); let payments = self.get_non_empty_payments(); - self.check_additional_payments_original_owner(&user, &payments); + let farm_token_mapper = self.farm_token(); + self.check_additional_payments_original_owner::>( + &user, + &payments, + &farm_token_mapper, + ); let boosted_rewards = self.claim_only_boosted_payment(&user); let boosted_rewards_payment = @@ -81,14 +86,18 @@ pub trait ExternalInteractionsModule: #[payable("*")] #[endpoint(claimRewardsOnBehalf)] fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType { - let payment = self.call_value().single_esdt(); - let user = self.check_and_return_original_owner(&payment); + let payments = self.get_non_empty_payments(); + let farm_token_mapper = self.farm_token(); let caller = self.blockchain().get_caller(); + let user = self.check_and_return_original_owner::>( + &payments, + &farm_token_mapper, + ); self.require_user_whitelisted(&user, &caller); let claim_result = self.claim_rewards_base_no_farm_token_mint::>( user.clone(), - ManagedVec::from_single_item(payment), + payments, ); let mut virtual_farm_token = claim_result.new_farm_token.clone(); @@ -120,45 +129,6 @@ pub trait ExternalInteractionsModule: (virtual_farm_token.payment, claim_result.rewards).into() } - fn check_and_return_original_owner(&self, payment: &EsdtTokenPayment) -> ManagedAddress { - let farm_token_mapper = self.farm_token(); - let attributes: StakingFarmTokenAttributes = - farm_token_mapper.get_token_attributes(payment.token_nonce); - - require!( - !attributes.original_owner.is_zero(), - "Original owner could not be identified" - ); - - attributes.original_owner - } - - fn check_additional_payments_original_owner( - &self, - user: &ManagedAddress, - payments: &ManagedVec, - ) { - 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 diff --git a/farm-staking/farm-staking/src/token_attributes.rs b/farm-staking/farm-staking/src/token_attributes.rs index 59f8c6326..0fcabeba3 100644 --- a/farm-staking/farm-staking/src/token_attributes.rs +++ b/farm-staking/farm-staking/src/token_attributes.rs @@ -82,6 +82,10 @@ impl FarmToken for StakingFarmTokenAttributes { fn get_initial_farming_tokens(&self) -> BigUint { &self.current_farm_amount - &self.compounded_reward } + + fn get_original_owner(&self) -> ManagedAddress { + self.original_owner.clone() + } } impl FixedSupplyToken for StakingFarmTokenAttributes { diff --git a/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs b/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs index bf732da74..1f6863d86 100644 --- a/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs +++ b/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs @@ -679,7 +679,9 @@ where &self.permissions_hub_wrapper, &rust_biguint!(0), |sc| { - sc.whitelist(managed_address!(address_to_whitelist)); + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); }, ) .assert_ok(); From e233615e0155455565fe552f43b932216ae7675f Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Wed, 20 Nov 2024 02:42:34 +0000 Subject: [PATCH 13/20] fixed unit tests --- .../mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 b1a056345..1418c9161 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 @@ -7,7 +7,7 @@ use energy_query::Energy; use farm_with_locked_rewards::Farm; use multiversx_sc::{ codec::multi_types::OptionalValue, - types::{Address, BigInt}, + types::{Address, BigInt, MultiValueEncoded}, }; use multiversx_sc_scenario::{ managed_address, managed_biguint, managed_token_id, rust_biguint, @@ -708,7 +708,9 @@ where &self.permissions_hub_wrapper, &rust_biguint!(0), |sc| { - sc.whitelist(managed_address!(address_to_whitelist)); + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); }, ) .assert_ok(); From 6b1fbc16a3e2e14bcd7a416c1448d2e06a6d07b9 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Wed, 20 Nov 2024 03:00:02 +0000 Subject: [PATCH 14/20] added permissions hub module --- Cargo.lock | 12 +++++++ .../modules/permissions_hub_module/Cargo.toml | 15 +++++++++ .../src/permissions_hub_module.rs | 32 +++++++++++++++++++ dex/farm-with-locked-rewards/Cargo.toml | 3 ++ .../src/external_interaction.rs | 26 +-------------- dex/farm-with-locked-rewards/src/lib.rs | 1 + dex/farm-with-locked-rewards/wasm/Cargo.lock | 10 ++++++ dex/farm-with-locked-rewards/wasm/src/lib.rs | 2 +- dex/farm/Cargo.toml | 3 ++ dex/farm/src/external_interaction.rs | 26 +-------------- dex/farm/src/lib.rs | 1 + dex/farm/wasm/Cargo.lock | 9 ++++++ dex/farm/wasm/src/lib.rs | 2 +- farm-staking/farm-staking-proxy/Cargo.toml | 3 ++ farm-staking/farm-staking-proxy/src/lib.rs | 1 + .../src/proxy_actions/external_interaction.rs | 26 +-------------- .../mod.rs | 1 + farm-staking/farm-staking/Cargo.toml | 3 ++ .../farm-staking/src/external_interaction.rs | 26 +-------------- farm-staking/farm-staking/src/lib.rs | 3 +- .../tests/farm_staking_setup/mod.rs | 1 + farm-staking/farm-staking/wasm/Cargo.lock | 10 ++++++ farm-staking/farm-staking/wasm/src/lib.rs | 2 +- 23 files changed, 114 insertions(+), 104 deletions(-) create mode 100644 common/modules/permissions_hub_module/Cargo.toml create mode 100644 common/modules/permissions_hub_module/src/permissions_hub_module.rs diff --git a/Cargo.lock b/Cargo.lock index 72759ef35..432affd11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,6 +533,7 @@ dependencies = [ "pair", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -590,6 +591,7 @@ dependencies = [ "pair", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -631,6 +633,7 @@ dependencies = [ "pair", "pausable", "permissions-hub", + "permissions_hub_module", "rewards", "sc_whitelist_module", "simple-lock", @@ -771,6 +774,7 @@ dependencies = [ "num-bigint", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -1464,6 +1468,14 @@ dependencies = [ "permissions-hub", ] +[[package]] +name = "permissions_hub_module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions-hub", +] + [[package]] name = "permissions_module" version = "0.0.0" diff --git a/common/modules/permissions_hub_module/Cargo.toml b/common/modules/permissions_hub_module/Cargo.toml new file mode 100644 index 000000000..503fe9d60 --- /dev/null +++ b/common/modules/permissions_hub_module/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "permissions_hub_module" +version = "0.0.0" +authors = ["MultiversX "] +edition = "2021" + +[lib] +path = "src/permissions_hub_module.rs" + +[dependencies.permissions-hub] +path = "../../../dex/permissions-hub" + +[dependencies.multiversx-sc] +version = "=0.53.2" +features = ["esdt-token-payment-legacy-decode"] diff --git a/common/modules/permissions_hub_module/src/permissions_hub_module.rs b/common/modules/permissions_hub_module/src/permissions_hub_module.rs new file mode 100644 index 000000000..3b7832d06 --- /dev/null +++ b/common/modules/permissions_hub_module/src/permissions_hub_module.rs @@ -0,0 +1,32 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait PermissionsHubModule { + 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/Cargo.toml b/dex/farm-with-locked-rewards/Cargo.toml index f637b3249..3a5a753e1 100644 --- a/dex/farm-with-locked-rewards/Cargo.toml +++ b/dex/farm-with-locked-rewards/Cargo.toml @@ -41,6 +41,9 @@ path = "../../common/modules/utils" [dependencies.permissions_module] path = "../../common/modules/permissions_module" +[dependencies.permissions_hub_module] +path = "../../common/modules/permissions_hub_module" + [dependencies.sc_whitelist_module] path = "../../common/modules/sc_whitelist_module" diff --git a/dex/farm-with-locked-rewards/src/external_interaction.rs b/dex/farm-with-locked-rewards/src/external_interaction.rs index 3fd724465..77445dbe3 100644 --- a/dex/farm-with-locked-rewards/src/external_interaction.rs +++ b/dex/farm-with-locked-rewards/src/external_interaction.rs @@ -16,6 +16,7 @@ pub trait ExternalInteractionsModule: + farm_token::FarmTokenModule + pausable::PausableModule + permissions_module::PermissionsModule + + permissions_hub_module::PermissionsHubModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule @@ -105,29 +106,4 @@ pub trait ExternalInteractionsModule: (claim_rewards_result.new_farm_token, locked_rewards_payment).into() } - - 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 fefbf6275..0de59d77e 100644 --- a/dex/farm-with-locked-rewards/src/lib.rs +++ b/dex/farm-with-locked-rewards/src/lib.rs @@ -29,6 +29,7 @@ pub trait Farm: + utils::UtilsModule + pausable::PausableModule + permissions_module::PermissionsModule + + permissions_hub_module::PermissionsHubModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule diff --git a/dex/farm-with-locked-rewards/wasm/Cargo.lock b/dex/farm-with-locked-rewards/wasm/Cargo.lock index aa7396f98..2e5098f9b 100644 --- a/dex/farm-with-locked-rewards/wasm/Cargo.lock +++ b/dex/farm-with-locked-rewards/wasm/Cargo.lock @@ -139,6 +139,7 @@ dependencies = [ "pair", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -184,6 +185,7 @@ dependencies = [ "multiversx-sc-modules", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -432,6 +434,14 @@ dependencies = [ "multiversx-sc", ] +[[package]] +name = "permissions_hub_module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions-hub", +] + [[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 6efe13330..81ef9c901 100644 --- a/dex/farm-with-locked-rewards/wasm/src/lib.rs +++ b/dex/farm-with-locked-rewards/wasm/src/lib.rs @@ -56,6 +56,7 @@ multiversx_sc_wasm_adapter::endpoints! { removeAdmin => remove_admin_endpoint updateOwnerOrAdmin => update_owner_or_admin_endpoint getPermissions => permissions + setPermissionsHubAddress => set_permissions_hub_address addSCAddressToWhitelist => add_sc_address_to_whitelist removeSCAddressFromWhitelist => remove_sc_address_from_whitelist isSCAddressWhitelisted => is_sc_address_whitelisted @@ -68,7 +69,6 @@ multiversx_sc_wasm_adapter::endpoints! { 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/farm/Cargo.toml b/dex/farm/Cargo.toml index 1c60a80ce..12cb0ffa0 100644 --- a/dex/farm/Cargo.toml +++ b/dex/farm/Cargo.toml @@ -38,6 +38,9 @@ path = "../../common/modules/pausable" [dependencies.permissions_module] path = "../../common/modules/permissions_module" +[dependencies.permissions_hub_module] +path = "../../common/modules/permissions_hub_module" + [dependencies.sc_whitelist_module] path = "../../common/modules/sc_whitelist_module" diff --git a/dex/farm/src/external_interaction.rs b/dex/farm/src/external_interaction.rs index 66b594ed6..d840b4e19 100644 --- a/dex/farm/src/external_interaction.rs +++ b/dex/farm/src/external_interaction.rs @@ -15,6 +15,7 @@ pub trait ExternalInteractionsModule: + farm_token::FarmTokenModule + pausable::PausableModule + permissions_module::PermissionsModule + + permissions_hub_module::PermissionsHubModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule @@ -85,29 +86,4 @@ pub trait ExternalInteractionsModule: claim_rewards_result.into() } - - 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 50883f52f..acaca9b0b 100644 --- a/dex/farm/src/lib.rs +++ b/dex/farm/src/lib.rs @@ -30,6 +30,7 @@ pub trait Farm: + farm_token::FarmTokenModule + pausable::PausableModule + permissions_module::PermissionsModule + + permissions_hub_module::PermissionsHubModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule diff --git a/dex/farm/wasm/Cargo.lock b/dex/farm/wasm/Cargo.lock index 18594d924..a080bf9ba 100644 --- a/dex/farm/wasm/Cargo.lock +++ b/dex/farm/wasm/Cargo.lock @@ -139,6 +139,7 @@ dependencies = [ "pair", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -401,6 +402,14 @@ dependencies = [ "multiversx-sc", ] +[[package]] +name = "permissions_hub_module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions-hub", +] + [[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 3902fdb5d..235653731 100644 --- a/dex/farm/wasm/src/lib.rs +++ b/dex/farm/wasm/src/lib.rs @@ -53,6 +53,7 @@ multiversx_sc_wasm_adapter::endpoints! { removeAdmin => remove_admin_endpoint updateOwnerOrAdmin => update_owner_or_admin_endpoint getPermissions => permissions + setPermissionsHubAddress => set_permissions_hub_address addSCAddressToWhitelist => add_sc_address_to_whitelist removeSCAddressFromWhitelist => remove_sc_address_from_whitelist isSCAddressWhitelisted => is_sc_address_whitelisted @@ -65,7 +66,6 @@ multiversx_sc_wasm_adapter::endpoints! { 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/farm-staking/farm-staking-proxy/Cargo.toml b/farm-staking/farm-staking-proxy/Cargo.toml index 0da624de0..34d8c7151 100644 --- a/farm-staking/farm-staking-proxy/Cargo.toml +++ b/farm-staking/farm-staking-proxy/Cargo.toml @@ -66,6 +66,9 @@ path = "../../energy-integration/common-modules/energy-query" [dependencies.permissions-hub] path = "../../dex/permissions-hub" +[dependencies.permissions_hub_module] +path = "../../common/modules/permissions_hub_module" + [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 4840bd965..f28d5d083 100644 --- a/farm-staking/farm-staking-proxy/src/lib.rs +++ b/farm-staking/farm-staking-proxy/src/lib.rs @@ -14,6 +14,7 @@ pub trait FarmStakingProxy: + external_contracts_interactions::ExternalContractsInteractionsModule + lp_farm_token::LpFarmTokenModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + permissions_hub_module::PermissionsHubModule + utils::UtilsModule + token_send::TokenSendModule + energy_query::EnergyQueryModule 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 12d8b257d..722bc2eb8 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 @@ -15,6 +15,7 @@ pub trait ProxyExternalInteractionsModule: + crate::proxy_actions::stake::ProxyStakeModule + crate::proxy_actions::claim::ProxyClaimModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + permissions_hub_module::PermissionsHubModule + utils::UtilsModule + token_send::TokenSendModule + energy_query::EnergyQueryModule @@ -115,29 +116,4 @@ pub trait ProxyExternalInteractionsModule: 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/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 1418c9161..7bc22b29a 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 @@ -32,6 +32,7 @@ use farm_staking_proxy::proxy_actions::stake::ProxyStakeModule; use farm_staking_proxy::proxy_actions::unstake::ProxyUnstakeModule; use permissions_hub::PermissionsHub; +use permissions_hub_module::PermissionsHubModule; use sc_whitelist_module::SCWhitelistModule; use crate::{ diff --git a/farm-staking/farm-staking/Cargo.toml b/farm-staking/farm-staking/Cargo.toml index e19231477..b99ae9927 100644 --- a/farm-staking/farm-staking/Cargo.toml +++ b/farm-staking/farm-staking/Cargo.toml @@ -53,6 +53,9 @@ path = "../../common/modules/pausable" [dependencies.permissions_module] path = "../../common/modules/permissions_module" +[dependencies.permissions_hub_module] +path = "../../common/modules/permissions_hub_module" + [dependencies.sc_whitelist_module] path = "../../common/modules/sc_whitelist_module" diff --git a/farm-staking/farm-staking/src/external_interaction.rs b/farm-staking/farm-staking/src/external_interaction.rs index b870f5fa3..ab450a4b3 100644 --- a/farm-staking/farm-staking/src/external_interaction.rs +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -19,6 +19,7 @@ pub trait ExternalInteractionsModule: + sc_whitelist_module::SCWhitelistModule + pausable::PausableModule + permissions_module::PermissionsModule + + permissions_hub_module::PermissionsHubModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + farm_base_impl::base_farm_init::BaseFarmInitModule + farm_base_impl::base_farm_validation::BaseFarmValidationModule @@ -128,29 +129,4 @@ pub trait ExternalInteractionsModule: (virtual_farm_token.payment, claim_result.rewards).into() } - - 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 3f4f9363b..3907e38ce 100644 --- a/farm-staking/farm-staking/src/lib.rs +++ b/farm-staking/farm-staking/src/lib.rs @@ -18,12 +18,12 @@ pub mod claim_only_boosted_staking_rewards; pub mod claim_stake_farm_rewards; pub mod compound_stake_farm_rewards; pub mod custom_rewards; +pub mod external_interaction; pub mod farm_token_roles; 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: @@ -36,6 +36,7 @@ pub trait FarmStaking: + sc_whitelist_module::SCWhitelistModule + pausable::PausableModule + permissions_module::PermissionsModule + + permissions_hub_module::PermissionsHubModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + farm_base_impl::base_farm_init::BaseFarmInitModule + farm_base_impl::base_farm_validation::BaseFarmValidationModule 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 1f6863d86..35ed665bc 100644 --- a/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs +++ b/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs @@ -27,6 +27,7 @@ use farm_staking::*; use farm_token::FarmTokenModule; use pausable::{PausableModule, State}; use permissions_hub::PermissionsHub; +use permissions_hub_module::PermissionsHubModule; use rewards::RewardsModule; pub static REWARD_TOKEN_ID: &[u8] = b"RIDE-abcdef"; // reward token ID diff --git a/farm-staking/farm-staking/wasm/Cargo.lock b/farm-staking/farm-staking/wasm/Cargo.lock index 267c19b5d..e0155a29d 100644 --- a/farm-staking/farm-staking/wasm/Cargo.lock +++ b/farm-staking/farm-staking/wasm/Cargo.lock @@ -139,6 +139,7 @@ dependencies = [ "pair", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -185,6 +186,7 @@ dependencies = [ "pair", "pausable", "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -433,6 +435,14 @@ dependencies = [ "multiversx-sc", ] +[[package]] +name = "permissions_hub_module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions-hub", +] + [[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 f2e25d699..e0fdd5bb3 100644 --- a/farm-staking/farm-staking/wasm/src/lib.rs +++ b/farm-staking/farm-staking/wasm/src/lib.rs @@ -59,6 +59,7 @@ multiversx_sc_wasm_adapter::endpoints! { removeAdmin => remove_admin_endpoint updateOwnerOrAdmin => update_owner_or_admin_endpoint getPermissions => permissions + setPermissionsHubAddress => set_permissions_hub_address setBurnRoleForAddress => set_burn_role_for_address stakeFarmThroughProxy => stake_farm_through_proxy stakeFarm => stake_farm_endpoint @@ -70,7 +71,6 @@ multiversx_sc_wasm_adapter::endpoints! { 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 6d2132547cbc46f6d17d73fce628eea230d1490c Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Wed, 20 Nov 2024 03:02:39 +0000 Subject: [PATCH 15/20] unit tests fix --- dex/farm/tests/farm_setup/multi_user_farm_setup.rs | 1 + 1 file changed, 1 insertion(+) 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 40010f0ea..3398fd679 100644 --- a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs +++ b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs @@ -25,6 +25,7 @@ use farm_boosted_yields::FarmBoostedYieldsModule; use farm_token::FarmTokenModule; use pausable::{PausableModule, State}; use permissions_hub::PermissionsHub; +use permissions_hub_module::PermissionsHubModule; use sc_whitelist_module::SCWhitelistModule; use week_timekeeping::Epoch; use weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule; From dcb640793de9a7b6977c9c55d640cf328733c526 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Wed, 20 Nov 2024 03:04:57 +0000 Subject: [PATCH 16/20] farm with locked rewards unit tests fix --- .../tests/farm_with_locked_rewards_setup/mod.rs | 1 + 1 file changed, 1 insertion(+) 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 6be3e313d..4d2a2819a 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 @@ -26,6 +26,7 @@ use locking_module::lock_with_energy_module::LockWithEnergyModule; use multiversx_sc_modules::pause::PauseModule; use pausable::{PausableModule, State}; use permissions_hub::PermissionsHub; +use permissions_hub_module::PermissionsHubModule; use rewards::RewardsModule; use sc_whitelist_module::SCWhitelistModule; use simple_lock::locked_token::LockedTokenModule; From a2f526f67a6deeb39b6f7e015820e3600c3531e0 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Wed, 20 Nov 2024 09:43:45 +0000 Subject: [PATCH 17/20] added OriginalOwnerHelperModule --- Cargo.lock | 11 ++++ .../modules/original_owner_helper/Cargo.toml | 15 +++++ .../modules/original_owner_helper/src/lib.rs | 62 +++++++++++++++++++ common/modules/utils/src/lib.rs | 56 +---------------- dex/farm-with-locked-rewards/Cargo.toml | 3 + .../src/external_interaction.rs | 1 + dex/farm-with-locked-rewards/src/lib.rs | 1 + dex/farm-with-locked-rewards/wasm/Cargo.lock | 10 +++ dex/farm/Cargo.toml | 3 + dex/farm/src/external_interaction.rs | 1 + dex/farm/src/lib.rs | 1 + dex/farm/wasm/Cargo.lock | 9 +++ farm-staking/farm-staking/Cargo.toml | 3 + .../farm-staking/src/external_interaction.rs | 1 + farm-staking/farm-staking/src/lib.rs | 1 + farm-staking/farm-staking/wasm/Cargo.lock | 10 +++ 16 files changed, 133 insertions(+), 55 deletions(-) create mode 100644 common/modules/original_owner_helper/Cargo.toml create mode 100644 common/modules/original_owner_helper/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 432affd11..4b4c525d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -530,6 +530,7 @@ dependencies = [ "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", + "original_owner_helper", "pair", "pausable", "permissions-hub", @@ -588,6 +589,7 @@ dependencies = [ "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", + "original_owner_helper", "pair", "pausable", "permissions-hub", @@ -772,6 +774,7 @@ dependencies = [ "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", + "original_owner_helper", "pausable", "permissions-hub", "permissions_hub_module", @@ -1373,6 +1376,14 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "original_owner_helper" +version = "0.0.0" +dependencies = [ + "common_structs", + "multiversx-sc", +] + [[package]] name = "pair" version = "0.0.0" diff --git a/common/modules/original_owner_helper/Cargo.toml b/common/modules/original_owner_helper/Cargo.toml new file mode 100644 index 000000000..21fa393dc --- /dev/null +++ b/common/modules/original_owner_helper/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "original_owner_helper" +version = "0.0.0" +authors = ["MultiversX "] +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "=0.53.2" +features = ["esdt-token-payment-legacy-decode"] + +[dependencies.common_structs] +path = "../../common_structs" diff --git a/common/modules/original_owner_helper/src/lib.rs b/common/modules/original_owner_helper/src/lib.rs new file mode 100644 index 000000000..3f5126929 --- /dev/null +++ b/common/modules/original_owner_helper/src/lib.rs @@ -0,0 +1,62 @@ +#![no_std] + +multiversx_sc::imports!(); + +use common_structs::{FarmToken, PaymentsVec}; + +#[multiversx_sc::module] +pub trait OriginalOwnerHelperModule { + fn check_and_return_original_owner + TopDecode>( + &self, + payments: &PaymentsVec, + farm_token_mapper: &NonFungibleTokenMapper, + ) -> ManagedAddress { + let mut original_owner = ManagedAddress::zero(); + for payment in payments.iter() { + let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); + let payment_original_owner = attributes.get_original_owner(); + + if original_owner.is_zero() { + original_owner = payment_original_owner; + } else { + require!( + original_owner == payment_original_owner, + "All position must have the same original owner" + ); + } + } + + require!( + !original_owner.is_zero(), + "Original owner could not be identified" + ); + + original_owner + } + + fn check_additional_payments_original_owner + TopDecode>( + &self, + user: &ManagedAddress, + payments: &PaymentsVec, + farm_token_mapper: &NonFungibleTokenMapper, + ) { + if payments.len() == 1 { + return; + } + + 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: T = farm_token_mapper.get_token_attributes(payment.token_nonce); + let payment_original_owner = attributes.get_original_owner(); + + require!( + user == &payment_original_owner, + "Provided address is not the same as the original owner" + ); + } + } +} diff --git a/common/modules/utils/src/lib.rs b/common/modules/utils/src/lib.rs index 861061b28..990b88329 100644 --- a/common/modules/utils/src/lib.rs +++ b/common/modules/utils/src/lib.rs @@ -2,7 +2,7 @@ multiversx_sc::imports!(); -use common_structs::{FarmToken, PaymentAttributesPair, PaymentsVec}; +use common_structs::{PaymentAttributesPair, PaymentsVec}; use fixed_supply_token::FixedSupplyToken; use mergeable::Mergeable; @@ -116,60 +116,6 @@ pub trait UtilsModule { } } - fn check_and_return_original_owner + TopDecode>( - &self, - payments: &PaymentsVec, - farm_token_mapper: &NonFungibleTokenMapper, - ) -> ManagedAddress { - let mut original_owner = ManagedAddress::zero(); - for payment in payments.iter() { - let attributes: T = farm_token_mapper.get_token_attributes(payment.token_nonce); - let payment_original_owner = attributes.get_original_owner(); - - if original_owner.is_zero() { - original_owner = payment_original_owner; - } else { - require!( - original_owner == payment_original_owner, - "All position must have the same original owner" - ); - } - } - - require!( - !original_owner.is_zero(), - "Original owner could not be identified" - ); - - original_owner - } - - fn check_additional_payments_original_owner + TopDecode>( - &self, - user: &ManagedAddress, - payments: &PaymentsVec, - farm_token_mapper: &NonFungibleTokenMapper, - ) { - if payments.len() == 1 { - return; - } - - 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: T = farm_token_mapper.get_token_attributes(payment.token_nonce); - let payment_original_owner = attributes.get_original_owner(); - - require!( - user == &payment_original_owner, - "Provided address is not the same as the original owner" - ); - } - } - fn require_valid_token_id(&self, token_id: &TokenIdentifier) { require!(token_id.is_valid_esdt_identifier(), "Invalid token ID"); } diff --git a/dex/farm-with-locked-rewards/Cargo.toml b/dex/farm-with-locked-rewards/Cargo.toml index 3a5a753e1..d39dd6ed1 100644 --- a/dex/farm-with-locked-rewards/Cargo.toml +++ b/dex/farm-with-locked-rewards/Cargo.toml @@ -44,6 +44,9 @@ path = "../../common/modules/permissions_module" [dependencies.permissions_hub_module] path = "../../common/modules/permissions_hub_module" +[dependencies.original_owner_helper] +path = "../../common/modules/original_owner_helper" + [dependencies.sc_whitelist_module] path = "../../common/modules/sc_whitelist_module" diff --git a/dex/farm-with-locked-rewards/src/external_interaction.rs b/dex/farm-with-locked-rewards/src/external_interaction.rs index 77445dbe3..be29004f0 100644 --- a/dex/farm-with-locked-rewards/src/external_interaction.rs +++ b/dex/farm-with-locked-rewards/src/external_interaction.rs @@ -17,6 +17,7 @@ pub trait ExternalInteractionsModule: + pausable::PausableModule + permissions_module::PermissionsModule + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule diff --git a/dex/farm-with-locked-rewards/src/lib.rs b/dex/farm-with-locked-rewards/src/lib.rs index 0de59d77e..69b5dc4a1 100644 --- a/dex/farm-with-locked-rewards/src/lib.rs +++ b/dex/farm-with-locked-rewards/src/lib.rs @@ -30,6 +30,7 @@ pub trait Farm: + pausable::PausableModule + permissions_module::PermissionsModule + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule diff --git a/dex/farm-with-locked-rewards/wasm/Cargo.lock b/dex/farm-with-locked-rewards/wasm/Cargo.lock index 2e5098f9b..1ec1e747a 100644 --- a/dex/farm-with-locked-rewards/wasm/Cargo.lock +++ b/dex/farm-with-locked-rewards/wasm/Cargo.lock @@ -136,6 +136,7 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", "permissions-hub", @@ -183,6 +184,7 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pausable", "permissions-hub", "permissions_hub_module", @@ -403,6 +405,14 @@ dependencies = [ "autocfg", ] +[[package]] +name = "original_owner_helper" +version = "0.0.0" +dependencies = [ + "common_structs", + "multiversx-sc", +] + [[package]] name = "pair" version = "0.0.0" diff --git a/dex/farm/Cargo.toml b/dex/farm/Cargo.toml index 12cb0ffa0..b91a99066 100644 --- a/dex/farm/Cargo.toml +++ b/dex/farm/Cargo.toml @@ -41,6 +41,9 @@ path = "../../common/modules/permissions_module" [dependencies.permissions_hub_module] path = "../../common/modules/permissions_hub_module" +[dependencies.original_owner_helper] +path = "../../common/modules/original_owner_helper" + [dependencies.sc_whitelist_module] path = "../../common/modules/sc_whitelist_module" diff --git a/dex/farm/src/external_interaction.rs b/dex/farm/src/external_interaction.rs index d840b4e19..0af8fe5a5 100644 --- a/dex/farm/src/external_interaction.rs +++ b/dex/farm/src/external_interaction.rs @@ -16,6 +16,7 @@ pub trait ExternalInteractionsModule: + pausable::PausableModule + permissions_module::PermissionsModule + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule diff --git a/dex/farm/src/lib.rs b/dex/farm/src/lib.rs index acaca9b0b..5eb7ea834 100644 --- a/dex/farm/src/lib.rs +++ b/dex/farm/src/lib.rs @@ -31,6 +31,7 @@ pub trait Farm: + pausable::PausableModule + permissions_module::PermissionsModule + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + sc_whitelist_module::SCWhitelistModule + events::EventsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule diff --git a/dex/farm/wasm/Cargo.lock b/dex/farm/wasm/Cargo.lock index a080bf9ba..c85e89764 100644 --- a/dex/farm/wasm/Cargo.lock +++ b/dex/farm/wasm/Cargo.lock @@ -136,6 +136,7 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", "permissions-hub", @@ -371,6 +372,14 @@ dependencies = [ "autocfg", ] +[[package]] +name = "original_owner_helper" +version = "0.0.0" +dependencies = [ + "common_structs", + "multiversx-sc", +] + [[package]] name = "pair" version = "0.0.0" diff --git a/farm-staking/farm-staking/Cargo.toml b/farm-staking/farm-staking/Cargo.toml index b99ae9927..51876247b 100644 --- a/farm-staking/farm-staking/Cargo.toml +++ b/farm-staking/farm-staking/Cargo.toml @@ -56,6 +56,9 @@ path = "../../common/modules/permissions_module" [dependencies.permissions_hub_module] path = "../../common/modules/permissions_hub_module" +[dependencies.original_owner_helper] +path = "../../common/modules/original_owner_helper" + [dependencies.sc_whitelist_module] path = "../../common/modules/sc_whitelist_module" diff --git a/farm-staking/farm-staking/src/external_interaction.rs b/farm-staking/farm-staking/src/external_interaction.rs index ab450a4b3..1ece48d9a 100644 --- a/farm-staking/farm-staking/src/external_interaction.rs +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -20,6 +20,7 @@ pub trait ExternalInteractionsModule: + pausable::PausableModule + permissions_module::PermissionsModule + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + farm_base_impl::base_farm_init::BaseFarmInitModule + farm_base_impl::base_farm_validation::BaseFarmValidationModule diff --git a/farm-staking/farm-staking/src/lib.rs b/farm-staking/farm-staking/src/lib.rs index 3907e38ce..4cf1c3ebf 100644 --- a/farm-staking/farm-staking/src/lib.rs +++ b/farm-staking/farm-staking/src/lib.rs @@ -37,6 +37,7 @@ pub trait FarmStaking: + pausable::PausableModule + permissions_module::PermissionsModule + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + farm_base_impl::base_farm_init::BaseFarmInitModule + farm_base_impl::base_farm_validation::BaseFarmValidationModule diff --git a/farm-staking/farm-staking/wasm/Cargo.lock b/farm-staking/farm-staking/wasm/Cargo.lock index e0155a29d..7da9c7389 100644 --- a/farm-staking/farm-staking/wasm/Cargo.lock +++ b/farm-staking/farm-staking/wasm/Cargo.lock @@ -136,6 +136,7 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", "permissions-hub", @@ -183,6 +184,7 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", "permissions-hub", @@ -404,6 +406,14 @@ dependencies = [ "autocfg", ] +[[package]] +name = "original_owner_helper" +version = "0.0.0" +dependencies = [ + "common_structs", + "multiversx-sc", +] + [[package]] name = "pair" version = "0.0.0" From ff2d08c973aefede5907928517cd5c75b29d8a3d Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Fri, 6 Dec 2024 02:45:28 +0200 Subject: [PATCH 18/20] add readme for permissions hub SC + onBehalf features --- dex/farm-with-locked-rewards/README.md | 65 ++++++++++++ dex/farm/README.md | 68 ++++++++++++ dex/permissions-hub/README.md | 124 ++++++++++++++++++++++ farm-staking/farm-staking-proxy/README.md | 77 ++++++++++++++ farm-staking/farm-staking/README.md | 69 ++++++++++++ 5 files changed, 403 insertions(+) create mode 100644 dex/permissions-hub/README.md diff --git a/dex/farm-with-locked-rewards/README.md b/dex/farm-with-locked-rewards/README.md index fd70b0aa2..995451c9d 100644 --- a/dex/farm-with-locked-rewards/README.md +++ b/dex/farm-with-locked-rewards/README.md @@ -110,3 +110,68 @@ The interaction scripts for this contract are located in the dex subdirectory of ## Deployment The deployment of this contract is done using interaction scripts and it is managed by its admin. + +# Farm With Locked Rewards OnBehalf Operations + +## Abstract + +The Farm With Locked Rewards OnBehalf operations extend the Farm With Locked Rewards smart contract with the ability to allow whitelisted contracts to perform actions on behalf of users, enabling enhanced protocol composability while maintaining security through integration with the Permissions Hub. + +## Introduction + +This module allows third-party contracts to perform farm operations on behalf of users, after being explicitly whitelisted through the Permissions Hub. Users maintain full control over their assets by managing contract permissions, while protocols can build more complex DeFi interactions. + +## Endpoints + +### enterFarmOnBehalf + +```rust +#[payable("*")] +#[endpoint(enterFarmOnBehalf)] +fn enter_farm_on_behalf(&self, user: ManagedAddress) -> EnterFarmResultType +``` + +The enterFarmOnBehalf function allows whitelisted contracts to enter farm positions on behalf of users. It receives several arguments: + +- __user__ - The address of the user for whom the operation is being performed. This address must have whitelisted the caller contract through the Permissions Hub. +- __payment__ - The tokens to be used are received as payment in the transaction. + +The function performs the following steps: +1. Validates that the caller is whitelisted by the user through Permissions Hub +2. Processes the farming tokens payment +3. Claims any pending boosted rewards for the original owner +4. Performs the enter farm operation on behalf of the original owner +5. Sends the new farm token to the caller +6. Sends the locked rewards, if any, to the original owner +7. Updates energy and progress for the original owner + +### claimRewardsOnBehalf + +```rust +#[payable("*")] +#[endpoint(claimRewardsOnBehalf)] +fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType +``` + +The claimRewardsOnBehalf function enables whitelisted contracts to claim rewards on behalf of the users. This function does not require any address parameter, as the original owner is read from the farm position metadata. The operation requires: + +- __payment__ - The farm token must be received as payment in the transaction. + +The function performs these steps: +1. Processes the farm token payment +2. Extracts the original owner from the farm token attributes +3. Validates that the caller is whitelisted by the original owner +4. Claims and sends locked rewards to the original owner +5. Sends the new farm token to the caller + +## Storage + +The contract relies on the Permissions Hub for permission management, thus no additional storage, other than the one holding the Permissions Hub SC address, is required. All whitelisting data is managed through the Permissions Hub contract. + +## Deployment + +The onBehalf features are part of the core farm contract and require: + +1. A deployed Permissions Hub contract +2. Configuration of the Permissions Hub address in the farm contract +3. User whitelisting of contracts that will perform onBehalf operations diff --git a/dex/farm/README.md b/dex/farm/README.md index fcc860299..d4a8aa0b2 100644 --- a/dex/farm/README.md +++ b/dex/farm/README.md @@ -103,3 +103,71 @@ The interaction scripts for this contract are located in the dex subdirectory of ## Deployment The deployment of this contract is done using interaction scripts and it is managed by its admin (regular wallet at the moment, yet soon to be governance smart contract). + + +# Farm OnBehalf Operations + +## Abstract + +The Farm OnBehalf operations extend the Farm smart contract with the ability to allow whitelisted contracts to perform actions on behalf of users, enabling enhanced protocol composability while maintaining security through integration with the Permissions Hub. + +## Introduction + +This module allows third-party contracts to perform farm operations on behalf of users, after being explicitly whitelisted through the Permissions Hub. Users maintain full control over their assets by managing contract permissions, while protocols can build more complex DeFi interactions. + +## Endpoints + +### enterFarmOnBehalf + +```rust +#[payable("*")] +#[endpoint(enterFarmOnBehalf)] +fn enter_farm_on_behalf(&self, user: ManagedAddress) -> EnterFarmResultType +``` + +The enterFarmOnBehalf function allows whitelisted contracts to enter farm positions on behalf of users. It receives several arguments: + +- __user__ - The address of the user for whom the operation is being performed. This address must have whitelisted the caller contract through the Permissions Hub. +- __payment__ - The tokens to be used are received as payment in the transaction. + +The function performs the following steps: +1. Validates that the caller is whitelisted by the user through Permissions Hub +2. Processes the farming tokens payment +3. Claims any pending boosted rewards for the original owner +4. Performs the enter farm operation on behalf of the original owner +5. Sends the new farm token to the caller +6. Sends the collected rewards, if any, to the original owner +7. Updates energy and progress for the original owner + +### claimRewardsOnBehalf + +```rust +#[payable("*")] +#[endpoint(claimRewardsOnBehalf)] +fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType +``` + +The claimRewardsOnBehalf function enables whitelisted contracts to claim rewards on behalf of the users. This function does not require any address parameter, as the original owner is read from the farm position metadata. The operation requires: + +- __payment__ - The farm token must be received as payment in the transaction. + +The function performs these steps: +1. Processes the farm token payment +2. Extracts the original owner from the farm token attributes +3. Validates that the caller is whitelisted by the original owner +4. Claims and sends rewards to the original owner +5. Sends the new farm token to the caller + +## Storage + +The contract relies on the Permissions Hub for permission management, thus no additional storage, other than the one holding the Permissions Hub SC address, is required. All whitelisting data is managed through the Permissions Hub contract. + + +## Deployment + +The onBehalf features are part of the core farm contract and require: + +1. A deployed Permissions Hub contract +2. Configuration of the Permissions Hub address in the farm contract +3. User whitelisting of contracts that will perform onBehalf operations + diff --git a/dex/permissions-hub/README.md b/dex/permissions-hub/README.md new file mode 100644 index 000000000..87c5fd091 --- /dev/null +++ b/dex/permissions-hub/README.md @@ -0,0 +1,124 @@ +# Permissions Hub Smart Contract + +## Overview +The Permissions Hub is a security-focused smart contract that manages permissions for on-behalf operations across the MultiversX DeFi ecosystem. It allows users to whitelist specific contracts that can perform operations on their behalf, enabling secure contract-to-contract interactions while maintaining user control over their assets. + +## Features +- User-controlled whitelisting of trusted contracts +- Administrative blacklisting for security purposes +- Granular permission management +- Efficient permission checking through optimized storage +- Integration support for other smart contracts + +## Core Functionality + +### User Operations + +#### Whitelisting Contracts +Users can whitelist multiple contract addresses that they trust to operate on their behalf: +```rust +#[endpoint] +fn whitelist(&self, addresses_to_whitelist: MultiValueEncoded) +``` +- Allows users to add multiple trusted contracts in a single transaction +- Prevents duplicate whitelisting through built-in validation +- Each user maintains their own whitelist independently + +#### Removing Whitelisted Contracts +Users can remove previously whitelisted contracts: +```rust +#[endpoint(removeWhitelist)] +fn remove_whitelist(&self, addresses_to_remove: MultiValueEncoded) +``` +- Allows batch removal of whitelisted addresses +- Validates that addresses were previously whitelisted +- Maintains user control over their permissions + +### Administrative Functions + +#### Blacklisting +Contract owner can blacklist potentially malicious addresses: +```rust +#[only_owner] +#[endpoint(blacklist)] +fn blacklist(&self, address_to_blacklist: ManagedAddress) +``` +- Restricted to contract owner +- Global blacklist affecting all users +- Security measure against identified threats + +#### Removing from Blacklist +Contract owner can remove addresses from the blacklist: +```rust +#[only_owner] +#[endpoint(removeBlacklist)] +fn remove_blacklist(&self, address_to_remove: ManagedAddress) +``` + +### View Functions + +#### Permission Checking +Contracts can verify if they have permission to operate on behalf of a user: +```rust +#[view(isWhitelisted)] +fn is_whitelisted(&self, user: &ManagedAddress, address_to_check: &ManagedAddress) -> bool +``` +- Returns true only if: + 1. The address is not blacklisted + 2. The address is in the user's whitelist +- Efficient for integration with other contracts + +#### Blacklist Viewing +```rust +#[view(getBlacklistedAddresses)] +fn blacklisted_addresses(&self) -> UnorderedSetMapper +``` +- Public view of globally blacklisted addresses +- Useful for transparency and integration purposes + +## Storage + +The contract uses two main storage mappers: + +1. User Whitelists: +```rust +#[storage_mapper("whitelistedAddresses")] +fn user_whitelisted_addresses(&self, user: &ManagedAddress) -> UnorderedSetMapper +``` +- Separate whitelist for each user +- Implemented as an UnorderedSetMapper for efficient operations + +2. Global Blacklist: +```rust +#[storage_mapper("blacklistedAddresses")] +fn blacklisted_addresses(&self) -> UnorderedSetMapper +``` +- Single global blacklist +- Managed by contract owner + +## Integration Guide + +### For Smart Contracts +To integrate with the Permissions Hub: + +1. Add the Permissions Hub address as a configurable parameter in your contract +2. Before performing operations on behalf of a user, check permissions: +```rust +let is_allowed = permissions_hub_proxy.is_whitelisted(user_address, caller_address); +require!(is_allowed, "Not authorized to perform operations on behalf of user"); +``` + +### For Users +To enable contracts to operate on your behalf: + +1. Call the `whitelist` endpoint with the contract address(es) you want to authorize +2. Monitor your active whitelisted addresses +3. Remove permissions using `removeWhitelist` when they're no longer needed + +## Security Considerations + +- Users should carefully verify contract addresses before whitelisting +- Regular auditing of whitelisted addresses is recommended +- The blacklist provides an additional security layer managed by the contract owner +- All permission changes are permanent until explicitly modified +- Users maintain full control over their whitelist diff --git a/farm-staking/farm-staking-proxy/README.md b/farm-staking/farm-staking-proxy/README.md index 158d30351..e5d9fa806 100644 --- a/farm-staking/farm-staking-proxy/README.md +++ b/farm-staking/farm-staking-proxy/README.md @@ -98,3 +98,80 @@ One thing to note here is that between claiming rewards in the farming contract To unstake his current position, a user must send the desired amount of dual yield tokens to the proxy contract. At this moment, the proxy contract knows, based on the sent dual yield token, both the farm token position and staking token position. The first step is for the proxy contract to withdraw the LP tokens from the farms and the liquidity from the pair contract. After that all the harvested rewards, the resulting eGLD from removing the LP token and the unstake position of the staking token are all sent to the user. The unstaking process is ended with the burning of the dual yield tokens. It is important to note that because of the user’s unstaked position, an unbonding period is not needed. + +# Farm Staking Proxy OnBehalf Operations + +## Abstract + +The Farm Staking Proxy contract enables complex yield strategies by managing dual yield positions. The OnBehalf operations allow whitelisted contracts to manage these positions for users, combining LP farming and staking rewards while maintaining proper ownership and security through the Permissions Hub. + +## Introduction + +This feature extends the dual yield functionality with delegated operations, allowing third-party contracts to manage composite farming positions. It maintains the security of underlying positions, proper reward distribution, and ownership tracking while enabling more complex DeFi integrations through the Permissions Hub. + +## Endpoints + +### stakeFarmOnBehalf + +```rust +#[payable("*")] +#[endpoint(stakeFarmOnBehalf)] +fn stake_farm_on_behalf(&self, original_owner: ManagedAddress) -> StakeProxyResult +``` + +The stakeFarmOnBehalf function enables whitelisted contracts to create dual yield positions. It receives: + +- __original_owner__ - The address of the user for whom the position is being created +- __payments__ - Multiple token payments required for the dual yield position: + - First payment must be an LP farm token + - Additional payments must belong to the same original owner + +The function performs these operations: +1. Validates caller's whitelist status through Permissions Hub +2. Verifies ownership of all provided tokens +3. Creates the dual yield position +4. Distributes the results: + - LP farm boosted rewards to original owner + - Staking boosted rewards to original owner + - Dual yield tokens to caller + +### claimDualYieldOnBehalf + +```rust +#[payable("*")] +#[endpoint(claimDualYieldOnBehalf)] +fn claim_dual_yield_on_behalf(&self) -> ClaimDualYieldResult +``` + +The claimDualYieldOnBehalf function allows whitelisted contracts to claim rewards from dual yield positions. It requires: + +- __payment__ - A dual yield token payment + +The function performs these steps: +1. Extracts original owner from underlying farm position +2. Validates caller's whitelist status for the token owner +3. Claims both LP farming and staking rewards +4. Distributes rewards: + - LP farm rewards to original owner + - Staking farm rewards to original owner + - New dual yield tokens to caller + +## Storage + +The contract maintains its standard dual yield token storage and relies on underlying contracts and the Permissions Hub for any additional data. + +## Deployment + +The onBehalf features require: + +1. Proper configuration of: + - Permissions Hub address + - LP Farm contract address + - Staking Farm contract address + - Token IDs and roles + +2. Required external contracts: + - Active Permissions Hub + - Active LP Farm contract + - Active Staking Farm contract + diff --git a/farm-staking/farm-staking/README.md b/farm-staking/farm-staking/README.md index a3b188322..adb9cd5a5 100644 --- a/farm-staking/farm-staking/README.md +++ b/farm-staking/farm-staking/README.md @@ -222,3 +222,72 @@ As stated in the proxy contract documentation, the staking farm tokens are also ``` Payable endpoint that allows the caller to harvest the rewards generated by the staking farm and reinvest them seamlessly, within a single endpoint. It burns the current farm tokens and computes the actual position with the rewards included. + + +# Farm Staking onBehalf Operations + +## Abstract + +The Farm Staking smart contract extends its functionality with onBehalf operations, enabling whitelisted contracts to stake tokens and manage rewards on behalf of users. This feature integrates with the Permissions Hub to provide secure delegation of staking operations while maintaining proper reward distribution and energy system mechanics. + +## Introduction + +The onBehalf operations in Farm Staking allow third-party contracts to manage staking positions for users, facilitating complex DeFi integrations. The contract maintains all standard staking mechanics, including energy-based boosted rewards and weekly reward distributions, while adding secure delegation capabilities through the Permissions Hub integration. + +## Endpoints + +### stakeFarmOnBehalf + +```rust +#[payable("*")] +#[endpoint(stakeFarmOnBehalf)] +fn stake_farm_on_behalf(&self, user: ManagedAddress) -> EnterFarmResultType +``` + +The stakeFarmOnBehalf function enables whitelisted contracts to create staking positions for users. It receives: + +- __user__ - The address of the user for whom the staking position is being created +- __payment__ - The tokens to be staked are received as payment in the transaction + +The function performs these operations: +1. Validates caller's whitelist status through Permissions Hub +2. Processes any pending boosted rewards for the user +3. Creates the staking position for the original owner +4. Sends the staking position to the caller +5. Sends the rewards, if any, to the original owner +6. Maintains energy system and progress tracking +7. Emits appropriate events with both caller and user information + +### claimRewardsOnBehalf + +```rust +#[payable("*")] +#[endpoint(claimRewardsOnBehalf)] +fn claim_rewards_on_behalf(&self) -> ClaimRewardsResultType +``` + +The claimRewardsOnBehalf function allows whitelisted contracts to claim staking rewards. This function does not require any address parameter, as the original owner is read from the staking position metadata. It requires: + +- __payment__ - The farm staking token must be received as payment + +The function executes the following steps: +1. Validates the farm token and extracts the original owner +2. Verifies caller's whitelist status for the token owner +3. Claims any pending rewards +4. Creates new farm token with updated attributes +5. Updates weekly farm supply +6. Maintains energy and progress tracking +7. Distributes new farm token to caller and rewards to the original owner +8. Emits claim events with relevant information + +## Storage + +The contract relies on the Permissions Hub for permission management, thus no additional storage, other than the one holding the Permissions Hub SC address, is required. All whitelisting data is managed through the Permissions Hub contract. + +## Deployment + +The onBehalf features are part of the core farm contract and require: + +1. A deployed Permissions Hub contract +2. Configuration of the Permissions Hub address in the staking contract +3. User whitelisting of contracts that will perform onBehalf operations \ No newline at end of file From 23c1adbbc6c37191f3555407c6ee8914692a5897 Mon Sep 17 00:00:00 2001 From: Sorin Petreasca Date: Wed, 11 Dec 2024 17:41:54 +0200 Subject: [PATCH 19/20] exitOnBehalf readme --- dex/farm-with-locked-rewards/README.md | 4 ++++ dex/farm/README.md | 4 ++++ farm-staking/farm-staking-proxy/README.md | 4 ++++ farm-staking/farm-staking/README.md | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/dex/farm-with-locked-rewards/README.md b/dex/farm-with-locked-rewards/README.md index 995451c9d..4b06389fe 100644 --- a/dex/farm-with-locked-rewards/README.md +++ b/dex/farm-with-locked-rewards/README.md @@ -164,6 +164,10 @@ The function performs these steps: 4. Claims and sends locked rewards to the original owner 5. Sends the new farm token to the caller +## exitOnBehalf +The exit operation remains under the direct control of the position owner to ensure maximum security. When third-party contracts interact with farming positions through onBehalf operations, they receive and hold the position tokens. These tokens maintain the original owner information in their attributes, protecting the user's ownership rights. To exit their position, users must first reclaim their position tokens from the third-party contract through that protocol's specific mechanisms. Once users have regained control of their position tokens, they can perform the standard exit operation directly through the specific xExchange contract. +This design ensures users maintain ultimate control over their funds while allowing protocols to build complex DeFi interactions. + ## Storage The contract relies on the Permissions Hub for permission management, thus no additional storage, other than the one holding the Permissions Hub SC address, is required. All whitelisting data is managed through the Permissions Hub contract. diff --git a/dex/farm/README.md b/dex/farm/README.md index d4a8aa0b2..3a33264b8 100644 --- a/dex/farm/README.md +++ b/dex/farm/README.md @@ -158,6 +158,10 @@ The function performs these steps: 4. Claims and sends rewards to the original owner 5. Sends the new farm token to the caller +## exitOnBehalf +The exit operation remains under the direct control of the position owner to ensure maximum security. When third-party contracts interact with farming positions through onBehalf operations, they receive and hold the position tokens. These tokens maintain the original owner information in their attributes, protecting the user's ownership rights. To exit their position, users must first reclaim their position tokens from the third-party contract through that protocol's specific mechanisms. Once users have regained control of their position tokens, they can perform the standard exit operation directly through the specific xExchange contract. +This design ensures users maintain ultimate control over their funds while allowing protocols to build complex DeFi interactions. + ## Storage The contract relies on the Permissions Hub for permission management, thus no additional storage, other than the one holding the Permissions Hub SC address, is required. All whitelisting data is managed through the Permissions Hub contract. diff --git a/farm-staking/farm-staking-proxy/README.md b/farm-staking/farm-staking-proxy/README.md index e5d9fa806..edf67e811 100644 --- a/farm-staking/farm-staking-proxy/README.md +++ b/farm-staking/farm-staking-proxy/README.md @@ -156,6 +156,10 @@ The function performs these steps: - Staking farm rewards to original owner - New dual yield tokens to caller +## exitOnBehalf +The exit operation remains under the direct control of the position owner to ensure maximum security. When third-party contracts interact with farming or staking positions through onBehalf operations, they receive and hold the position tokens. These tokens maintain the original owner information in their attributes, protecting the user's ownership rights. To exit their position, users must first reclaim their position tokens from the third-party contract through that protocol's specific mechanisms. Once users have regained control of their position tokens, they can perform the standard exit operation directly through the specific xExchange contract. +This design ensures users maintain ultimate control over their funds while allowing protocols to build complex DeFi interactions. + ## Storage The contract maintains its standard dual yield token storage and relies on underlying contracts and the Permissions Hub for any additional data. diff --git a/farm-staking/farm-staking/README.md b/farm-staking/farm-staking/README.md index adb9cd5a5..b2f755e63 100644 --- a/farm-staking/farm-staking/README.md +++ b/farm-staking/farm-staking/README.md @@ -280,6 +280,10 @@ The function executes the following steps: 7. Distributes new farm token to caller and rewards to the original owner 8. Emits claim events with relevant information +## exitOnBehalf +The exit operation remains under the direct control of the position owner to ensure maximum security. When third-party contracts interact with staking positions through onBehalf operations, they receive and hold the position tokens. These tokens maintain the original owner information in their attributes, protecting the user's ownership rights. To exit their position, users must first reclaim their position tokens from the third-party contract through that protocol's specific mechanisms. Once users have regained control of their position tokens, they can perform the standard exit operation directly through the specific xExchange contract. +This design ensures users maintain ultimate control over their funds while allowing protocols to build complex DeFi interactions. + ## Storage The contract relies on the Permissions Hub for permission management, thus no additional storage, other than the one holding the Permissions Hub SC address, is required. All whitelisting data is managed through the Permissions Hub contract. From f1889052a70321427022b82a5594e2fe11d4c5c7 Mon Sep 17 00:00:00 2001 From: Bianca Ialangi Date: Fri, 10 Jan 2025 13:25:28 +0200 Subject: [PATCH 20/20] bump mx-sc-actions version --- .github/workflows/actions.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index c166a6cfb..f50d01216 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -20,9 +20,9 @@ permissions: jobs: contracts: name: Contracts - uses: multiversx/mx-sc-actions/.github/workflows/contracts.yml@v3.3.1 + uses: multiversx/mx-sc-actions/.github/workflows/contracts.yml@v4.2.1 with: - rust-toolchain: nightly-2024-05-22 + rust-toolchain: stable coverage-args: --ignore-filename-regex='/.cargo/git' --output ./coverage.md secrets: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3bb9fa310..8672e0210 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ permissions: jobs: build: - uses: multiversx/mx-sc-actions/.github/workflows/reproducible-build.yml@v3.3.1 + uses: multiversx/mx-sc-actions/.github/workflows/reproducible-build.yml@v4.2.1 with: image_tag: v7.0.0 attach_to_existing_release: true