diff --git a/Cargo.lock b/Cargo.lock index 724f6ca9d..4b4c525d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -530,8 +530,11 @@ dependencies = [ "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", + "original_owner_helper", "pair", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -586,8 +589,11 @@ dependencies = [ "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", + "original_owner_helper", "pair", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -628,6 +634,8 @@ dependencies = [ "num-bigint", "pair", "pausable", + "permissions-hub", + "permissions_hub_module", "rewards", "sc_whitelist_module", "simple-lock", @@ -766,7 +774,10 @@ dependencies = [ "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", + "original_owner_helper", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -1365,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" @@ -1443,6 +1462,31 @@ 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_hub_module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "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/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/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/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 eedd45925..d39dd6ed1 100644 --- a/dex/farm-with-locked-rewards/Cargo.toml +++ b/dex/farm-with-locked-rewards/Cargo.toml @@ -41,6 +41,12 @@ path = "../../common/modules/utils" [dependencies.permissions_module] 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" @@ -74,6 +80,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..be29004f0 --- /dev/null +++ b/dex/farm-with-locked-rewards/src/external_interaction.rs @@ -0,0 +1,110 @@ +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 + + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + + 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); + + 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()); + 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 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()); + + 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() + } +} diff --git a/dex/farm-with-locked-rewards/src/lib.rs b/dex/farm-with-locked-rewards/src/lib.rs index 7434704d1..69b5dc4a1 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; @@ -27,11 +29,14 @@ pub trait Farm: + utils::UtilsModule + 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 + 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/tests/farm_with_locked_rewards_setup/mod.rs b/dex/farm-with-locked-rewards/tests/farm_with_locked_rewards_setup/mod.rs index 5f6015658..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 @@ -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,12 @@ 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 permissions_hub_module::PermissionsHubModule; use rewards::RewardsModule; use sc_whitelist_module::SCWhitelistModule; use simple_lock::locked_token::LockedTokenModule; @@ -35,8 +38,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 +61,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 +77,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 +118,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 +188,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 +256,7 @@ where last_farm_token_nonce: 0, farm_wrapper, energy_factory_wrapper, + permissions_hub_wrapper, } } @@ -450,6 +480,99 @@ 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| { + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); + }, + ) + .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), + ); +} diff --git a/dex/farm-with-locked-rewards/wasm/Cargo.lock b/dex/farm-with-locked-rewards/wasm/Cargo.lock index 897ce8c8e..1ec1e747a 100644 --- a/dex/farm-with-locked-rewards/wasm/Cargo.lock +++ b/dex/farm-with-locked-rewards/wasm/Cargo.lock @@ -136,8 +136,11 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -181,7 +184,10 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -399,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" @@ -423,6 +437,21 @@ dependencies = [ "permissions_module", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +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 e8aa3099f..81ef9c901 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] @@ -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 @@ -66,6 +67,8 @@ 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 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 f883c4579..b91a99066 100644 --- a/dex/farm/Cargo.toml +++ b/dex/farm/Cargo.toml @@ -38,6 +38,12 @@ path = "../../common/modules/pausable" [dependencies.permissions_module] 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" @@ -68,6 +74,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..0af8fe5a5 --- /dev/null +++ b/dex/farm/src/external_interaction.rs @@ -0,0 +1,90 @@ +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 + + permissions_hub_module::PermissionsHubModule + + original_owner_helper::OriginalOwnerHelperModule + + 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); + + 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 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 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()); + + 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() + } +} diff --git a/dex/farm/src/lib.rs b/dex/farm/src/lib.rs index 2ca53489d..5eb7ea834 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; @@ -29,11 +30,14 @@ pub trait Farm: + farm_token::FarmTokenModule + 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 + 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/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/external_interaction_test.rs b/dex/farm/tests/external_interaction_test.rs new file mode 100644 index 000000000..6a36ae680 --- /dev/null +++ b/dex/farm/tests/external_interaction_test.rs @@ -0,0 +1,309 @@ +#![allow(deprecated)] + +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, managed_biguint, rust_biguint, DebugApi, +}; + +#[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() { + DebugApi::dummy(); + + let mut farm_setup = MultiUserFarmSetup::new( + farm::contract_obj, + energy_factory_mock::contract_obj, + energy_update::contract_obj, + permissions_hub::contract_obj, + ); + + farm_setup.set_boosted_yields_rewards_percentage(BOOSTED_YIELDS_PERCENTAGE); + farm_setup.set_boosted_yields_factors(); + let mut block_nonce = 0u64; + farm_setup.b_mock.set_block_nonce(block_nonce); + + // new external user + let external_user = farm_setup.b_mock.create_user_account(&rust_biguint!(0)); + farm_setup.set_user_energy(&external_user, 1_000, 1, 1); + + // authorized address + let farm_token_amount = 100_000_000; + let authorized_address = farm_setup.first_user.clone(); + + farm_setup.whitelist_address_on_behalf(&external_user, &authorized_address); + + farm_setup.check_farm_token_supply(0); + farm_setup.enter_farm_on_behalf(&authorized_address, &external_user, farm_token_amount, 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), + ); + + 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] +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..3398fd679 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,8 @@ 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 permissions_hub_module::PermissionsHubModule; use sc_whitelist_module::SCWhitelistModule; use week_timekeeping::Epoch; use weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule; @@ -54,11 +57,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 +79,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 +125,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 +165,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 +223,7 @@ where farm_wrapper, energy_factory_wrapper, eu_wrapper, + permissions_hub_wrapper, } } @@ -616,6 +651,117 @@ 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| { + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); + }, + ) + .assert_ok(); + } + + pub fn remove_whitelist_address_on_behalf( + &mut self, + user: &Address, + address_to_remove: &Address, + ) { + self.b_mock + .execute_tx( + user, + &self.permissions_hub_wrapper, + &rust_biguint!(0), + |sc| { + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_remove)); + sc.remove_whitelist(addresses); + }, + ) + .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/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); diff --git a/dex/farm/wasm/Cargo.lock b/dex/farm/wasm/Cargo.lock index c3dea8cdc..c85e89764 100644 --- a/dex/farm/wasm/Cargo.lock +++ b/dex/farm/wasm/Cargo.lock @@ -136,8 +136,11 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -369,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" @@ -393,6 +404,21 @@ dependencies = [ "permissions_module", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +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 525d76239..235653731 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] @@ -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 @@ -63,6 +64,8 @@ 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 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..7dfe4392d --- /dev/null +++ b/dex/permissions-hub/src/lib.rs @@ -0,0 +1,67 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::contract] +pub trait PermissionsHub { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + #[endpoint] + fn whitelist(&self, addresses_to_whitelist: MultiValueEncoded) { + let caller = self.blockchain().get_caller(); + 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, addresses_to_remove: MultiValueEncoded) { + let caller = self.blockchain().get_caller(); + 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] + #[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..4ea948115 --- /dev/null +++ b/dex/permissions-hub/wasm/src/lib.rs @@ -0,0 +1,32 @@ +// Code generated by the multiversx-sc build system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Upgrade: 1 +// Endpoints: 6 +// Async Callback (empty): 1 +// Total number of exported functions: 9 + +#![no_std] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + permissions_hub + ( + init => init + upgrade => upgrade + whitelist => whitelist + removeWhitelist => remove_whitelist + blacklist => blacklist + removeBlacklist => remove_blacklist + isWhitelisted => is_whitelisted + getBlacklistedAddresses => blacklisted_addresses + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/farm-staking/farm-staking-proxy/Cargo.toml b/farm-staking/farm-staking-proxy/Cargo.toml index 34ee079f9..34d8c7151 100644 --- a/farm-staking/farm-staking-proxy/Cargo.toml +++ b/farm-staking/farm-staking-proxy/Cargo.toml @@ -63,6 +63,12 @@ path = "../../common/modules/sc_whitelist_module" [dependencies.energy-query] 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 7bfc66bab..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 @@ -21,6 +22,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/claim.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/claim.rs index 01df77c36..a3a40a5de 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,31 @@ 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; + + dual_yield_token_mapper.nft_burn(payment.token_nonce, &payment.amount); + + ClaimDualYieldResult { + lp_farm_rewards, + staking_farm_rewards, + new_dual_yield_tokens, } } } 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..722bc2eb8 --- /dev/null +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/external_interaction.rs @@ -0,0 +1,119 @@ +multiversx_sc::imports!(); + +use common_structs::FarmTokenAttributes; + +use crate::{ + dual_yield_token::DualYieldTokenAttributes, + result_types::{ClaimDualYieldResult, StakeProxyResult}, +}; + +#[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 + + permissions_hub_module::PermissionsHubModule + + 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(); + self.check_stake_farm_payments(&original_owner, &payments); + + 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); + 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 payment = self.call_value().single_esdt(); + + 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 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); + + claim_result + } + + fn check_stake_farm_payments( + &self, + original_owner: &ManagedAddress, + payments: &ManagedVec, + ) { + 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!( + 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!( + &attributes.original_owner == original_owner, + "Provided address is not the same as the original owner" + ); + + for payment in additional_payments.into_iter() { + require!( + &self.get_underlying_farm_position_original_owner(&payment) == original_owner, + "Provided address is not the same as the original owner" + ); + } + } + + 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, + 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/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/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-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..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 @@ -1,12 +1,13 @@ #![allow(deprecated)] +use common_structs::FarmTokenAttributes; use config::ConfigModule; use energy_factory::energy::EnergyModule; 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, @@ -21,12 +22,17 @@ 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 permissions_hub_module::PermissionsHubModule; use sc_whitelist_module::SCWhitelistModule; use crate::{ @@ -46,12 +52,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 +71,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 +83,7 @@ impl< PairObjBuilder, FarmObjBuilder, EnergyFactoryBuilder, + PermissionsHubObjBuilder, StakingContractObjBuilder, ProxyContractObjBuilder, > @@ -80,6 +91,7 @@ impl< PairObjBuilder, FarmObjBuilder, EnergyFactoryBuilder, + PermissionsHubObjBuilder, StakingContractObjBuilder, ProxyContractObjBuilder, > @@ -87,6 +99,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 +107,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 +149,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 +188,7 @@ where pair_wrapper, lp_farm_wrapper, energy_factory_wrapper, + permissions_hub_wrapper, staking_farm_wrapper, proxy_wrapper, } @@ -603,6 +639,84 @@ where ) } + #[allow(clippy::too_many_arguments)] + 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| { + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); + }, + ) + .assert_ok(); + } + pub fn set_user_energy( &mut self, user: &Address, @@ -700,6 +814,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 ) } diff --git a/farm-staking/farm-staking/Cargo.toml b/farm-staking/farm-staking/Cargo.toml index 8f1817a89..51876247b 100644 --- a/farm-staking/farm-staking/Cargo.toml +++ b/farm-staking/farm-staking/Cargo.toml @@ -53,6 +53,12 @@ path = "../../common/modules/pausable" [dependencies.permissions_module] 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" @@ -77,6 +83,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..1ece48d9a --- /dev/null +++ b/farm-staking/farm-staking/src/external_interaction.rs @@ -0,0 +1,133 @@ +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 + + 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 + + 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); + + 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 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 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(), + payments, + ); + + 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() + } +} diff --git a/farm-staking/farm-staking/src/lib.rs b/farm-staking/farm-staking/src/lib.rs index f783c7831..4cf1c3ebf 100644 --- a/farm-staking/farm-staking/src/lib.rs +++ b/farm-staking/farm-staking/src/lib.rs @@ -18,6 +18,7 @@ pub mod claim_only_boosted_staking_rewards; pub mod claim_stake_farm_rewards; pub mod compound_stake_farm_rewards; pub mod custom_rewards; +pub mod external_interaction; pub mod farm_token_roles; pub mod stake_farm; pub mod token_attributes; @@ -35,6 +36,8 @@ pub trait FarmStaking: + sc_whitelist_module::SCWhitelistModule + 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 @@ -49,6 +52,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/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_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..35ed665bc 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,8 @@ use farm_staking::unstake_farm::UnstakeFarmModule; 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 @@ -38,6 +41,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 +56,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 +69,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 +98,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 +138,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 +197,7 @@ where user_address2: user_addr2, farm_wrapper, energy_factory_wrapper, + permissions_hub_wrapper, } } @@ -566,6 +597,97 @@ 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| { + let mut addresses = MultiValueEncoded::new(); + addresses.push(managed_address!(address_to_whitelist)); + sc.whitelist(addresses); + }, + ) + .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(); diff --git a/farm-staking/farm-staking/wasm/Cargo.lock b/farm-staking/farm-staking/wasm/Cargo.lock index 465de9d30..7da9c7389 100644 --- a/farm-staking/farm-staking/wasm/Cargo.lock +++ b/farm-staking/farm-staking/wasm/Cargo.lock @@ -136,8 +136,11 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -181,8 +184,11 @@ dependencies = [ "mergeable", "multiversx-sc", "multiversx-sc-modules", + "original_owner_helper", "pair", "pausable", + "permissions-hub", + "permissions_hub_module", "permissions_module", "rewards", "sc_whitelist_module", @@ -400,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" @@ -424,6 +438,21 @@ dependencies = [ "permissions_module", ] +[[package]] +name = "permissions-hub" +version = "0.0.0" +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 9489b1d7e..e0fdd5bb3 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] @@ -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 @@ -68,6 +69,8 @@ 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 claimBoostedRewards => claim_boosted_rewards collectUndistributedBoostedRewards => collect_undistributed_boosted_rewards getBoostedYieldsRewardsPercentage => boosted_yields_rewards_percentage