diff --git a/Cargo.toml b/Cargo.toml index dbdd0d3ec..d3c9de76a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,9 @@ members = [ "dex/pair-mock", "dex/pair-mock/meta", + "dex/sample-hooks/pair-hooks-sample", + "dex/sample-hooks/pair-hooks-sample/meta", + "energy-integration/energy-factory-mock", "energy-integration/energy-factory-mock/meta", "energy-integration/energy-update", diff --git a/common/modules/banned_addresses/Cargo.toml b/common/modules/banned_addresses/Cargo.toml new file mode 100644 index 000000000..149d2dbcf --- /dev/null +++ b/common/modules/banned_addresses/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "banned_addresses" +version = "0.0.0" +authors = ["Dorin Iancu "] +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "=0.46.1" +features = ["esdt-token-payment-legacy-decode"] + +[dependencies.permissions_module] +path = "../permissions_module" diff --git a/common/modules/banned_addresses/src/lib.rs b/common/modules/banned_addresses/src/lib.rs new file mode 100644 index 000000000..7b8356d10 --- /dev/null +++ b/common/modules/banned_addresses/src/lib.rs @@ -0,0 +1,36 @@ +#![no_std] + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait BannedAddressModule: permissions_module::PermissionsModule { + #[endpoint(addBannedAddress)] + fn add_banned_address(&self, addresses: MultiValueEncoded) { + self.require_caller_has_owner_or_admin_permissions(); + + let mapper = self.banned_addresses(); + for address in addresses { + mapper.add(&address); + } + } + + #[endpoint(removeBannedAddress)] + fn remove_banned_address(&self, addresses: MultiValueEncoded) { + self.require_caller_has_owner_or_admin_permissions(); + + let mapper = self.banned_addresses(); + for address in addresses { + mapper.remove(&address); + } + } + + fn require_not_banned_address(&self, address: &ManagedAddress) { + require!( + !self.banned_addresses().contains(address), + "Cannot add hook for this address" + ); + } + + #[storage_mapper("bannedAddresses")] + fn banned_addresses(&self) -> WhitelistMapper; +} diff --git a/dex/pair/Cargo.toml b/dex/pair/Cargo.toml index 457fb8d8f..2cb51d6a7 100644 --- a/dex/pair/Cargo.toml +++ b/dex/pair/Cargo.toml @@ -32,6 +32,9 @@ path = "../../energy-integration/fees-collector" [dependencies.utils] path = "../../common/modules/utils" +[dependencies.banned_addresses] +path = "../../common/modules/banned_addresses" + [dependencies.itertools] version = "0.10.1" default-features = false diff --git a/dex/pair/src/lib.rs b/dex/pair/src/lib.rs index 98996e34a..8bb3c36e7 100644 --- a/dex/pair/src/lib.rs +++ b/dex/pair/src/lib.rs @@ -12,6 +12,7 @@ pub mod fee; mod liquidity_pool; pub mod locking_wrapper; pub mod pair_actions; +pub mod pair_hooks; pub mod safe_price; pub mod safe_price_view; @@ -45,6 +46,9 @@ pub trait Pair: + pair_actions::swap::SwapModule + pair_actions::views::ViewsModule + pair_actions::common_methods::CommonMethodsModule + + pair_hooks::change_hooks::ChangeHooksModule + + pair_hooks::call_hook::CallHookModule + + banned_addresses::BannedAddressModule + utils::UtilsModule { #[init] @@ -97,6 +101,9 @@ pub trait Pair: ); self.add_permissions_for_all(admins, Permissions::ADMIN); }; + + let sc_address = self.blockchain().get_sc_address(); + self.banned_addresses().add(&sc_address); } #[endpoint] diff --git a/dex/pair/src/pair_actions/add_liq.rs b/dex/pair/src/pair_actions/add_liq.rs index 769e805f4..9d5ae41bc 100644 --- a/dex/pair/src/pair_actions/add_liq.rs +++ b/dex/pair/src/pair_actions/add_liq.rs @@ -1,7 +1,7 @@ use crate::{ - contexts::add_liquidity::AddLiquidityContext, StorageCache, ERROR_BAD_PAYMENT_TOKENS, - ERROR_INITIAL_LIQUIDITY_NOT_ADDED, ERROR_INVALID_ARGS, ERROR_K_INVARIANT_FAILED, - ERROR_LP_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, + contexts::add_liquidity::AddLiquidityContext, pair_hooks::hook_type::PairHookType, + StorageCache, ERROR_BAD_PAYMENT_TOKENS, ERROR_INITIAL_LIQUIDITY_NOT_ADDED, ERROR_INVALID_ARGS, + ERROR_K_INVARIANT_FAILED, ERROR_LP_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, }; use super::common_result_types::AddLiquidityResultType; @@ -21,6 +21,9 @@ pub trait AddLiquidityModule: + permissions_module::PermissionsModule + pausable::PausableModule + super::common_methods::CommonMethodsModule + + crate::pair_hooks::change_hooks::ChangeHooksModule + + crate::pair_hooks::call_hook::CallHookModule + + banned_addresses::BannedAddressModule + utils::UtilsModule { #[payable("*")] @@ -62,6 +65,28 @@ pub trait AddLiquidityModule: ERROR_INITIAL_LIQUIDITY_NOT_ADDED ); + let mut payments_vec = ManagedVec::new(); + payments_vec.push(first_payment); + payments_vec.push(second_payment); + + let mut args = ManagedVec::new(); + + let (hook_type_before, hook_type_after) = if storage_cache.lp_token_supply == 0 { + ( + PairHookType::BeforeAddInitialLiq, + PairHookType::AfterAddInitialLiq, + ) + } else { + self.encode_arg_to_vec(&first_token_amount_min, &mut args); + self.encode_arg_to_vec(&second_token_amount_min, &mut args); + + (PairHookType::BeforeAddLiq, PairHookType::AfterAddLiq) + }; + let payments_after_hook = + self.call_hook(hook_type_before, caller.clone(), payments_vec, args); + let first_payment = payments_after_hook.get(0); + let second_payment = payments_after_hook.get(1); + self.update_safe_price( &storage_cache.first_token_reserve, &storage_cache.second_token_reserve, @@ -103,11 +128,18 @@ pub trait AddLiquidityModule: self.send() .esdt_local_mint(&storage_cache.lp_token_id, 0, &add_liq_context.liq_added); - let lp_payment = EsdtTokenPayment::new( + let mut lp_payment = EsdtTokenPayment::new( storage_cache.lp_token_id.clone(), 0, add_liq_context.liq_added.clone(), ); + let lp_payment_after_hook = self.call_hook( + hook_type_after, + caller.clone(), + ManagedVec::from_single_item(lp_payment), + ManagedVec::new(), + ); + lp_payment = lp_payment_after_hook.get(0); let mut output_payments = self.build_add_liq_output_payments(&storage_cache, &add_liq_context); diff --git a/dex/pair/src/pair_actions/initial_liq.rs b/dex/pair/src/pair_actions/initial_liq.rs index b418fec2a..f62a0e654 100644 --- a/dex/pair/src/pair_actions/initial_liq.rs +++ b/dex/pair/src/pair_actions/initial_liq.rs @@ -2,8 +2,8 @@ use common_errors::ERROR_PERMISSION_DENIED; use pausable::State; use crate::{ - contexts::add_liquidity::AddLiquidityContext, StorageCache, ERROR_ACTIVE, - ERROR_BAD_PAYMENT_TOKENS, ERROR_INITIAL_LIQUIDITY_ALREADY_ADDED, + contexts::add_liquidity::AddLiquidityContext, pair_hooks::hook_type::PairHookType, + StorageCache, ERROR_ACTIVE, ERROR_BAD_PAYMENT_TOKENS, ERROR_INITIAL_LIQUIDITY_ALREADY_ADDED, }; use super::common_result_types::AddLiquidityResultType; @@ -22,6 +22,9 @@ pub trait InitialLiquidityModule: + permissions_module::PermissionsModule + pausable::PausableModule + super::common_methods::CommonMethodsModule + + crate::pair_hooks::change_hooks::ChangeHooksModule + + crate::pair_hooks::call_hook::CallHookModule + + banned_addresses::BannedAddressModule + utils::UtilsModule { #[payable("*")] @@ -55,6 +58,19 @@ pub trait InitialLiquidityModule: ERROR_INITIAL_LIQUIDITY_ALREADY_ADDED ); + let mut payments_vec = ManagedVec::new(); + payments_vec.push(first_payment); + payments_vec.push(second_payment); + + let payments_after_hook = self.call_hook( + PairHookType::BeforeAddInitialLiq, + caller.clone(), + payments_vec, + ManagedVec::new(), + ); + let first_payment = payments_after_hook.get(0); + let second_payment = payments_after_hook.get(1); + let first_token_optimal_amount = &first_payment.amount; let second_token_optimal_amount = &second_payment.amount; let liq_added = self.pool_add_initial_liquidity( @@ -66,8 +82,15 @@ pub trait InitialLiquidityModule: self.send() .esdt_local_mint(&storage_cache.lp_token_id, 0, &liq_added); - let lp_payment = + let mut lp_payment = EsdtTokenPayment::new(storage_cache.lp_token_id.clone(), 0, liq_added.clone()); + let lp_payment_after_hook = self.call_hook( + PairHookType::AfterAddInitialLiq, + caller.clone(), + ManagedVec::from_single_item(lp_payment), + ManagedVec::new(), + ); + lp_payment = lp_payment_after_hook.get(0); self.send() .direct_non_zero_esdt_payment(&caller, &lp_payment); diff --git a/dex/pair/src/pair_actions/remove_liq.rs b/dex/pair/src/pair_actions/remove_liq.rs index 5ee58d591..535fd68f5 100644 --- a/dex/pair/src/pair_actions/remove_liq.rs +++ b/dex/pair/src/pair_actions/remove_liq.rs @@ -1,7 +1,8 @@ use crate::{ - contexts::remove_liquidity::RemoveLiquidityContext, StorageCache, SwapTokensOrder, - ERROR_BAD_PAYMENT_TOKENS, ERROR_INVALID_ARGS, ERROR_K_INVARIANT_FAILED, - ERROR_LP_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, ERROR_NOT_WHITELISTED, ERROR_SLIPPAGE_ON_REMOVE, + contexts::remove_liquidity::RemoveLiquidityContext, pair_hooks::hook_type::PairHookType, + StorageCache, SwapTokensOrder, ERROR_BAD_PAYMENT_TOKENS, ERROR_INVALID_ARGS, + ERROR_K_INVARIANT_FAILED, ERROR_LP_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, ERROR_NOT_WHITELISTED, + ERROR_SLIPPAGE_ON_REMOVE, }; use super::common_result_types::RemoveLiquidityResultType; @@ -22,6 +23,9 @@ pub trait RemoveLiquidityModule: + permissions_module::PermissionsModule + pausable::PausableModule + super::common_methods::CommonMethodsModule + + crate::pair_hooks::change_hooks::ChangeHooksModule + + crate::pair_hooks::call_hook::CallHookModule + + banned_addresses::BannedAddressModule + utils::UtilsModule { #[payable("*")] @@ -63,6 +67,18 @@ pub trait RemoveLiquidityModule: &storage_cache.second_token_reserve, ); + let mut args = ManagedVec::new(); + self.encode_arg_to_vec(&first_token_amount_min, &mut args); + self.encode_arg_to_vec(&second_token_amount_min, &mut args); + + let payments_after_hook = self.call_hook( + PairHookType::BeforeRemoveLiq, + caller.clone(), + ManagedVec::from_single_item(payment), + ManagedVec::new(), + ); + let payment = payments_after_hook.get(0); + let mut remove_liq_context = RemoveLiquidityContext::new( payment.amount, first_token_amount_min, @@ -83,9 +99,15 @@ pub trait RemoveLiquidityModule: let output_payments = self.build_remove_liq_output_payments(&storage_cache, &remove_liq_context); + let output_payments_after_hook = self.call_hook( + PairHookType::AfterRemoveLiq, + caller.clone(), + output_payments, + args, + ); - let first_payment_after = output_payments.get(0); - let second_payment_after = output_payments.get(1); + let first_payment_after = output_payments_after_hook.get(0); + let second_payment_after = output_payments_after_hook.get(1); require!( first_payment_after.amount >= remove_liq_context.first_token_amount_min, ERROR_SLIPPAGE_ON_REMOVE @@ -95,11 +117,11 @@ pub trait RemoveLiquidityModule: ERROR_SLIPPAGE_ON_REMOVE ); - self.send_multiple_tokens_if_not_zero(&caller, &output_payments); + self.send_multiple_tokens_if_not_zero(&caller, &output_payments_after_hook); self.emit_remove_liquidity_event(&storage_cache, remove_liq_context); - self.build_remove_liq_results(output_payments) + self.build_remove_liq_results(output_payments_after_hook) } #[payable("*")] diff --git a/dex/pair/src/pair_actions/swap.rs b/dex/pair/src/pair_actions/swap.rs index f1ec88d94..7381602e2 100644 --- a/dex/pair/src/pair_actions/swap.rs +++ b/dex/pair/src/pair_actions/swap.rs @@ -1,7 +1,7 @@ use crate::{ - contexts::swap::SwapContext, StorageCache, ERROR_INVALID_ARGS, ERROR_K_INVARIANT_FAILED, - ERROR_NOT_ENOUGH_RESERVE, ERROR_NOT_WHITELISTED, ERROR_SLIPPAGE_EXCEEDED, - ERROR_SWAP_NOT_ENABLED, ERROR_ZERO_AMOUNT, + contexts::swap::SwapContext, pair_hooks::hook_type::PairHookType, StorageCache, + ERROR_INVALID_ARGS, ERROR_K_INVARIANT_FAILED, ERROR_NOT_ENOUGH_RESERVE, ERROR_NOT_WHITELISTED, + ERROR_SLIPPAGE_EXCEEDED, ERROR_SWAP_NOT_ENABLED, ERROR_ZERO_AMOUNT, }; use super::common_result_types::{SwapTokensFixedInputResultType, SwapTokensFixedOutputResultType}; @@ -29,6 +29,9 @@ pub trait SwapModule: + permissions_module::PermissionsModule + pausable::PausableModule + super::common_methods::CommonMethodsModule + + crate::pair_hooks::change_hooks::ChangeHooksModule + + crate::pair_hooks::call_hook::CallHookModule + + banned_addresses::BannedAddressModule + utils::UtilsModule { #[payable("*")] @@ -99,6 +102,7 @@ pub trait SwapModule: require!(amount_out_min > 0, ERROR_INVALID_ARGS); let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); let payment = self.call_value().single_esdt(); let swap_tokens_order = storage_cache.get_swap_tokens_order(&payment.token_identifier, &token_out); @@ -121,6 +125,20 @@ pub trait SwapModule: &storage_cache.second_token_reserve, ); + let mut args = ManagedVec::new(); + self.encode_arg_to_vec(&SwapType::FixedInput, &mut args); + + let payments_after_hook = self.call_hook( + PairHookType::BeforeSwap, + caller.clone(), + ManagedVec::from_single_item(payment), + args.clone(), + ); + let payment = payments_after_hook.get(0); + + self.encode_arg_to_vec(&token_out, &mut args); + self.encode_arg_to_vec(&amount_out_min, &mut args); + let mut swap_context = SwapContext::new( payment.token_identifier, payment.amount, @@ -147,17 +165,23 @@ pub trait SwapModule: let caller = self.blockchain().get_caller(); let output_payments = self.build_swap_output_payments(&swap_context); + let output_payments_after_hook = self.call_hook( + PairHookType::AfterSwap, + caller.clone(), + output_payments, + args, + ); require!( - output_payments.get(0).amount >= swap_context.output_token_amount, + output_payments_after_hook.get(0).amount >= swap_context.output_token_amount, ERROR_SLIPPAGE_EXCEEDED ); - self.send_multiple_tokens_if_not_zero(&caller, &output_payments); + self.send_multiple_tokens_if_not_zero(&caller, &output_payments_after_hook); self.emit_swap_event(&storage_cache, swap_context); - self.build_swap_fixed_input_results(output_payments) + self.build_swap_fixed_input_results(output_payments_after_hook) } #[payable("*")] @@ -170,6 +194,7 @@ pub trait SwapModule: require!(amount_out > 0, ERROR_INVALID_ARGS); let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); let payment = self.call_value().single_esdt(); let swap_tokens_order = storage_cache.get_swap_tokens_order(&payment.token_identifier, &token_out); @@ -192,6 +217,20 @@ pub trait SwapModule: &storage_cache.second_token_reserve, ); + let mut args = ManagedVec::new(); + self.encode_arg_to_vec(&SwapType::FixedOutput, &mut args); + + let payments_after_hook = self.call_hook( + PairHookType::BeforeSwap, + caller.clone(), + ManagedVec::from_single_item(payment), + args.clone(), + ); + let payment = payments_after_hook.get(0); + + self.encode_arg_to_vec(&token_out, &mut args); + self.encode_arg_to_vec(&amount_out, &mut args); + let mut swap_context = SwapContext::new( payment.token_identifier, payment.amount, @@ -218,12 +257,18 @@ pub trait SwapModule: let caller = self.blockchain().get_caller(); let output_payments = self.build_swap_output_payments(&swap_context); + let output_payments_after_hook = self.call_hook( + PairHookType::AfterSwap, + caller.clone(), + output_payments, + args, + ); - self.send_multiple_tokens_if_not_zero(&caller, &output_payments); + self.send_multiple_tokens_if_not_zero(&caller, &output_payments_after_hook); self.emit_swap_event(&storage_cache, swap_context); - self.build_swap_fixed_output_results(output_payments) + self.build_swap_fixed_output_results(output_payments_after_hook) } fn perform_swap_fixed_input( diff --git a/dex/pair/src/pair_hooks/call_hook.rs b/dex/pair/src/pair_hooks/call_hook.rs new file mode 100644 index 000000000..c91266844 --- /dev/null +++ b/dex/pair/src/pair_hooks/call_hook.rs @@ -0,0 +1,70 @@ +use common_structs::PaymentsVec; + +use super::hook_type::{Hook, PairHookType}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait CallHookModule { + fn call_hook( + &self, + hook_type: PairHookType, + caller: ManagedAddress, + input_payments: PaymentsVec, + args: ManagedVec, + ) -> PaymentsVec { + let hooks = self.hooks(hook_type).get(); + if hooks.is_empty() { + return input_payments; + } + + let payments_len = input_payments.len(); + let mut call_args = ManagedArgBuffer::new(); + call_args.push_arg(caller); + + for arg in &args { + call_args.push_arg(arg); + } + + let mut output_payments = input_payments; + for hook in &hooks { + let (_, back_transfers) = + ContractCallNoPayment::<_, MultiValueEncoded>::new( + hook.dest_address, + hook.endpoint_name, + ) + .with_raw_arguments(call_args.clone()) + .with_multi_token_transfer(output_payments.clone()) + .execute_on_dest_context_with_back_transfers::>(); + + require!( + back_transfers.esdt_payments.len() == payments_len, + "Wrong payments received from SC" + ); + + for (payment_before, payment_after) in output_payments + .iter() + .zip(back_transfers.esdt_payments.iter()) + { + require!( + payment_before.token_identifier == payment_after.token_identifier + && payment_before.token_nonce == payment_after.token_nonce, + "Invalid payment received from SC" + ); + } + + output_payments = back_transfers.esdt_payments; + } + + output_payments + } + + fn encode_arg_to_vec(&self, arg: &T, vec: &mut ManagedVec) { + let mut encoded_value = ManagedBuffer::new(); + let _ = arg.top_encode(&mut encoded_value); + vec.push(encoded_value); + } + + #[storage_mapper("hooks")] + fn hooks(&self, hook_type: PairHookType) -> SingleValueMapper>>; +} diff --git a/dex/pair/src/pair_hooks/change_hooks.rs b/dex/pair/src/pair_hooks/change_hooks.rs new file mode 100644 index 000000000..9ce4f7111 --- /dev/null +++ b/dex/pair/src/pair_hooks/change_hooks.rs @@ -0,0 +1,48 @@ +use super::hook_type::{Hook, PairHookType}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ChangeHooksModule: + super::call_hook::CallHookModule + + banned_addresses::BannedAddressModule + + permissions_module::PermissionsModule + + utils::UtilsModule +{ + #[endpoint(addHook)] + fn add_hook(&self, hook_type: PairHookType, to: ManagedAddress, endpoint_name: ManagedBuffer) { + self.require_caller_has_owner_or_admin_permissions(); + self.require_not_banned_address(&to); + self.require_sc_address(&to); + self.require_not_empty_buffer(&endpoint_name); + + self.hooks(hook_type).update(|hooks| { + hooks.push(Hook { + dest_address: to, + endpoint_name, + }) + }); + } + + #[endpoint(removeHook)] + fn remove_hook( + &self, + hook_type: PairHookType, + to: ManagedAddress, + endpoint_name: ManagedBuffer, + ) { + self.require_caller_has_owner_or_admin_permissions(); + + self.hooks(hook_type).update(|hooks| { + let opt_index = hooks.find(&Hook { + dest_address: to, + endpoint_name, + }); + + require!(opt_index.is_some(), "Item not found"); + + let index = unsafe { opt_index.unwrap_unchecked() }; + hooks.remove(index); + }) + } +} diff --git a/dex/pair/src/pair_hooks/hook_type.rs b/dex/pair/src/pair_hooks/hook_type.rs new file mode 100644 index 000000000..04cacedb5 --- /dev/null +++ b/dex/pair/src/pair_hooks/hook_type.rs @@ -0,0 +1,88 @@ +use crate::pair_actions::swap::SwapType; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode, Clone, Copy)] +pub enum PairHookType { + // can't be done, execute_on_dest does not work on init + _BeforeInitialize, + _AfterInitialize, + BeforeAddInitialLiq, + AfterAddInitialLiq, + BeforeAddLiq, + AfterAddLiq, + BeforeRemoveLiq, + AfterRemoveLiq, + BeforeSwap, + AfterSwap, +} + +#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, PartialEq)] +pub struct Hook { + pub dest_address: ManagedAddress, + pub endpoint_name: ManagedBuffer, +} + +pub trait PairHook { + type Sc: ContractBase; + + fn before_add_initial_liq( + sc: &Self::Sc, + first_payment: EsdtTokenPayment<::Api>, + second_payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + ); + + fn after_add_initial_liq( + sc: &Self::Sc, + lp_payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + ); + + fn before_add_liq( + sc: &Self::Sc, + first_payment: EsdtTokenPayment<::Api>, + second_payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + first_token_amount_min: BigUint<::Api>, + second_token_amount_min: BigUint<::Api>, + ); + + fn after_add_liq( + sc: &Self::Sc, + lp_payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + ); + + fn before_remove_liq( + sc: &Self::Sc, + lp_payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + ); + + fn after_remove_liq( + sc: &Self::Sc, + first_payment: EsdtTokenPayment<::Api>, + second_payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + first_token_amount_min: BigUint<::Api>, + second_token_amount_min: BigUint<::Api>, + ); + + fn before_swap( + sc: &Self::Sc, + payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + swap_type: SwapType, + ); + + fn after_swap( + sc: &Self::Sc, + payment: EsdtTokenPayment<::Api>, + original_caller: ManagedAddress<::Api>, + swap_type: SwapType, + token_out: TokenIdentifier<::Api>, + amount_out: BigUint<::Api>, + ); +} diff --git a/dex/pair/src/pair_hooks/mod.rs b/dex/pair/src/pair_hooks/mod.rs new file mode 100644 index 000000000..4441862d0 --- /dev/null +++ b/dex/pair/src/pair_hooks/mod.rs @@ -0,0 +1,3 @@ +pub mod call_hook; +pub mod change_hooks; +pub mod hook_type; diff --git a/dex/pair/wasm-pair-full/Cargo.lock b/dex/pair/wasm-pair-full/Cargo.lock index 77c4e33e1..bd9f43d99 100644 --- a/dex/pair/wasm-pair-full/Cargo.lock +++ b/dex/pair/wasm-pair-full/Cargo.lock @@ -14,6 +14,14 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "banned_addresses" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions_module", +] + [[package]] name = "bitflags" version = "2.4.1" @@ -247,6 +255,7 @@ dependencies = [ name = "pair" version = "0.0.0" dependencies = [ + "banned_addresses", "common_errors", "common_structs", "fees-collector", diff --git a/dex/pair/wasm-pair-full/src/lib.rs b/dex/pair/wasm-pair-full/src/lib.rs index 149c4a91b..f9e43af4e 100644 --- a/dex/pair/wasm-pair-full/src/lib.rs +++ b/dex/pair/wasm-pair-full/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 64 +// Endpoints: 68 // Async Callback (empty): 1 -// Total number of exported functions: 66 +// Total number of exported functions: 70 #![no_std] #![allow(internal_features)] @@ -75,6 +75,10 @@ multiversx_sc_wasm_adapter::endpoints! { getAmountOut => get_amount_out_view getAmountIn => get_amount_in_view getEquivalent => get_equivalent + addHook => add_hook + removeHook => remove_hook + addBannedAddress => add_banned_address + removeBannedAddress => remove_banned_address getLpTokensSafePriceByDefaultOffset => get_lp_tokens_safe_price_by_default_offset getLpTokensSafePriceByRoundOffset => get_lp_tokens_safe_price_by_round_offset getLpTokensSafePriceByTimestampOffset => get_lp_tokens_safe_price_by_timestamp_offset diff --git a/dex/pair/wasm-safe-price-view/Cargo.lock b/dex/pair/wasm-safe-price-view/Cargo.lock index 48471aad0..9c0bdd3ca 100644 --- a/dex/pair/wasm-safe-price-view/Cargo.lock +++ b/dex/pair/wasm-safe-price-view/Cargo.lock @@ -14,6 +14,14 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "banned_addresses" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions_module", +] + [[package]] name = "bitflags" version = "2.4.1" @@ -247,6 +255,7 @@ dependencies = [ name = "pair" version = "0.0.0" dependencies = [ + "banned_addresses", "common_errors", "common_structs", "fees-collector", diff --git a/dex/pair/wasm/Cargo.lock b/dex/pair/wasm/Cargo.lock index c1f2406db..34434c55b 100644 --- a/dex/pair/wasm/Cargo.lock +++ b/dex/pair/wasm/Cargo.lock @@ -14,6 +14,14 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "banned_addresses" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions_module", +] + [[package]] name = "bitflags" version = "2.4.1" @@ -247,6 +255,7 @@ dependencies = [ name = "pair" version = "0.0.0" dependencies = [ + "banned_addresses", "common_errors", "common_structs", "fees-collector", diff --git a/dex/pair/wasm/src/lib.rs b/dex/pair/wasm/src/lib.rs index 82a357b8a..ce29b60ec 100644 --- a/dex/pair/wasm/src/lib.rs +++ b/dex/pair/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 55 +// Endpoints: 59 // Async Callback (empty): 1 -// Total number of exported functions: 57 +// Total number of exported functions: 61 #![no_std] #![allow(internal_features)] @@ -75,6 +75,10 @@ multiversx_sc_wasm_adapter::endpoints! { getAmountOut => get_amount_out_view getAmountIn => get_amount_in_view getEquivalent => get_equivalent + addHook => add_hook + removeHook => remove_hook + addBannedAddress => add_banned_address + removeBannedAddress => remove_banned_address ) } diff --git a/dex/sample-hooks/pair-hooks-sample/Cargo.toml b/dex/sample-hooks/pair-hooks-sample/Cargo.toml new file mode 100644 index 000000000..211b40099 --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pair-hooks-sample" +version = "0.0.0" +authors = ["you"] +edition = "2021" +publish = false + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "=0.46.1" + +[dependencies.pair] +path = "../../pair" + +[dev-dependencies] +num-bigint = "0.4.2" + +[dev-dependencies.multiversx-sc-scenario] +version = "=0.46.1" diff --git a/dex/sample-hooks/pair-hooks-sample/meta/Cargo.toml b/dex/sample-hooks/pair-hooks-sample/meta/Cargo.toml new file mode 100644 index 000000000..06eda390c --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/meta/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pair-hooks-sample-meta" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies.pair-hooks-sample] +path = ".." + +[dependencies.multiversx-sc-meta] +version = "=0.46.1" +default-features = false diff --git a/dex/sample-hooks/pair-hooks-sample/meta/src/main.rs b/dex/sample-hooks/pair-hooks-sample/meta/src/main.rs new file mode 100644 index 000000000..956f28e39 --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::(); +} diff --git a/dex/sample-hooks/pair-hooks-sample/multiversx.json b/dex/sample-hooks/pair-hooks-sample/multiversx.json new file mode 100644 index 000000000..736553962 --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/dex/sample-hooks/pair-hooks-sample/src/lib.rs b/dex/sample-hooks/pair-hooks-sample/src/lib.rs new file mode 100644 index 000000000..b76d0137e --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/src/lib.rs @@ -0,0 +1,140 @@ +#![no_std] + +use core::marker::PhantomData; + +use pair::pair_hooks::hook_type::PairHook; + +multiversx_sc::imports!(); + +#[multiversx_sc::contract] +pub trait PairHooksSample { + #[init] + fn init(&self, known_pairs: MultiValueEncoded) { + let mapper = self.known_pairs(); + for pair in known_pairs { + mapper.add(&pair); + } + } + + #[payable("*")] + #[endpoint(beforeAddInitialLiqHook)] + fn before_add_initial_liq_hook(&self, original_caller: ManagedAddress) { + self.require_known_pair(); + + let [first_payment, second_payment] = self.call_value().multi_esdt(); + Wrapper::::before_add_initial_liq( + self, + first_payment, + second_payment, + original_caller, + ); + } + + #[payable("*")] + #[endpoint(afterAddInitialLiqHook)] + fn after_add_initial_liq_hook(&self, original_caller: ManagedAddress) { + self.require_known_pair(); + + let lp_payment = self.call_value().single_esdt(); + Wrapper::::after_add_initial_liq(self, lp_payment, original_caller); + } + + fn require_known_pair(&self) { + let caller = self.blockchain().get_caller(); + require!( + self.known_pairs().contains(&caller), + "Only known pairs may call this endpoint" + ); + } + + #[storage_mapper("knownPairs")] + fn known_pairs(&self) -> WhitelistMapper; +} + +pub struct Wrapper { + _phantom: PhantomData, +} + +impl PairHook for Wrapper { + type Sc = T; + + fn before_add_initial_liq( + sc: &Self::Sc, + first_payment: EsdtTokenPayment<::Api>, + second_payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + ) { + let caller = sc.blockchain().get_caller(); + sc.send() + .direct_non_zero_esdt_payment(&caller, &first_payment); + sc.send() + .direct_non_zero_esdt_payment(&caller, &second_payment); + } + + fn after_add_initial_liq( + sc: &Self::Sc, + lp_payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + ) { + let caller = sc.blockchain().get_caller(); + sc.send().direct_non_zero_esdt_payment(&caller, &lp_payment); + } + + fn before_add_liq( + _sc: &Self::Sc, + _first_payment: EsdtTokenPayment<::Api>, + _second_payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + _first_token_amount_min: BigUint<::Api>, + _second_token_amount_min: BigUint<::Api>, + ) { + todo!() + } + + fn after_add_liq( + _sc: &Self::Sc, + _lp_payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + ) { + todo!() + } + + fn before_remove_liq( + _sc: &Self::Sc, + _lp_payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + ) { + todo!() + } + + fn after_remove_liq( + _sc: &Self::Sc, + _first_payment: EsdtTokenPayment<::Api>, + _second_payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + _first_token_amount_min: BigUint<::Api>, + _second_token_amount_min: BigUint<::Api>, + ) { + todo!() + } + + fn before_swap( + _sc: &Self::Sc, + _payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + _swap_type: pair::pair_actions::swap::SwapType, + ) { + todo!() + } + + fn after_swap( + _sc: &Self::Sc, + _payment: EsdtTokenPayment<::Api>, + _original_caller: ManagedAddress<::Api>, + _swap_type: pair::pair_actions::swap::SwapType, + _token_out: TokenIdentifier<::Api>, + _amount_out: BigUint<::Api>, + ) { + todo!() + } +} diff --git a/dex/sample-hooks/pair-hooks-sample/wasm/Cargo.lock b/dex/sample-hooks/pair-hooks-sample/wasm/Cargo.lock new file mode 100644 index 000000000..b4c6c24cc --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/wasm/Cargo.lock @@ -0,0 +1,407 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "common-types" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "common_errors" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "common_structs" +version = "0.0.0" +dependencies = [ + "fixed-supply-token", + "math", + "mergeable", + "multiversx-sc", + "unwrappable", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "energy-factory" +version = "0.0.0" +dependencies = [ + "common_structs", + "legacy_token_decode_module", + "math", + "mergeable", + "multiversx-sc", + "multiversx-sc-modules", + "sc_whitelist_module", + "simple-lock", + "unwrappable", + "utils", +] + +[[package]] +name = "energy-query" +version = "0.0.0" +dependencies = [ + "energy-factory", + "multiversx-sc", +] + +[[package]] +name = "fees-collector" +version = "0.0.0" +dependencies = [ + "common-types", + "common_errors", + "energy-factory", + "energy-query", + "locking_module", + "multiversx-sc", + "multiversx-sc-modules", + "sc_whitelist_module", + "simple-lock", + "utils", + "week-timekeeping", + "weekly-rewards-splitting", +] + +[[package]] +name = "fixed-supply-token" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[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 = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "legacy_token_decode_module" +version = "0.0.0" +dependencies = [ + "common_structs", + "multiversx-sc", + "utils", +] + +[[package]] +name = "locking_module" +version = "0.0.0" +dependencies = [ + "energy-factory", + "multiversx-sc", + "simple-lock", +] + +[[package]] +name = "math" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "mergeable" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "multiversx-sc" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c94b173dc5ff0e157f767275fe6b7a1b4d2ad343bef7b66cd22a6353e016b93" +dependencies = [ + "bitflags", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19908153158c03df4582af08f47c0eb39fb52a7dff4736b301a66acbbb9955d3" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b03b43f9cad320992f54ed162de2ed63e3ec83ed01361e57ee9c1865fba5a2" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b78945957036c281ad6ee21bb5120dcefa2017688adf43ec94e3e7c982efb09" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-modules" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c63ffaba95e630ff75981e2f5f50da64f523219b52f484234c66f3adc248885f" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9579f40c00da56a5a68e010ff851fa48ac7b9c6a16ad4314795cb32d889d9e78" +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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "pair" +version = "0.0.0" +dependencies = [ + "common_errors", + "common_structs", + "fees-collector", + "itertools", + "multiversx-sc", + "pausable", + "permissions_module", + "simple-lock", + "token_send", + "utils", +] + +[[package]] +name = "pair-hooks-sample" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "pair", +] + +[[package]] +name = "pair-hooks-sample-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "pair-hooks-sample", +] + +[[package]] +name = "pausable" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "permissions_module", +] + +[[package]] +name = "permissions_module" +version = "0.0.0" +dependencies = [ + "bitflags", + "common_errors", + "multiversx-sc", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +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 = "sc_whitelist_module" +version = "0.0.0" +dependencies = [ + "common_errors", + "multiversx-sc", +] + +[[package]] +name = "simple-lock" +version = "0.0.0" +dependencies = [ + "common_structs", + "multiversx-sc", + "multiversx-sc-modules", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "token_send" +version = "0.0.0" +dependencies = [ + "common_errors", + "common_structs", + "multiversx-sc", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unwrappable" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "utils" +version = "0.0.0" +dependencies = [ + "common_structs", + "fixed-supply-token", + "mergeable", + "multiversx-sc", +] + +[[package]] +name = "week-timekeeping" +version = "0.0.0" +dependencies = [ + "common-types", + "multiversx-sc", +] + +[[package]] +name = "weekly-rewards-splitting" +version = "0.0.0" +dependencies = [ + "common-types", + "energy-query", + "math", + "multiversx-sc", + "unwrappable", + "week-timekeeping", +] diff --git a/dex/sample-hooks/pair-hooks-sample/wasm/Cargo.toml b/dex/sample-hooks/pair-hooks-sample/wasm/Cargo.toml new file mode 100644 index 000000000..9ce799efc --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/wasm/Cargo.toml @@ -0,0 +1,31 @@ +# Code generated by the multiversx-sc build system. DO NOT EDIT. + +# ########################################## +# ############## AUTO-GENERATED ############# +# ########################################## + +[package] +name = "pair-hooks-sample-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 + +[dependencies.pair-hooks-sample] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "=0.46.1" + +[workspace] +members = ["."] diff --git a/dex/sample-hooks/pair-hooks-sample/wasm/src/lib.rs b/dex/sample-hooks/pair-hooks-sample/wasm/src/lib.rs new file mode 100644 index 000000000..a059bba6b --- /dev/null +++ b/dex/sample-hooks/pair-hooks-sample/wasm/src/lib.rs @@ -0,0 +1,28 @@ +// Code generated by the multiversx-sc build system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 2 +// Async Callback (empty): 1 +// Total number of exported functions: 4 + +#![no_std] +#![allow(internal_features)] +#![feature(lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + pair_hooks_sample + ( + init => init + beforeAddInitialLiqHook => before_add_initial_liq_hook + afterAddInitialLiqHook => after_add_initial_liq_hook + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {}