diff --git a/Cargo.lock b/Cargo.lock index fe863b981..1904423cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,9 +226,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", "clap_derive", @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstream", "anstyle", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -576,9 +576,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "encoding_rs" @@ -1336,9 +1336,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown", @@ -1788,19 +1788,18 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -2617,9 +2616,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "subtle" @@ -3038,9 +3037,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.121.0" +version = "0.121.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8" +checksum = "99ffe16b4aa1ebab8724f61c9ee38cd5481c89caf10bf1a5af9eab8f0c2e6c05" dependencies = [ "bitflags 2.4.2", "indexmap", @@ -3049,12 +3048,12 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e32c13c59fdc64d3f6998a1d52eb1d362b6904a88b754190ccb85661ad577a" +checksum = "b4a76a9228f2e6653f0b3d912b2f3a9b6ac79c690e5642c9ee2dfd914c545cf0" dependencies = [ "anyhow", - "wasmparser 0.121.0", + "wasmparser 0.121.1", ] [[package]] diff --git a/common/modules/farm/farm_base_impl/src/base_farm_init.rs b/common/modules/farm/farm_base_impl/src/base_farm_init.rs index 700057a3e..1f1431abf 100644 --- a/common/modules/farm/farm_base_impl/src/base_farm_init.rs +++ b/common/modules/farm/farm_base_impl/src/base_farm_init.rs @@ -32,10 +32,10 @@ pub trait BaseFarmInitModule: self.state().set(State::Inactive); self.division_safety_constant() - .set_if_empty(&division_safety_constant); + .set(&division_safety_constant); - self.reward_token_id().set_if_empty(&reward_token_id); - self.farming_token_id().set_if_empty(&farming_token_id); + self.reward_token_id().set(&reward_token_id); + self.farming_token_id().set(&farming_token_id); if !owner.is_zero() { self.add_permissions(owner, Permissions::OWNER | Permissions::PAUSE); diff --git a/common/modules/farm/farm_base_impl/src/base_traits_impl.rs b/common/modules/farm/farm_base_impl/src/base_traits_impl.rs index f2ccc293c..d86c3c685 100644 --- a/common/modules/farm/farm_base_impl/src/base_traits_impl.rs +++ b/common/modules/farm/farm_base_impl/src/base_traits_impl.rs @@ -63,19 +63,18 @@ pub trait FarmContract { ) -> BigUint<::Api> { let current_block_nonce = sc.blockchain().get_block_nonce(); let last_reward_nonce = sc.last_reward_block_nonce().get(); - if current_block_nonce > last_reward_nonce { - let to_mint = - Self::calculate_per_block_rewards(sc, current_block_nonce, last_reward_nonce); - if to_mint != 0 { - Self::mint_rewards(sc, token_id, &to_mint); - } - - sc.last_reward_block_nonce().set(current_block_nonce); + if current_block_nonce <= last_reward_nonce { + return BigUint::zero(); + } - to_mint - } else { - BigUint::zero() + let to_mint = Self::calculate_per_block_rewards(sc, current_block_nonce, last_reward_nonce); + if to_mint != 0 { + Self::mint_rewards(sc, token_id, &to_mint); } + + sc.last_reward_block_nonce().set(current_block_nonce); + + to_mint } fn generate_aggregated_rewards( @@ -83,14 +82,16 @@ pub trait FarmContract { storage_cache: &mut StorageCache, ) { let total_reward = Self::mint_per_block_rewards(sc, &storage_cache.reward_token_id); - if total_reward > 0u64 { - storage_cache.reward_reserve += &total_reward; + if total_reward == 0u64 { + return; + } - if storage_cache.farm_token_supply != 0u64 { - let increase = (&total_reward * &storage_cache.division_safety_constant) - / &storage_cache.farm_token_supply; - storage_cache.reward_per_share += &increase; - } + storage_cache.reward_reserve += &total_reward; + + if storage_cache.farm_token_supply != 0u64 { + let increase = (&total_reward * &storage_cache.division_safety_constant) + / &storage_cache.farm_token_supply; + storage_cache.reward_per_share += &increase; } } @@ -102,12 +103,12 @@ pub trait FarmContract { storage_cache: &StorageCache, ) -> BigUint<::Api> { let token_rps = token_attributes.get_reward_per_share(); - if storage_cache.reward_per_share > token_rps { - let rps_diff = &storage_cache.reward_per_share - &token_rps; - farm_token_amount * &rps_diff / &storage_cache.division_safety_constant - } else { - BigUint::zero() + if storage_cache.reward_per_share <= token_rps { + return BigUint::zero(); } + + let rps_diff = &storage_cache.reward_per_share - &token_rps; + farm_token_amount * &rps_diff / &storage_cache.division_safety_constant } fn create_enter_farm_initial_attributes( @@ -212,7 +213,6 @@ pub trait FarmContract { } } - #[inline] fn increase_user_farm_position( sc: &Self::FarmSc, user: &ManagedAddress<::Api>, diff --git a/common/modules/farm/rewards/src/rewards.rs b/common/modules/farm/rewards/src/rewards.rs index acd9349df..bd3b4bc3f 100644 --- a/common/modules/farm/rewards/src/rewards.rs +++ b/common/modules/farm/rewards/src/rewards.rs @@ -6,7 +6,7 @@ multiversx_sc::imports!(); pub trait RewardsModule: config::ConfigModule + pausable::PausableModule + permissions_module::PermissionsModule { - fn start_produce_rewards(&self) { + fn start_produce_rewards(&self, start_block_nonce_opt: OptionalValue) { require!( self.per_block_reward_amount().get() != 0u64, "Cannot produce zero reward amount" @@ -15,9 +15,20 @@ pub trait RewardsModule: !self.produce_rewards_enabled().get(), "Producing rewards is already enabled" ); - let current_nonce = self.blockchain().get_block_nonce(); + let current_block_nonce = self.blockchain().get_block_nonce(); + let next_reward_block_nonce = match start_block_nonce_opt { + OptionalValue::Some(start_block_nonce) => { + require!( + start_block_nonce >= current_block_nonce, + "The starting block nonce needs to be equal or greater than the current nonce" + ); + start_block_nonce + } + OptionalValue::None => current_block_nonce, + }; + self.produce_rewards_enabled().set(true); - self.last_reward_block_nonce().set(current_nonce); + self.last_reward_block_nonce().set(next_reward_block_nonce); } #[inline] diff --git a/dex/farm-with-locked-rewards/src/lib.rs b/dex/farm-with-locked-rewards/src/lib.rs index 473fa77d2..4630278bd 100644 --- a/dex/farm-with-locked-rewards/src/lib.rs +++ b/dex/farm-with-locked-rewards/src/lib.rs @@ -8,6 +8,7 @@ multiversx_sc::derive_imports!(); use common_structs::FarmTokenAttributes; use contexts::storage_cache::StorageCache; use core::marker::PhantomData; +use week_timekeeping::Epoch; use farm::{ base_functions::{BaseFunctionsModule, ClaimRewardsResultType, DoubleMultiPayment, Wrapper}, @@ -57,6 +58,7 @@ pub trait Farm: division_safety_constant: BigUint, pair_contract_address: ManagedAddress, owner: ManagedAddress, + first_week_start_epoch: Epoch, admins: MultiValueEncoded, ) { self.base_farm_init( @@ -67,18 +69,18 @@ pub trait Farm: admins, ); - self.penalty_percent().set_if_empty(DEFAULT_PENALTY_PERCENT); - self.minimum_farming_epochs() - .set_if_empty(DEFAULT_MINUMUM_FARMING_EPOCHS); - self.burn_gas_limit().set_if_empty(DEFAULT_BURN_GAS_LIMIT); - self.pair_contract_address().set(&pair_contract_address); - let current_epoch = self.blockchain().get_block_epoch(); - self.first_week_start_epoch().set_if_empty(current_epoch); + require!( + first_week_start_epoch >= current_epoch, + "Invalid start epoch" + ); + self.first_week_start_epoch().set(first_week_start_epoch); - // Farm position migration code - let farm_token_mapper = self.farm_token(); - self.try_set_farm_position_migration_nonce(farm_token_mapper); + self.penalty_percent().set(DEFAULT_PENALTY_PERCENT); + self.minimum_farming_epochs() + .set(DEFAULT_MINUMUM_FARMING_EPOCHS); + self.burn_gas_limit().set(DEFAULT_BURN_GAS_LIMIT); + self.pair_contract_address().set(&pair_contract_address); } #[endpoint] @@ -94,6 +96,13 @@ pub trait Farm: &self, opt_orig_caller: OptionalValue, ) -> EnterFarmResultType { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + current_epoch >= first_week_start_epoch, + "Cannot enter farm yet" + ); + let caller = self.blockchain().get_caller(); let orig_caller = self.get_orig_caller_from_opt(&caller, opt_orig_caller); @@ -209,10 +218,14 @@ pub trait Farm: } #[endpoint(claimBoostedRewards)] - fn claim_boosted_rewards( - &self, - opt_user: OptionalValue, - ) -> EsdtTokenPayment { + fn claim_boosted_rewards(&self, opt_user: OptionalValue) -> EsdtTokenPayment { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + first_week_start_epoch <= current_epoch, + "Cannot claim rewards yet" + ); + let caller = self.blockchain().get_caller(); let user = match &opt_user { OptionalValue::Some(user) => user, @@ -236,9 +249,18 @@ pub trait Farm: } #[endpoint(startProduceRewards)] - fn start_produce_rewards_endpoint(&self) { + fn start_produce_rewards_endpoint(&self, start_block_nonce_opt: OptionalValue) { self.require_caller_has_admin_permissions(); - self.start_produce_rewards(); + if start_block_nonce_opt.is_none() { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + current_epoch >= first_week_start_epoch, + "Cannot start the rewards yet" + ); + } + + self.start_produce_rewards(start_block_nonce_opt); } #[endpoint(endProduceRewards)] @@ -313,15 +335,17 @@ where storage_cache: &mut StorageCache, ) { let total_reward = Self::mint_per_block_rewards(sc, &storage_cache.reward_token_id); - if total_reward > 0u64 { - storage_cache.reward_reserve += &total_reward; - let split_rewards = sc.take_reward_slice(total_reward); - - if storage_cache.farm_token_supply != 0u64 { - let increase = (&split_rewards.base_farm * &storage_cache.division_safety_constant) - / &storage_cache.farm_token_supply; - storage_cache.reward_per_share += &increase; - } + if total_reward == 0u64 { + return; + } + + storage_cache.reward_reserve += &total_reward; + let split_rewards = sc.take_reward_slice(total_reward); + + if storage_cache.farm_token_supply != 0u64 { + let increase = (&split_rewards.base_farm * &storage_cache.division_safety_constant) + / &storage_cache.farm_token_supply; + storage_cache.reward_per_share += &increase; } } 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 f92abc5c7..5f0d032d1 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 @@ -139,6 +139,7 @@ where division_safety_constant, pair_address, managed_address!(&owner), + 0, MultiValueEncoded::new(), ); diff --git a/dex/farm/src/base_functions.rs b/dex/farm/src/base_functions.rs index 968e3381d..c41119095 100644 --- a/dex/farm/src/base_functions.rs +++ b/dex/farm/src/base_functions.rs @@ -283,6 +283,12 @@ where sc: &::FarmSc, caller: &ManagedAddress<<::FarmSc as ContractBase>::Api>, ) -> BigUint<<::FarmSc as ContractBase>::Api> { + let current_epoch = sc.blockchain().get_block_epoch(); + let first_week_start_epoch = sc.first_week_start_epoch().get(); + if first_week_start_epoch > current_epoch { + return BigUint::zero(); + } + let user_total_farm_position = sc.get_user_total_farm_position(caller); let user_farm_position = user_total_farm_position.total_farm_position; @@ -304,15 +310,17 @@ where storage_cache: &mut StorageCache, ) { let total_reward = Self::mint_per_block_rewards(sc, &storage_cache.reward_token_id); - if total_reward > 0u64 { - storage_cache.reward_reserve += &total_reward; - let split_rewards = sc.take_reward_slice(total_reward); - - if storage_cache.farm_token_supply != 0u64 { - let increase = (&split_rewards.base_farm * &storage_cache.division_safety_constant) - / &storage_cache.farm_token_supply; - storage_cache.reward_per_share += &increase; - } + if total_reward == 0u64 { + return; + } + + storage_cache.reward_reserve += &total_reward; + let split_rewards = sc.take_reward_slice(total_reward); + + if storage_cache.farm_token_supply != 0u64 { + let increase = (&split_rewards.base_farm * &storage_cache.division_safety_constant) + / &storage_cache.farm_token_supply; + storage_cache.reward_per_share += &increase; } } @@ -323,6 +331,12 @@ where token_attributes: &Self::AttributesType, storage_cache: &StorageCache, ) -> BigUint<::Api> { + let current_epoch = sc.blockchain().get_block_epoch(); + let first_week_start_epoch = sc.first_week_start_epoch().get(); + if first_week_start_epoch > current_epoch { + return BigUint::zero(); + } + let base_farm_reward = DefaultFarmWrapper::::calculate_rewards( sc, caller, diff --git a/dex/farm/src/lib.rs b/dex/farm/src/lib.rs index 48cf893f1..58c85e9b0 100644 --- a/dex/farm/src/lib.rs +++ b/dex/farm/src/lib.rs @@ -9,7 +9,7 @@ pub mod base_functions; pub mod exit_penalty; use base_functions::{ClaimRewardsResultType, DoubleMultiPayment, Wrapper}; -use common_structs::FarmTokenAttributes; +use common_structs::{Epoch, FarmTokenAttributes}; use contexts::storage_cache::StorageCache; use exit_penalty::{ @@ -58,6 +58,7 @@ pub trait Farm: division_safety_constant: BigUint, pair_contract_address: ManagedAddress, owner: ManagedAddress, + first_week_start_epoch: Epoch, admins: MultiValueEncoded, ) { self.base_farm_init( @@ -68,18 +69,18 @@ pub trait Farm: admins, ); - self.penalty_percent().set_if_empty(DEFAULT_PENALTY_PERCENT); - self.minimum_farming_epochs() - .set_if_empty(DEFAULT_MINUMUM_FARMING_EPOCHS); - self.burn_gas_limit().set_if_empty(DEFAULT_BURN_GAS_LIMIT); - self.pair_contract_address().set(&pair_contract_address); - let current_epoch = self.blockchain().get_block_epoch(); - self.first_week_start_epoch().set_if_empty(current_epoch); + require!( + first_week_start_epoch >= current_epoch, + "Invalid start epoch" + ); + self.first_week_start_epoch().set(first_week_start_epoch); - // Farm position migration code - let farm_token_mapper = self.farm_token(); - self.try_set_farm_position_migration_nonce(farm_token_mapper); + self.penalty_percent().set(DEFAULT_PENALTY_PERCENT); + self.minimum_farming_epochs() + .set(DEFAULT_MINUMUM_FARMING_EPOCHS); + self.burn_gas_limit().set(DEFAULT_BURN_GAS_LIMIT); + self.pair_contract_address().set(&pair_contract_address); } #[endpoint] @@ -95,6 +96,13 @@ pub trait Farm: &self, opt_orig_caller: OptionalValue, ) -> EnterFarmResultType { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + current_epoch >= first_week_start_epoch, + "Cannot enter farm yet" + ); + let caller = self.blockchain().get_caller(); let orig_caller = self.get_orig_caller_from_opt(&caller, opt_orig_caller); @@ -198,10 +206,7 @@ pub trait Farm: } #[endpoint(claimBoostedRewards)] - fn claim_boosted_rewards( - &self, - opt_user: OptionalValue, - ) -> EsdtTokenPayment { + fn claim_boosted_rewards(&self, opt_user: OptionalValue) -> EsdtTokenPayment { let caller = self.blockchain().get_caller(); let user = match &opt_user { OptionalValue::Some(user) => user, @@ -225,9 +230,19 @@ pub trait Farm: } #[endpoint(startProduceRewards)] - fn start_produce_rewards_endpoint(&self) { + fn start_produce_rewards_endpoint(&self, start_block_nonce_opt: OptionalValue) { self.require_caller_has_admin_permissions(); - self.start_produce_rewards(); + + if start_block_nonce_opt.is_none() { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + current_epoch >= first_week_start_epoch, + "Cannot start the rewards yet" + ); + }; + + self.start_produce_rewards(start_block_nonce_opt); } #[endpoint(endProduceRewards)] diff --git a/dex/farm/tests/farm_setup/farm_rewards_distr_setup.rs b/dex/farm/tests/farm_setup/farm_rewards_distr_setup.rs index f39c3136e..f5f701e60 100644 --- a/dex/farm/tests/farm_setup/farm_rewards_distr_setup.rs +++ b/dex/farm/tests/farm_setup/farm_rewards_distr_setup.rs @@ -91,6 +91,7 @@ where division_safety_constant, pair_address, ManagedAddress::::zero(), + 0, MultiValueEncoded::new(), ); diff --git a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs index 2e74ab42c..532779c38 100644 --- a/dex/farm/tests/farm_setup/multi_user_farm_setup.rs +++ b/dex/farm/tests/farm_setup/multi_user_farm_setup.rs @@ -115,6 +115,7 @@ where division_safety_constant, pair_address, managed_address!(&owner), + 0, MultiValueEncoded::new(), ); diff --git a/dex/farm/tests/farm_setup/single_user_farm_setup.rs b/dex/farm/tests/farm_setup/single_user_farm_setup.rs index 1e23f999b..0f1b66c95 100644 --- a/dex/farm/tests/farm_setup/single_user_farm_setup.rs +++ b/dex/farm/tests/farm_setup/single_user_farm_setup.rs @@ -51,7 +51,7 @@ impl SingleUserFarmSetup where FarmObjBuilder: 'static + Copy + Fn() -> farm::ContractObj, { - pub fn new(farm_builder: FarmObjBuilder) -> Self { + pub fn new(farm_builder: FarmObjBuilder, first_week_start_boosted_rewards_epoch: u64) -> Self { let rust_zero = rust_biguint!(0u64); let mut blockchain_wrapper = BlockchainStateWrapper::new(); let owner_addr = blockchain_wrapper.create_user_account(&rust_zero); @@ -77,6 +77,7 @@ where division_safety_constant, pair_address, ManagedAddress::::zero(), + first_week_start_boosted_rewards_epoch, MultiValueEncoded::new(), ); @@ -264,6 +265,7 @@ where expected_user_lp_token_balance: &RustBigUint, expected_farm_token_nonce_out: u64, expected_reward_per_share: u64, + expected_entering_epoch: u64, ) { let b_mock = &mut self.blockchain_wrapper; b_mock @@ -298,7 +300,7 @@ where DebugApi::dummy(); let expected_attributes = FarmTokenAttributes:: { reward_per_share: managed_biguint!(expected_reward_per_share), - entering_epoch: 0, + entering_epoch: expected_entering_epoch, compounded_reward: managed_biguint!(0), current_farm_amount: managed_biguint!(farm_token_amount), original_owner: managed_address!(&self.user_address), diff --git a/dex/farm/tests/farm_single_user_test.rs b/dex/farm/tests/farm_single_user_test.rs index c3df21b83..65f3030a0 100644 --- a/dex/farm/tests/farm_single_user_test.rs +++ b/dex/farm/tests/farm_single_user_test.rs @@ -3,8 +3,9 @@ mod farm_setup; use config::ConfigModule; +use farm::Farm; use farm_setup::single_user_farm_setup::*; -use multiversx_sc::types::EsdtLocalRole; +use multiversx_sc::{codec::multi_types::OptionalValue, types::EsdtLocalRole}; use multiversx_sc_scenario::{ managed_address, managed_biguint, managed_token_id, rust_biguint, whitebox_legacy::TxTokenTransfer, DebugApi, @@ -13,12 +14,12 @@ use sc_whitelist_module::SCWhitelistModule; #[test] fn test_farm_setup() { - let _ = SingleUserFarmSetup::new(farm::contract_obj); + let _ = SingleUserFarmSetup::new(farm::contract_obj, 0); } #[test] fn test_enter_farm() { - let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj); + let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj, 0); let farm_in_amount = 100_000_000; let expected_farm_token_nonce = 1; @@ -28,7 +29,7 @@ fn test_enter_farm() { #[test] fn test_exit_farm() { - let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj); + let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj, 0); let farm_in_amount = 100_000_000; let expected_farm_token_nonce = 1; @@ -53,7 +54,7 @@ fn test_exit_farm() { #[test] fn test_exit_farm_with_penalty() { - let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj); + let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj, 0); let farm_in_amount = 100_000_000; let expected_farm_token_nonce = 1; @@ -81,7 +82,7 @@ fn test_exit_farm_with_penalty() { #[test] fn test_claim_rewards() { - let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj); + let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj, 0); let farm_in_amount = 100_000_000; let expected_farm_token_nonce = 1; @@ -102,6 +103,7 @@ fn test_claim_rewards() { &expected_lp_token_balance, expected_farm_token_nonce + 1, expected_reward_per_share, + 0, ); farm_setup.check_farm_token_supply(farm_in_amount); } @@ -112,7 +114,7 @@ fn steps_enter_farm_twice( where FarmObjBuilder: 'static + Copy + Fn() -> farm::ContractObj, { - let mut farm_setup = SingleUserFarmSetup::new(farm_builder); + let mut farm_setup = SingleUserFarmSetup::new(farm_builder, 0); let farm_in_amount = 100_000_000; let expected_farm_token_nonce = 1; @@ -211,7 +213,7 @@ fn test_farm_through_simple_lock() { DebugApi::dummy(); let rust_zero = rust_biguint!(0); - let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj); + let mut farm_setup = SingleUserFarmSetup::new(farm::contract_obj, 0); let b_mock = &mut farm_setup.blockchain_wrapper; // setup simple lock SC @@ -615,3 +617,72 @@ fn test_farm_through_simple_lock() { Some(&lp_proxy_token_attributes), ); } + +#[test] +fn test_custom_start_rewards_block() { + let rust_zero = rust_biguint!(0u64); + + let first_week_start_boosted_rewards_epoch = 7u64; + let start_rewards_block = 20u64; + let mut farm_setup = + SingleUserFarmSetup::new(farm::contract_obj, first_week_start_boosted_rewards_epoch); + let _ = farm_setup.blockchain_wrapper.execute_tx( + &farm_setup.owner_address, + &farm_setup.farm_wrapper, + &rust_zero, + |sc| { + sc.end_produce_rewards_endpoint(); + sc.start_produce_rewards_endpoint(OptionalValue::Some(start_rewards_block)); + }, + ); + + // Check that enter farm is blocked until first_week_start_epoch is reached + farm_setup + .blockchain_wrapper + .execute_esdt_multi_transfer( + &farm_setup.user_address, + &farm_setup.farm_wrapper, + &[], + |sc| { + let _ = sc.enter_farm_endpoint(OptionalValue::None); + }, + ) + .assert_error(4, "Cannot enter farm yet"); + + // Rewards should be generated starting from + let current_epoch = 7; + let mut current_block_nonce = 10; + let farm_in_amount = 100_000_000; + let expected_farm_token_nonce = 1; + + farm_setup.set_block_epoch(current_epoch); + farm_setup.set_block_nonce(current_block_nonce); + + farm_setup.enter_farm( + farm_in_amount, + &[], + expected_farm_token_nonce, + 0, + current_epoch, + 0, + ); + + current_block_nonce = 30; + farm_setup.set_block_nonce(current_block_nonce); + + // Expected rewards should be generated between block 20 and 30 + let expected_mex_out = (current_block_nonce - start_rewards_block) * PER_BLOCK_REWARD_AMOUNT; + let expected_lp_token_balance = rust_biguint!(USER_TOTAL_LP_TOKENS - farm_in_amount); + let expected_reward_per_share = 500_000_000; + farm_setup.claim_rewards( + farm_in_amount, + expected_farm_token_nonce, + expected_mex_out, + &rust_biguint!(expected_mex_out), + &expected_lp_token_balance, + expected_farm_token_nonce + 1, + expected_reward_per_share, + current_epoch, + ); + farm_setup.check_farm_token_supply(farm_in_amount); +} diff --git a/dex/fuzz/src/fuzz_data.rs b/dex/fuzz/src/fuzz_data.rs index 14de4e7c3..c7448addb 100644 --- a/dex/fuzz/src/fuzz_data.rs +++ b/dex/fuzz/src/fuzz_data.rs @@ -442,6 +442,7 @@ pub mod fuzz_data_tests { division_safety_constant, pair_address, ManagedAddress::::zero(), + 0, MultiValueEncoded::new(), ); diff --git a/dex/proxy-deployer/src/farm_deploy.rs b/dex/proxy-deployer/src/farm_deploy.rs index 8500946e8..5e098c7c5 100644 --- a/dex/proxy-deployer/src/farm_deploy.rs +++ b/dex/proxy-deployer/src/farm_deploy.rs @@ -12,6 +12,7 @@ pub trait FarmDeployModule { reward_token_id: TokenIdentifier, farming_token_id: TokenIdentifier, pair_contract_address: ManagedAddress, + first_week_start_epoch: u64, ) -> ManagedAddress { let owner = self.blockchain().get_owner_address(); let caller = self.blockchain().get_caller(); @@ -29,6 +30,7 @@ pub trait FarmDeployModule { DIVISION_SAFETY_CONST, pair_contract_address, owner, + first_week_start_epoch, admins_list, ) .deploy_from_source(&farm_template, code_metadata); diff --git a/dex/scenarios/steps/deploy_contracts.steps.json b/dex/scenarios/steps/deploy_contracts.steps.json index 83231fb69..4a8507699 100644 --- a/dex/scenarios/steps/deploy_contracts.steps.json +++ b/dex/scenarios/steps/deploy_contracts.steps.json @@ -51,7 +51,8 @@ "str:LPTOK-abcdef", "1000000000000", "sc:pair_contract", - "0x0000000000000000000000000000000000000000000000000000000000000000" + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0" ], "gasLimit": "100,000,000", "gasPrice": "0" diff --git a/energy-integration/farm-boosted-yields/src/boosted_yields_factors.rs b/energy-integration/farm-boosted-yields/src/boosted_yields_factors.rs index 60a3626a2..3dd12a634 100644 --- a/energy-integration/farm-boosted-yields/src/boosted_yields_factors.rs +++ b/energy-integration/farm-boosted-yields/src/boosted_yields_factors.rs @@ -123,7 +123,10 @@ pub trait BoostedYieldsFactorsModule: min_farm_amount, }; - let current_week = self.get_current_week(); + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + let epoch_for_current_week = core::cmp::max(current_epoch, first_week_start_epoch); + let current_week = self.get_week_for_epoch(epoch_for_current_week); let config_mapper = self.boosted_yields_config(); if !config_mapper.is_empty() { config_mapper.update(|config| { diff --git a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs index 327897bfb..9d1da7b8d 100644 --- a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs +++ b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs @@ -276,6 +276,7 @@ where division_safety_constant, pair_address, ManagedAddress::::zero(), + 0, MultiValueEncoded::new(), ); diff --git a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_setup/mod.rs b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_setup/mod.rs index 2c67d2714..649a7180d 100644 --- a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_setup/mod.rs +++ b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_staking_contract_setup/mod.rs @@ -46,6 +46,7 @@ where max_apr, UNBOND_EPOCHS, ManagedAddress::::zero(), + 0, MultiValueEncoded::new(), ); diff --git a/farm-staking/farm-staking/src/base_impl_wrapper.rs b/farm-staking/farm-staking/src/base_impl_wrapper.rs index 2b671677f..8694e472d 100644 --- a/farm-staking/farm-staking/src/base_impl_wrapper.rs +++ b/farm-staking/farm-staking/src/base_impl_wrapper.rs @@ -48,6 +48,12 @@ where sc: &::FarmSc, caller: &ManagedAddress<<::FarmSc as ContractBase>::Api>, ) -> BigUint<<::FarmSc as ContractBase>::Api> { + let current_epoch = sc.blockchain().get_block_epoch(); + let first_week_start_epoch = sc.first_week_start_epoch().get(); + if first_week_start_epoch > current_epoch { + return BigUint::zero(); + } + let user_total_farm_position = sc.get_user_total_farm_position(caller); let user_farm_position = user_total_farm_position.total_farm_position; let mut boosted_rewards = BigUint::zero(); @@ -133,8 +139,11 @@ where token_attributes: &Self::AttributesType, storage_cache: &StorageCache, ) -> BigUint<::Api> { - let base_farm_reward = - Self::calculate_base_farm_rewards(farm_token_amount, token_attributes, storage_cache); + let base_farm_reward = Self::calculate_base_farm_rewards( + farm_token_amount, + token_attributes, + storage_cache, + ); let boosted_yield_rewards = Self::calculate_boosted_rewards(sc, caller); base_farm_reward + boosted_yield_rewards diff --git a/farm-staking/farm-staking/src/claim_only_boosted_staking_rewards.rs b/farm-staking/farm-staking/src/claim_only_boosted_staking_rewards.rs index 71e7ab9e3..7dce2ea23 100644 --- a/farm-staking/farm-staking/src/claim_only_boosted_staking_rewards.rs +++ b/farm-staking/farm-staking/src/claim_only_boosted_staking_rewards.rs @@ -27,6 +27,13 @@ pub trait ClaimOnlyBoostedStakingRewardsModule: #[payable("*")] #[endpoint(claimBoostedRewards)] fn claim_boosted_rewards(&self, opt_user: OptionalValue) -> EsdtTokenPayment { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + first_week_start_epoch <= current_epoch, + "Cannot claim rewards yet" + ); + let caller = self.blockchain().get_caller(); let user = match &opt_user { OptionalValue::Some(user) => user, diff --git a/farm-staking/farm-staking/src/custom_rewards.rs b/farm-staking/farm-staking/src/custom_rewards.rs index f62ec0cb7..2636484d9 100644 --- a/farm-staking/farm-staking/src/custom_rewards.rs +++ b/farm-staking/farm-staking/src/custom_rewards.rs @@ -10,7 +10,8 @@ use crate::base_impl_wrapper::FarmStakingWrapper; pub const MAX_PERCENT: u64 = 10_000; pub const BLOCKS_IN_YEAR: u64 = 31_536_000 / 6; // seconds_in_year / 6_seconds_per_block pub const MAX_MIN_UNBOND_EPOCHS: u64 = 30; -pub const WITHDRAW_AMOUNT_TOO_HIGH: &str = "Withdraw amount is higher than the remaining uncollected rewards!"; +pub const WITHDRAW_AMOUNT_TOO_HIGH: &str = + "Withdraw amount is higher than the remaining uncollected rewards!"; #[multiversx_sc::module] pub trait CustomRewardsModule: @@ -55,7 +56,10 @@ pub trait CustomRewardsModule: let reward_capacity_mapper = self.reward_capacity(); let accumulated_rewards_mapper = self.accumulated_rewards(); let remaining_rewards = reward_capacity_mapper.get() - accumulated_rewards_mapper.get(); - require!(withdraw_amount <= remaining_rewards, WITHDRAW_AMOUNT_TOO_HIGH); + require!( + withdraw_amount <= remaining_rewards, + WITHDRAW_AMOUNT_TOO_HIGH + ); reward_capacity_mapper.update(|rewards| { require!( @@ -121,9 +125,19 @@ pub trait CustomRewardsModule: } #[endpoint(startProduceRewards)] - fn start_produce_rewards_endpoint(&self) { + fn start_produce_rewards_endpoint(&self, start_block_nonce_opt: OptionalValue) { self.require_caller_has_admin_permissions(); - self.start_produce_rewards(); + + if start_block_nonce_opt.is_none() { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + current_epoch >= first_week_start_epoch, + "Cannot start the rewards yet" + ); + } + + self.start_produce_rewards(start_block_nonce_opt); } #[view(getAccumulatedRewards)] diff --git a/farm-staking/farm-staking/src/lib.rs b/farm-staking/farm-staking/src/lib.rs index ac1f23727..8dcbf8d1e 100644 --- a/farm-staking/farm-staking/src/lib.rs +++ b/farm-staking/farm-staking/src/lib.rs @@ -6,6 +6,7 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); use base_impl_wrapper::FarmStakingWrapper; +use common_structs::Epoch; use contexts::storage_cache::StorageCache; use farm::base_functions::DoubleMultiPayment; use farm_base_impl::base_traits_impl::FarmContract; @@ -69,6 +70,7 @@ pub trait FarmStaking: max_apr: BigUint, min_unbond_epochs: u64, owner: ManagedAddress, + first_week_start_epoch: Epoch, admins: MultiValueEncoded, ) { // farming and reward token are the same @@ -80,18 +82,21 @@ pub trait FarmStaking: admins, ); + let current_epoch = self.blockchain().get_block_epoch(); + require!( + first_week_start_epoch >= current_epoch, + "Invalid start epoch" + ); + self.first_week_start_epoch().set(first_week_start_epoch); + require!(max_apr > 0u64, "Invalid max APR percentage"); - self.max_annual_percentage_rewards().set_if_empty(&max_apr); + self.max_annual_percentage_rewards().set(&max_apr); require!( min_unbond_epochs <= MAX_MIN_UNBOND_EPOCHS, "Invalid min unbond epochs" ); - self.min_unbond_epochs().set_if_empty(min_unbond_epochs); - - // Farm position migration code - let farm_token_mapper = self.farm_token(); - self.try_set_farm_position_migration_nonce(farm_token_mapper); + self.min_unbond_epochs().set(min_unbond_epochs); } #[endpoint] @@ -123,7 +128,7 @@ pub trait FarmStaking: (merged_farm_token, boosted_rewards_payment).into() } - + #[view(calculateRewardsForGivenPosition)] fn calculate_rewards_for_given_position( &self, diff --git a/farm-staking/farm-staking/src/stake_farm.rs b/farm-staking/farm-staking/src/stake_farm.rs index 7ab3dea1c..cbd0c2d27 100644 --- a/farm-staking/farm-staking/src/stake_farm.rs +++ b/farm-staking/farm-staking/src/stake_farm.rs @@ -71,6 +71,12 @@ pub trait StakeFarmModule: original_caller: ManagedAddress, payments: PaymentsVec, ) -> EnterFarmResultType { + let current_epoch = self.blockchain().get_block_epoch(); + let first_week_start_epoch = self.first_week_start_epoch().get(); + require!( + current_epoch >= first_week_start_epoch, + "Cannot enter staking yet" + ); let caller = self.blockchain().get_caller(); self.migrate_old_farm_positions(&original_caller); let boosted_rewards = self.claim_only_boosted_payment(&original_caller); 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 0ba2b2bc5..28be21b3f 100644 --- a/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs +++ b/farm-staking/farm-staking/tests/farm_staking_setup/mod.rs @@ -43,8 +43,8 @@ pub const USER_REWARDS_ENERGY_CONST: u64 = 3; pub const USER_REWARDS_FARM_CONST: u64 = 2; pub const MIN_ENERGY_AMOUNT_FOR_BOOSTED_YIELDS: u64 = 1; pub const MIN_FARM_AMOUNT_FOR_BOOSTED_YIELDS: u64 = 1; -pub const WITHDRAW_AMOUNT_TOO_HIGH: &str = "Withdraw amount is higher than the remaining uncollected rewards!"; - +pub const WITHDRAW_AMOUNT_TOO_HIGH: &str = + "Withdraw amount is higher than the remaining uncollected rewards!"; pub struct FarmStakingSetup where @@ -91,6 +91,7 @@ where managed_biguint!(MAX_APR), MIN_UNBOND_EPOCHS, ManagedAddress::::zero(), + 0, MultiValueEncoded::new(), ); @@ -518,7 +519,12 @@ where .assert_ok(); } - pub fn withdraw_rewards_with_error(&mut self, withdraw_amount: &RustBigUint, expected_status: u64, expected_message: &str) { + pub fn withdraw_rewards_with_error( + &mut self, + withdraw_amount: &RustBigUint, + expected_status: u64, + expected_message: &str, + ) { self.b_mock .execute_tx( &self.owner_address, diff --git a/locked-asset/proxy_dex/tests/proxy_dex_test_setup/mod.rs b/locked-asset/proxy_dex/tests/proxy_dex_test_setup/mod.rs index 2ab0326d4..a82f050da 100644 --- a/locked-asset/proxy_dex/tests/proxy_dex_test_setup/mod.rs +++ b/locked-asset/proxy_dex/tests/proxy_dex_test_setup/mod.rs @@ -296,6 +296,7 @@ where division_safety_constant, pair_address, managed_address!(owner), + 1, MultiValueEncoded::new(), ); diff --git a/pause-all/tests/pause_all_test.rs b/pause-all/tests/pause_all_test.rs index 37cc38e70..9ec3b4de5 100644 --- a/pause-all/tests/pause_all_test.rs +++ b/pause-all/tests/pause_all_test.rs @@ -51,6 +51,7 @@ fn pause_all_test() { managed_biguint!(DIV_SAFETY), managed_address!(pair_sc.address_ref()), ManagedAddress::::zero(), + 0, MultiValueEncoded::new(), );