diff --git a/locked-asset/energy-factory/src/lib.rs b/locked-asset/energy-factory/src/lib.rs index 0b79aa0d7..a39dcc117 100644 --- a/locked-asset/energy-factory/src/lib.rs +++ b/locked-asset/energy-factory/src/lib.rs @@ -118,7 +118,6 @@ pub trait SimpleLockEnergy: let dest_address = self.dest_from_optional(opt_destination); let current_epoch = self.blockchain().get_block_epoch(); let unlock_epoch = self.unlock_epoch_to_start_of_month(current_epoch + lock_epochs); - require!( unlock_epoch > current_epoch, "Unlock epoch must be greater than the current epoch" @@ -184,4 +183,48 @@ pub trait SimpleLockEnergy: output_payment } + + /// Used internally by proxy-dex + #[payable("*")] + #[endpoint(extendLockPeriod)] + fn extend_lock_period(&self, lock_epochs: Epoch, user: ManagedAddress) -> EsdtTokenPayment { + self.require_not_paused(); + self.require_is_listed_lock_option(lock_epochs); + + let caller = self.blockchain().get_caller(); + require!( + self.token_transfer_whitelist().contains(&caller), + "May not call this endpoint. Use lockTokens instead" + ); + + let payment = self.call_value().single_esdt(); + self.locked_token() + .require_same_token(&payment.token_identifier); + + let current_epoch = self.blockchain().get_block_epoch(); + let unlock_epoch = self.unlock_epoch_to_start_of_month(current_epoch + lock_epochs); + require!( + unlock_epoch > current_epoch, + "Unlock epoch must be greater than the current epoch" + ); + + let output_tokens = self.update_energy(&user, |energy: &mut Energy| { + self.extend_new_token_period(payment.clone(), unlock_epoch, current_epoch, energy) + }); + + self.send().esdt_local_burn( + &payment.token_identifier, + payment.token_nonce, + &payment.amount, + ); + + self.send().direct_esdt( + &caller, + &output_tokens.token_identifier, + output_tokens.token_nonce, + &output_tokens.amount, + ); + + output_tokens + } } diff --git a/locked-asset/proxy_dex/src/energy_update.rs b/locked-asset/proxy_dex/src/energy_update.rs index 4242350ca..9bc045b6d 100644 --- a/locked-asset/proxy_dex/src/energy_update.rs +++ b/locked-asset/proxy_dex/src/energy_update.rs @@ -1,7 +1,7 @@ multiversx_sc::imports!(); -use common_structs::Nonce; -use energy_factory::locked_token_transfer::ProxyTrait as _; +use common_structs::{Epoch, Nonce}; +use energy_factory::{locked_token_transfer::ProxyTrait as _, ProxyTrait as _}; use energy_query::Energy; use simple_lock::locked_token::LockedTokenAttributes; @@ -46,9 +46,6 @@ pub trait EnergyUpdateModule: .get_token_attributes(token_id, token_nonce); energy.update_after_unlock_any(token_amount, attributes.unlock_epoch, current_epoch); } else if token_id == &old_locked_token_id { - if self.blockchain().is_smart_contract(user) { - return; - } let attributes = self.decode_legacy_token(token_id, token_nonce); let epoch_amount_pairs = attributes.get_unlock_amounts_per_epoch(token_amount); for pair in epoch_amount_pairs.pairs { @@ -62,6 +59,19 @@ pub trait EnergyUpdateModule: self.set_energy_in_factory(user.clone(), energy, energy_factory_addr); } + fn call_increase_energy( + &self, + user: ManagedAddress, + old_tokens: EsdtTokenPayment, + lock_epochs: Epoch, + energy_factory_addr: ManagedAddress, + ) -> EsdtTokenPayment { + self.energy_factory_proxy(energy_factory_addr) + .extend_lock_period(lock_epochs, user) + .with_esdt_transfer(old_tokens) + .execute_on_dest_context() + } + fn set_energy_in_factory( &self, user: ManagedAddress, diff --git a/locked-asset/proxy_dex/src/proxy_farm.rs b/locked-asset/proxy_dex/src/proxy_farm.rs index beb6cf425..1c1fdadc9 100644 --- a/locked-asset/proxy_dex/src/proxy_farm.rs +++ b/locked-asset/proxy_dex/src/proxy_farm.rs @@ -3,6 +3,7 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); +use common_structs::Epoch; use fixed_supply_token::FixedSupplyToken; use crate::{ @@ -321,6 +322,88 @@ pub trait ProxyFarmModule: (new_wrapped_token, claim_result.rewards).into() } + #[payable("*")] + #[endpoint(increaseProxyFarmTokenEnergy)] + fn increase_proxy_farm_token_energy_endpoint(&self, lock_epochs: Epoch) -> EsdtTokenPayment { + self.require_wrapped_farm_token_id_not_empty(); + self.require_wrapped_lp_token_id_not_empty(); + + let wrapped_farm_token_mapper = self.wrapped_farm_token(); + let payment = self.call_value().single_esdt(); + wrapped_farm_token_mapper.require_same_token(&payment.token_identifier); + + let wrapped_farm_attributes: WrappedFarmTokenAttributes = + self.get_attributes_as_part_of_fixed_supply(&payment, &wrapped_farm_token_mapper); + + let caller = self.blockchain().get_caller(); + let new_locked_token_id = self.get_locked_token_id(); + let wrapped_lp_token_id = self.wrapped_lp_token().get_token_id(); + + let new_attributes = if wrapped_farm_attributes.proxy_farming_token.token_identifier + == new_locked_token_id + { + let energy_factory_addr = self.energy_factory_address().get(); + let new_locked_token = self.call_increase_energy( + caller.clone(), + wrapped_farm_attributes.proxy_farming_token, + lock_epochs, + energy_factory_addr, + ); + + WrappedFarmTokenAttributes { + farm_token: wrapped_farm_attributes.farm_token, + proxy_farming_token: new_locked_token, + } + } else if wrapped_farm_attributes.proxy_farming_token.token_identifier + == wrapped_lp_token_id + { + let wrapped_lp_mapper = self.wrapped_lp_token(); + let wrapped_lp_attributes: WrappedLpTokenAttributes = + self.get_attributes_as_part_of_fixed_supply(&payment, &wrapped_lp_mapper); + let new_locked_tokens = self.increase_proxy_pair_token_energy( + caller.clone(), + lock_epochs, + &wrapped_lp_attributes, + ); + + let new_wrapped_lp_attributes = WrappedLpTokenAttributes { + locked_tokens: new_locked_tokens, + lp_token_id: wrapped_lp_attributes.lp_token_id, + lp_token_amount: wrapped_lp_attributes.lp_token_amount, + }; + + let new_token_amount = new_wrapped_lp_attributes.get_total_supply(); + let new_wrapped_lp = wrapped_lp_mapper.nft_create_and_send( + &caller, + new_token_amount, + &new_wrapped_lp_attributes, + ); + + self.send().esdt_local_burn( + &wrapped_farm_attributes.proxy_farming_token.token_identifier, + wrapped_farm_attributes.proxy_farming_token.token_nonce, + &wrapped_farm_attributes.proxy_farming_token.amount, + ); + + WrappedFarmTokenAttributes { + farm_token: wrapped_farm_attributes.farm_token, + proxy_farming_token: new_wrapped_lp, + } + } else { + sc_panic!(INVALID_PAYMENTS_ERR_MSG) + }; + + self.send().esdt_local_burn( + &payment.token_identifier, + payment.token_nonce, + &payment.amount, + ); + + let new_token_amount = new_attributes.get_total_supply(); + + wrapped_farm_token_mapper.nft_create_and_send(&caller, new_token_amount, &new_attributes) + } + fn require_wrapped_farm_token_id_not_empty(&self) { require!(!self.wrapped_farm_token().is_empty(), "Empty token id"); } diff --git a/locked-asset/proxy_dex/src/proxy_pair.rs b/locked-asset/proxy_dex/src/proxy_pair.rs index 5045e0961..e6a2f4d1b 100644 --- a/locked-asset/proxy_dex/src/proxy_pair.rs +++ b/locked-asset/proxy_dex/src/proxy_pair.rs @@ -6,6 +6,7 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); use crate::wrapped_lp_attributes::{WrappedLpToken, WrappedLpTokenAttributes}; +use common_structs::Epoch; use fixed_supply_token::FixedSupplyToken; #[multiversx_sc::module] @@ -221,6 +222,59 @@ pub trait ProxyPairModule: output_payments.into() } + #[payable("*")] + #[endpoint(increaseProxyPairTokenEnergy)] + fn increase_proxy_pair_token_energy_endpoint(&self, lock_epochs: Epoch) -> EsdtTokenPayment { + self.require_wrapped_lp_token_id_not_empty(); + + let payment = self.call_value().single_esdt(); + let wrapped_lp_mapper = self.wrapped_lp_token(); + wrapped_lp_mapper.require_same_token(&payment.token_identifier); + + let caller = self.blockchain().get_caller(); + let old_attributes: WrappedLpTokenAttributes = + self.get_attributes_as_part_of_fixed_supply(&payment, &wrapped_lp_mapper); + + let new_locked_tokens = + self.increase_proxy_pair_token_energy(caller.clone(), lock_epochs, &old_attributes); + let new_token_attributes = WrappedLpTokenAttributes { + locked_tokens: new_locked_tokens, + lp_token_id: old_attributes.lp_token_id, + lp_token_amount: old_attributes.lp_token_amount, + }; + + self.send().esdt_local_burn( + &payment.token_identifier, + payment.token_nonce, + &payment.amount, + ); + + let new_token_amount = new_token_attributes.get_total_supply(); + + wrapped_lp_mapper.nft_create_and_send(&caller, new_token_amount, &new_token_attributes) + } + + fn increase_proxy_pair_token_energy( + &self, + user: ManagedAddress, + lock_epochs: Epoch, + old_attributes: &WrappedLpTokenAttributes, + ) -> EsdtTokenPayment { + let new_locked_token_id = self.get_locked_token_id(); + require!( + old_attributes.locked_tokens.token_identifier == new_locked_token_id, + "Invalid payment" + ); + + let energy_factory_addr = self.energy_factory_address().get(); + self.call_increase_energy( + user, + old_attributes.locked_tokens.clone(), + lock_epochs, + energy_factory_addr, + ) + } + fn require_wrapped_lp_token_id_not_empty(&self) { require!(!self.wrapped_lp_token().is_empty(), "Empty token id"); } 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 413ddb980..c93cfc924 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 @@ -87,7 +87,7 @@ where farm_locked_builder: FarmLockedObjBuilder, simple_lock_builder: SimpleLockObjBuilder, ) -> Self { - let _ = DebugApi::dummy(); + DebugApi::dummy(); let rust_zero = rust_biguint!(0); let mut b_mock = BlockchainStateWrapper::new(); diff --git a/locked-asset/proxy_dex/tests/proxy_farm_test.rs b/locked-asset/proxy_dex/tests/proxy_farm_test.rs index bfa289fde..f0731d9d6 100644 --- a/locked-asset/proxy_dex/tests/proxy_farm_test.rs +++ b/locked-asset/proxy_dex/tests/proxy_farm_test.rs @@ -1180,3 +1180,240 @@ fn different_farm_locked_token_nonce_merging_test() { }), ); } + +#[test] +fn increase_proxy_farm_lkmex_energy() { + let mut setup = ProxySetup::new( + proxy_dex::contract_obj, + pair::contract_obj, + farm_with_locked_rewards::contract_obj, + energy_factory::contract_obj, + ); + let first_user = setup.first_user.clone(); + let farm_addr = setup.farm_locked_wrapper.address_ref().clone(); + + //////////////////////////////////////////// ENTER FARM ///////////////////////////////////// + + setup + .b_mock + .execute_esdt_transfer( + &first_user, + &setup.proxy_wrapper, + LOCKED_TOKEN_ID, + 1, + &rust_biguint!(USER_BALANCE), + |sc| { + sc.enter_farm_proxy_endpoint(managed_address!(&farm_addr)); + }, + ) + .assert_ok(); + + let block_epoch = 1; + + // check user energy before + setup + .b_mock + .execute_query(&setup.simple_lock_wrapper, |sc| { + let lock_epochs = LOCK_OPTIONS[0] - block_epoch; + let expected_energy_amount = + BigInt::from((USER_BALANCE) as i64) * BigInt::from(lock_epochs as i64); + let expected_energy = Energy::new( + expected_energy_amount, + block_epoch, + managed_biguint!(USER_BALANCE), + ); + let actual_energy = sc.user_energy(&managed_address!(&first_user)).get(); + assert_eq!(expected_energy, actual_energy); + }) + .assert_ok(); + + //////////////////////////////////////////// INCREASE ENERGY ///////////////////////////////////// + setup + .b_mock + .execute_esdt_transfer( + &first_user, + &setup.proxy_wrapper, + WRAPPED_FARM_TOKEN_ID, + 1, + &rust_biguint!(USER_BALANCE), + |sc| { + sc.increase_proxy_farm_token_energy_endpoint(LOCK_OPTIONS[1]); + }, + ) + .assert_ok(); + + // check user energy after + setup + .b_mock + .execute_query(&setup.simple_lock_wrapper, |sc| { + let lock_epochs = LOCK_OPTIONS[1] - block_epoch; + let expected_energy_amount = + BigInt::from((USER_BALANCE) as i64) * BigInt::from(lock_epochs as i64); + let expected_energy = Energy::new( + expected_energy_amount, + block_epoch, + managed_biguint!(USER_BALANCE), + ); + let actual_energy = sc.user_energy(&managed_address!(&first_user)).get(); + assert_eq!(expected_energy, actual_energy); + }) + .assert_ok(); +} + +#[test] +fn increase_proxy_farm_proxy_lp_energy() { + let mut setup = ProxySetup::new( + proxy_dex::contract_obj, + pair::contract_obj, + farm_with_locked_rewards::contract_obj, + energy_factory::contract_obj, + ); + + setup + .b_mock + .execute_tx( + &setup.owner, + &setup.farm_locked_wrapper, + &rust_biguint!(0), + |sc| { + sc.farming_token_id().set(&managed_token_id!(LP_TOKEN_ID)); + + // set produce rewards to false for easier calculation + sc.produce_rewards_enabled().set(false); + }, + ) + .assert_ok(); + + setup.b_mock.set_esdt_local_roles( + setup.farm_locked_wrapper.address_ref(), + LP_TOKEN_ID, + &[EsdtLocalRole::Burn], + ); + + let first_user = setup.first_user.clone(); + let locked_token_amount = rust_biguint!(1_000_000_000); + let other_token_amount = rust_biguint!(500_000_000); + let expected_lp_token_amount = rust_biguint!(499_999_000); + + // set the price to 1 EGLD = 2 MEX + let payments = vec![ + TxTokenTransfer { + token_identifier: LOCKED_TOKEN_ID.to_vec(), + nonce: 1, + value: locked_token_amount.clone(), + }, + TxTokenTransfer { + token_identifier: WEGLD_TOKEN_ID.to_vec(), + nonce: 0, + value: other_token_amount.clone(), + }, + ]; + + // add liquidity + let pair_addr = setup.pair_wrapper.address_ref().clone(); + setup + .b_mock + .execute_esdt_multi_transfer(&first_user, &setup.proxy_wrapper, &payments, |sc| { + sc.add_liquidity_proxy( + managed_address!(&pair_addr), + managed_biguint!(locked_token_amount.to_u64().unwrap()), + managed_biguint!(other_token_amount.to_u64().unwrap()), + ); + }) + .assert_ok(); + + let block_epoch = 1u64; + let user_balance = USER_BALANCE; + + // check energy before + setup + .b_mock + .execute_query(&setup.simple_lock_wrapper, |sc| { + let unlock_epoch = LOCK_OPTIONS[0]; + let lock_epochs = unlock_epoch - block_epoch; + let expected_energy_amount = + BigInt::from((user_balance) as i64) * BigInt::from(lock_epochs as i64); + let expected_energy = Energy::new( + expected_energy_amount, + block_epoch, + managed_biguint!(user_balance), + ); + let actual_energy = sc.user_energy(&managed_address!(&first_user)).get(); + assert_eq!(expected_energy, actual_energy); + }) + .assert_ok(); + + let farm_locked_addr = setup.farm_locked_wrapper.address_ref().clone(); + + //////////////////////////////////////////// ENTER FARM ///////////////////////////////////// + + setup + .b_mock + .execute_esdt_transfer( + &first_user, + &setup.proxy_wrapper, + WRAPPED_LP_TOKEN_ID, + 1, + &expected_lp_token_amount, + |sc| { + sc.enter_farm_proxy_endpoint(managed_address!(&farm_locked_addr)); + }, + ) + .assert_ok(); + + //////////////////////////////////////////// INCREASE ENERGY ///////////////////////////////////// + setup + .b_mock + .execute_esdt_transfer( + &first_user, + &setup.proxy_wrapper, + WRAPPED_FARM_TOKEN_ID, + 1, + &expected_lp_token_amount, + |sc| { + sc.increase_proxy_farm_token_energy_endpoint(LOCK_OPTIONS[1]); + }, + ) + .assert_ok(); + + // check energy after + let user_locked_tokens_in_lp = locked_token_amount.to_u64().unwrap(); + setup + .b_mock + .execute_query(&setup.simple_lock_wrapper, |sc| { + let first_lock_epochs = LOCK_OPTIONS[1] - block_epoch; + let second_lock_epochs = LOCK_OPTIONS[0] - block_epoch; + let expected_energy_amount = BigInt::from((user_locked_tokens_in_lp) as i64) + * BigInt::from(first_lock_epochs as i64) + + BigInt::from((USER_BALANCE - user_locked_tokens_in_lp) as i64) + * BigInt::from(second_lock_epochs as i64); + let expected_energy = Energy::new( + expected_energy_amount, + block_epoch, + managed_biguint!(USER_BALANCE), + ); + let actual_energy = sc.user_energy(&managed_address!(&first_user)).get(); + assert_eq!(expected_energy, actual_energy); + }) + .assert_ok(); + + // check user token after increase energy + setup.b_mock.check_nft_balance( + &first_user, + WRAPPED_FARM_TOKEN_ID, + 2, + &expected_lp_token_amount, + Some(&WrappedFarmTokenAttributes:: { + proxy_farming_token: EsdtTokenPayment { + token_identifier: managed_token_id!(WRAPPED_LP_TOKEN_ID), + token_nonce: 2, + amount: managed_biguint!(expected_lp_token_amount.to_u64().unwrap()), + }, + farm_token: EsdtTokenPayment { + token_identifier: managed_token_id!(FARM_LOCKED_TOKEN_ID), + token_nonce: 1, + amount: managed_biguint!(expected_lp_token_amount.to_u64().unwrap()), + }, + }), + ); +} diff --git a/locked-asset/proxy_dex/tests/proxy_lp_test.rs b/locked-asset/proxy_dex/tests/proxy_lp_test.rs index 1f025ca67..9c3710bb7 100644 --- a/locked-asset/proxy_dex/tests/proxy_lp_test.rs +++ b/locked-asset/proxy_dex/tests/proxy_lp_test.rs @@ -798,3 +798,146 @@ fn wrapped_different_nonce_lp_token_merge_test() { }), ); } + +#[test] +fn increase_proxy_lp_token_energy() { + let mut setup = ProxySetup::new( + proxy_dex::contract_obj, + pair::contract_obj, + farm_with_locked_rewards::contract_obj, + energy_factory::contract_obj, + ); + let first_user = setup.first_user.clone(); + let full_balance = rust_biguint!(USER_BALANCE); + let locked_token_amount = rust_biguint!(1_000_000_000); + let other_token_amount = rust_biguint!(500_000_000); + let expected_lp_token_amount = rust_biguint!(499_999_000); + + // set the price to 1 EGLD = 2 MEX + let payments = vec![ + TxTokenTransfer { + token_identifier: LOCKED_TOKEN_ID.to_vec(), + nonce: 1, + value: locked_token_amount.clone(), + }, + TxTokenTransfer { + token_identifier: WEGLD_TOKEN_ID.to_vec(), + nonce: 0, + value: other_token_amount.clone(), + }, + ]; + + // add liquidity + let pair_addr = setup.pair_wrapper.address_ref().clone(); + setup + .b_mock + .execute_esdt_multi_transfer(&first_user, &setup.proxy_wrapper, &payments, |sc| { + sc.add_liquidity_proxy( + managed_address!(&pair_addr), + managed_biguint!(locked_token_amount.to_u64().unwrap()), + managed_biguint!(other_token_amount.to_u64().unwrap()), + ); + }) + .assert_ok(); + + // check user's balance + setup.b_mock.check_nft_balance::( + &first_user, + LOCKED_TOKEN_ID, + 1, + &(&full_balance - &locked_token_amount), + None, + ); + setup.b_mock.check_esdt_balance( + &first_user, + WEGLD_TOKEN_ID, + &(&full_balance - &other_token_amount), + ); + setup.b_mock.check_nft_balance( + &first_user, + WRAPPED_LP_TOKEN_ID, + 1, + &expected_lp_token_amount, + Some(&WrappedLpTokenAttributes:: { + locked_tokens: EsdtTokenPayment { + token_identifier: managed_token_id!(LOCKED_TOKEN_ID), + token_nonce: 1, + amount: managed_biguint!(locked_token_amount.to_u64().unwrap()), + }, + lp_token_id: managed_token_id!(LP_TOKEN_ID), + lp_token_amount: managed_biguint!(expected_lp_token_amount.to_u64().unwrap()), + }), + ); + + let block_epoch = 1; + let user_locked_tokens_in_lp = locked_token_amount.to_u64().unwrap(); + + // check user energy before + setup + .b_mock + .execute_query(&setup.simple_lock_wrapper, |sc| { + let lock_epochs = LOCK_OPTIONS[0] - block_epoch; + let expected_energy_amount = + BigInt::from((USER_BALANCE) as i64) * BigInt::from(lock_epochs as i64); + let expected_energy = Energy::new( + expected_energy_amount, + block_epoch, + managed_biguint!(USER_BALANCE), + ); + let actual_energy = sc.user_energy(&managed_address!(&first_user)).get(); + assert_eq!(expected_energy, actual_energy); + }) + .assert_ok(); + + // call increase energy + setup + .b_mock + .execute_esdt_transfer( + &setup.first_user, + &setup.proxy_wrapper, + WRAPPED_LP_TOKEN_ID, + 1, + &expected_lp_token_amount, + |sc| { + let _ = sc.increase_proxy_pair_token_energy_endpoint(LOCK_OPTIONS[1]); + }, + ) + .assert_ok(); + + // chceck new wrapped lp token + setup.b_mock.check_nft_balance( + &first_user, + WRAPPED_LP_TOKEN_ID, + 2, + &expected_lp_token_amount, + Some(&WrappedLpTokenAttributes:: { + locked_tokens: EsdtTokenPayment { + token_identifier: managed_token_id!(LOCKED_TOKEN_ID), + token_nonce: 2, + amount: managed_biguint!(locked_token_amount.to_u64().unwrap()), + }, + lp_token_id: managed_token_id!(LP_TOKEN_ID), + lp_token_amount: managed_biguint!(expected_lp_token_amount.to_u64().unwrap()), + }), + ); + + // check user energy after + setup + .b_mock + .execute_query(&setup.simple_lock_wrapper, |sc| { + let first_lock_epochs = LOCK_OPTIONS[1] - block_epoch; + let second_lock_epochs = LOCK_OPTIONS[0] - block_epoch; + let expected_energy_amount = BigInt::from((user_locked_tokens_in_lp) as i64) + * BigInt::from(first_lock_epochs as i64) + + BigInt::from((USER_BALANCE - user_locked_tokens_in_lp) as i64) + * BigInt::from(second_lock_epochs as i64); + let expected_energy = Energy::new( + expected_energy_amount, + block_epoch, + managed_biguint!(USER_BALANCE), + ); + let actual_energy = sc.user_energy(&managed_address!(&first_user)).get(); + assert_eq!(expected_energy, actual_energy); + }) + .assert_ok(); +}