From 7842c9cd6e7d350d55eaeeecbad5ee4dc928541c Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Wed, 6 Nov 2024 21:20:18 +1300 Subject: [PATCH 1/3] Add a V3 for `cf_pool_swap_rate_v3` call that will consider broker and affiliate fees as well as DCA. WIP: Need to write tests --- state-chain/custom-rpc/src/lib.rs | 99 +++++++++++++++++++--- state-chain/pallets/cf-swapping/src/lib.rs | 13 +++ state-chain/primitives/src/lib.rs | 18 ++++ state-chain/runtime/src/lib.rs | 58 +++++++++++-- state-chain/runtime/src/runtime_apis.rs | 39 ++++++++- 5 files changed, 207 insertions(+), 20 deletions(-) diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index a84e888a1f..99ea7a4095 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -13,8 +13,8 @@ use cf_chains::{ }; use cf_primitives::{ chains::assets::any::{self, AssetMap}, - AccountRole, Asset, AssetAmount, BlockNumber, BroadcastId, EpochIndex, ForeignChain, - NetworkEnvironment, SemVer, SwapId, SwapRequestId, + AccountRole, Affiliates, Asset, AssetAmount, BasisPoints, BlockNumber, BroadcastId, + DcaParameters, EpochIndex, ForeignChain, NetworkEnvironment, SemVer, SwapId, SwapRequestId, }; use cf_utilities::rpc::NumberOrHex; use core::ops::Range; @@ -421,6 +421,32 @@ pub struct RpcSwapOutputV2 { pub egress_fee: RpcFee, } +impl From for RpcSwapOutputV2 { + fn from(swap_output: RpcSwapOutputV3) -> Self { + Self { + intermediary: swap_output.intermediary.map(Into::into), + output: swap_output.output, + network_fee: swap_output.network_fee, + ingress_fee: swap_output.ingress_fee, + egress_fee: swap_output.egress_fee, + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct RpcSwapOutputV3 { + // Intermediary amount, if there's any + pub intermediary: Option, + // Final output of the swap + pub output: U256, + pub network_fee: RpcFee, + pub ingress_fee: RpcFee, + pub egress_fee: RpcFee, + // Fees for broker and affiliates + pub broker_commission: RpcFee, + pub affiliate_fees: Vec<(sp_runtime::AccountId32, RpcFee)>, +} + #[derive(Serialize, Deserialize, Clone)] pub enum SwapRateV2AdditionalOrder { LimitOrder { base_asset: Asset, quote_asset: Asset, side: Side, tick: Tick, sell_amount: U256 }, @@ -746,6 +772,18 @@ pub trait CustomApi { additional_orders: Option>, at: Option, ) -> RpcResult; + #[method(name = "swap_rate_v3")] + fn cf_pool_swap_rate_v3( + &self, + from_asset: Asset, + to_asset: Asset, + amount: U256, + additional_orders: Option>, + broker_commission: BasisPoints, + affiliate_fees: Option>, + dca_parameters: Option, + at: Option, + ) -> RpcResult; #[method(name = "required_asset_ratio_for_range_order")] fn cf_required_asset_ratio_for_range_order( &self, @@ -1356,16 +1394,40 @@ where additional_orders: Option>, at: Option, ) -> RpcResult { + self.cf_pool_swap_rate_v3( + from_asset, + to_asset, + amount, + additional_orders, + 0u16, + None, + None, + at, + ) + .map(Into::into) + } + + fn cf_pool_swap_rate_v3( + &self, + from_asset: Asset, + to_asset: Asset, + amount: U256, + additional_orders: Option>, + broker_commission: BasisPoints, + affiliate_fees: Option>, + dca_parameters: Option, + at: Option, + ) -> RpcResult { self.with_runtime_api(at, |api, hash| { Ok::<_, CfApiError>( - api.cf_pool_simulate_swap( + api.cf_pool_simulate_swap_v2( hash, from_asset, to_asset, amount .try_into() .map_err(|_| "Swap input amount too large.") - .and_then(|amount| { + .and_then(|amount: u128| { if amount == 0 { Err("Swap input amount cannot be zero.") } else { @@ -1397,22 +1459,39 @@ where }) .collect() }), + broker_commission, + affiliate_fees, + dca_parameters, )? - .map(|simulated_swap_info| RpcSwapOutputV2 { - intermediary: simulated_swap_info.intermediary.map(Into::into), - output: simulated_swap_info.output.into(), + .map(|simulated_swap_info_v2| RpcSwapOutputV3 { + intermediary: simulated_swap_info_v2.intermediary.map(Into::into), + output: simulated_swap_info_v2.output.into(), network_fee: RpcFee { asset: cf_primitives::STABLE_ASSET, - amount: simulated_swap_info.network_fee.into(), + amount: simulated_swap_info_v2.network_fee.into(), }, ingress_fee: RpcFee { asset: from_asset, - amount: simulated_swap_info.ingress_fee.into(), + amount: simulated_swap_info_v2.ingress_fee.into(), }, egress_fee: RpcFee { asset: to_asset, - amount: simulated_swap_info.egress_fee.into(), + amount: simulated_swap_info_v2.egress_fee.into(), }, + broker_commission: RpcFee { + asset: cf_primitives::STABLE_ASSET, + amount: simulated_swap_info_v2.broker_fee.into(), + }, + affiliate_fees: simulated_swap_info_v2 + .affiliate_fees + .into_iter() + .map(|(account, fees)| { + ( + account, + RpcFee { asset: cf_primitives::STABLE_ASSET, amount: fees.into() }, + ) + }) + .collect(), })?, ) }) diff --git a/state-chain/pallets/cf-swapping/src/lib.rs b/state-chain/pallets/cf-swapping/src/lib.rs index a3199ee446..a83e9ea57a 100644 --- a/state-chain/pallets/cf-swapping/src/lib.rs +++ b/state-chain/pallets/cf-swapping/src/lib.rs @@ -2010,6 +2010,19 @@ pub mod pallet { }, }; } + + /// Swap some amount of an asset into the STABLE_ASSET with no fee deductions. + /// Used for fee estimation ONLY. + #[transactional] + pub fn swap_into_stable_without_fees( + from: Asset, + input_amount: AssetAmount, + ) -> Result { + match from { + STABLE_ASSET => Ok(input_amount), + _ => T::SwappingApi::swap_single_leg(from, STABLE_ASSET, input_amount), + } + } } impl SwapRequestHandler for Pallet { diff --git a/state-chain/primitives/src/lib.rs b/state-chain/primitives/src/lib.rs index b5c584df1a..a64b3fbcfa 100644 --- a/state-chain/primitives/src/lib.rs +++ b/state-chain/primitives/src/lib.rs @@ -275,6 +275,18 @@ pub struct SwapOutput { pub network_fee: AssetAmount, } +impl SwapOutput { + // Multiply each field by x. + // Uses Saturating arithmetics. + pub fn saturating_mul(self, x: AssetAmount) -> Self { + SwapOutput { + intermediary: self.intermediary.map(|intermediary| intermediary.saturating_mul(x)), + output: self.output.saturating_mul(x), + network_fee: self.network_fee.saturating_mul(x), + } + } +} + #[derive(PartialEq, Eq, Copy, Clone, Debug, Encode, Decode, TypeInfo)] pub enum SwapLeg { FromStable, @@ -425,3 +437,9 @@ pub struct DcaParameters { /// The interval in blocks between each swap. pub chunk_interval: u32, } + +/// Utility multiply an AssetAmount by a BasisPoint. +/// Only support up to 10_000 bps or 100%. +pub fn mul_bps(amount: AssetAmount, bps: BasisPoints) -> AssetAmount { + sp_arithmetic::Permill::from_rational(bps as u32, 10_000u32) * amount +} diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 7cde5a48c9..5342838777 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -28,7 +28,8 @@ use crate::{ runtime_decl_for_custom_runtime_api::CustomRuntimeApiV1, AuctionState, BoostPoolDepth, BoostPoolDetails, BrokerInfo, DispatchErrorWithMessage, FailingWitnessValidators, LiquidityProviderBoostPoolInfo, LiquidityProviderInfo, RuntimeApiPenalty, - SimulateSwapAdditionalOrder, SimulatedSwapInformation, ValidatorInfo, + SimulateSwapAdditionalOrder, SimulatedSwapInformation, SimulatedSwapInformationV2, + ValidatorInfo, }, }; use cf_amm::{ @@ -49,7 +50,10 @@ use cf_chains::{ sol::{SolAddress, SolanaCrypto}, Arbitrum, Bitcoin, DefaultRetryPolicy, ForeignChain, Polkadot, Solana, TransactionBuilder, }; -use cf_primitives::{BroadcastId, EpochIndex, NetworkEnvironment, STABLE_ASSET}; +use cf_primitives::{ + Affiliates, BasisPoints, Beneficiary, BroadcastId, DcaParameters, EpochIndex, + NetworkEnvironment, STABLE_ASSET, +}; use cf_runtime_utilities::NoopRuntimeUpgrade; use cf_traits::{ AdjustedFeeEstimationApi, AssetConverter, BalanceApi, DummyEgressSuccessWitnesser, @@ -1541,6 +1545,15 @@ impl_runtime_apis! { LiquidityPools::pool_price(base_asset, quote_asset).map_err(Into::into) } + fn cf_pool_simulate_swap( + from: Asset, + to: Asset, + amount: AssetAmount, + additional_orders: Option>, + ) -> Result { + Self::cf_pool_simulate_swap_v2(from, to, amount, additional_orders, Default::default(), None, None).map(Into::into) + } + /// Simulates a swap and return the intermediate (if any) and final output. /// /// If no swap rate can be calculated, returns None. This can happen if the pools are not @@ -1549,12 +1562,15 @@ impl_runtime_apis! { /// /// Note: This function must only be called through RPC, because RPC has its own storage buffer /// layer and would not affect on-chain storage. - fn cf_pool_simulate_swap( + fn cf_pool_simulate_swap_v2( from: Asset, to: Asset, amount: AssetAmount, additional_orders: Option>, - ) -> Result { + broker_commission: BasisPoints, + affiliate_fees: Option>, + dca_parameters: Option, + ) -> Result { if let Some(additional_orders) = additional_orders { for (index, additional_order) in additional_orders.into_iter().enumerate() { match additional_order { @@ -1628,20 +1644,46 @@ impl_runtime_apis! { let (amount_to_swap, ingress_fee) = remove_fees(IngressOrEgress::Ingress, from, amount); + fn accumulate_and_swap_fees_into_stable(asset: Asset, amount: AssetAmount, fees: BasisPoints, total_fees: &mut AssetAmount) -> Result { + let input = cf_primitives::mul_bps(amount, fees); + *total_fees += input; + Swapping::swap_into_stable_without_fees(asset, amount).map_err(Into::into) + } + + // Take broker and affiliate fees from the swap amount + let mut total_fees = 0; + + // Calculate broker fee + let broker_fee = accumulate_and_swap_fees_into_stable(from, amount_to_swap, broker_commission, &mut total_fees)?; + + // Calculate affiliate fees + let affiliate_fees = affiliate_fees.unwrap_or_default().into_iter().map(|Beneficiary {account, bps}|{ + let affiliate_fee = accumulate_and_swap_fees_into_stable(from, amount_to_swap, bps, &mut total_fees)?; + Ok((account, affiliate_fee)) + }).collect::, DispatchErrorWithMessage>>()?; + + let amount_to_swap_without_fees = amount_to_swap.saturating_sub(total_fees); + + // Estimate swap result for a chunk, then extrapolate the result. + // If no DCA parameter is given, swap the entire amount with 1 chunk. + let number_of_chunks: u128 = dca_parameters.map(|dca|dca.number_of_chunks).unwrap_or(1u32).into(); + let amount_per_chunk = amount_to_swap_without_fees / number_of_chunks; let swap_output = Swapping::swap_with_network_fee( from, to, - amount_to_swap, - )?; + amount_per_chunk, + )?.saturating_mul(number_of_chunks); let (output, egress_fee) = remove_fees(IngressOrEgress::Egress, to, swap_output.output); - Ok(SimulatedSwapInformation { + Ok(SimulatedSwapInformationV2 { intermediary: swap_output.intermediary, output, network_fee: swap_output.network_fee, ingress_fee, egress_fee, + broker_fee, + affiliate_fees, }) } @@ -2095,7 +2137,7 @@ impl_runtime_apis! { } fn cf_validate_dca_params(number_of_chunks: u32, chunk_interval: u32) -> Result<(), DispatchErrorWithMessage> { - pallet_cf_swapping::Pallet::::validate_dca_params(&cf_primitives::DcaParameters{number_of_chunks, chunk_interval}).map_err(Into::into) + pallet_cf_swapping::Pallet::::validate_dca_params(&DcaParameters{number_of_chunks, chunk_interval}).map_err(Into::into) } fn cf_validate_refund_params(retry_duration: u32) -> Result<(), DispatchErrorWithMessage> { diff --git a/state-chain/runtime/src/runtime_apis.rs b/state-chain/runtime/src/runtime_apis.rs index 536ebc0bfd..a07b4c3b21 100644 --- a/state-chain/runtime/src/runtime_apis.rs +++ b/state-chain/runtime/src/runtime_apis.rs @@ -7,8 +7,9 @@ use cf_chains::{ assets::any::AssetMap, eth::Address as EthereumAddress, Chain, ForeignChainAddress, }; use cf_primitives::{ - AccountRole, Asset, AssetAmount, BlockNumber, BroadcastId, EpochIndex, FlipBalance, - ForeignChain, NetworkEnvironment, PrewitnessedDepositId, SemVer, + AccountRole, Affiliates, Asset, AssetAmount, BasisPoints, BlockNumber, BroadcastId, + DcaParameters, EpochIndex, FlipBalance, ForeignChain, NetworkEnvironment, + PrewitnessedDepositId, SemVer, }; use cf_traits::SwapLimits; use codec::{Decode, Encode}; @@ -147,6 +148,31 @@ pub struct SimulatedSwapInformation { pub egress_fee: AssetAmount, } +impl From for SimulatedSwapInformation { + fn from(swap_into: SimulatedSwapInformationV2) -> Self { + Self { + intermediary: swap_into.intermediary, + output: swap_into.output, + network_fee: swap_into.network_fee, + ingress_fee: swap_into.ingress_fee, + egress_fee: swap_into.egress_fee, + } + } +} + +/// Struct that represents the estimated output of a Swap V2. +/// Adds additional output for Broker and affiliate fees. +#[derive(Encode, Decode, TypeInfo)] +pub struct SimulatedSwapInformationV2 { + pub intermediary: Option, + pub output: AssetAmount, + pub network_fee: AssetAmount, + pub ingress_fee: AssetAmount, + pub egress_fee: AssetAmount, + pub broker_fee: AssetAmount, + pub affiliate_fees: Vec<(AccountId32, AssetAmount)>, +} + #[derive(Debug, Decode, Encode, TypeInfo)] pub enum DispatchErrorWithMessage { Module(Vec), @@ -224,6 +250,15 @@ decl_runtime_apis!( amount: AssetAmount, additional_limit_orders: Option>, ) -> Result; + fn cf_pool_simulate_swap_v2( + from: Asset, + to: Asset, + amount: AssetAmount, + additional_limit_orders: Option>, + broker_commission: BasisPoints, + affiliate_fees: Option>, + dca_parameters: Option, + ) -> Result; fn cf_pool_info( base_asset: Asset, quote_asset: Asset, From 656f2b6413b5fecfdf84be769b715dfc7a820272 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Thu, 7 Nov 2024 14:51:04 +1300 Subject: [PATCH 2/3] Removed Affiliate fees Used `try_execute_without_violations` instead of `swap_with_network_fee` to estimate swap result. --- state-chain/custom-rpc/src/lib.rs | 32 ++------ state-chain/pallets/cf-swapping/src/lib.rs | 33 +++----- state-chain/primitives/src/lib.rs | 18 ----- state-chain/runtime/src/lib.rs | 91 +++++++++++++--------- state-chain/runtime/src/runtime_apis.rs | 9 +-- 5 files changed, 74 insertions(+), 109 deletions(-) diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index 99ea7a4095..50eaf85ca7 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -13,8 +13,8 @@ use cf_chains::{ }; use cf_primitives::{ chains::assets::any::{self, AssetMap}, - AccountRole, Affiliates, Asset, AssetAmount, BasisPoints, BlockNumber, BroadcastId, - DcaParameters, EpochIndex, ForeignChain, NetworkEnvironment, SemVer, SwapId, SwapRequestId, + AccountRole, Asset, AssetAmount, BasisPoints, BlockNumber, BroadcastId, DcaParameters, + EpochIndex, ForeignChain, NetworkEnvironment, SemVer, SwapId, SwapRequestId, }; use cf_utilities::rpc::NumberOrHex; use core::ops::Range; @@ -442,9 +442,7 @@ pub struct RpcSwapOutputV3 { pub network_fee: RpcFee, pub ingress_fee: RpcFee, pub egress_fee: RpcFee, - // Fees for broker and affiliates pub broker_commission: RpcFee, - pub affiliate_fees: Vec<(sp_runtime::AccountId32, RpcFee)>, } #[derive(Serialize, Deserialize, Clone)] @@ -778,10 +776,9 @@ pub trait CustomApi { from_asset: Asset, to_asset: Asset, amount: U256, - additional_orders: Option>, broker_commission: BasisPoints, - affiliate_fees: Option>, dca_parameters: Option, + additional_orders: Option>, at: Option, ) -> RpcResult; #[method(name = "required_asset_ratio_for_range_order")] @@ -1398,10 +1395,9 @@ where from_asset, to_asset, amount, - additional_orders, - 0u16, - None, + Default::default(), None, + additional_orders, at, ) .map(Into::into) @@ -1412,10 +1408,9 @@ where from_asset: Asset, to_asset: Asset, amount: U256, - additional_orders: Option>, broker_commission: BasisPoints, - affiliate_fees: Option>, dca_parameters: Option, + additional_orders: Option>, at: Option, ) -> RpcResult { self.with_runtime_api(at, |api, hash| { @@ -1437,6 +1432,8 @@ where .map_err(|s| { ErrorObject::owned(ErrorCode::InvalidParams.code(), s, None::<()>) })?, + broker_commission, + dca_parameters, additional_orders.map(|additional_orders| { additional_orders .into_iter() @@ -1459,9 +1456,6 @@ where }) .collect() }), - broker_commission, - affiliate_fees, - dca_parameters, )? .map(|simulated_swap_info_v2| RpcSwapOutputV3 { intermediary: simulated_swap_info_v2.intermediary.map(Into::into), @@ -1482,16 +1476,6 @@ where asset: cf_primitives::STABLE_ASSET, amount: simulated_swap_info_v2.broker_fee.into(), }, - affiliate_fees: simulated_swap_info_v2 - .affiliate_fees - .into_iter() - .map(|(account, fees)| { - ( - account, - RpcFee { asset: cf_primitives::STABLE_ASSET, amount: fees.into() }, - ) - }) - .collect(), })?, ) }) diff --git a/state-chain/pallets/cf-swapping/src/lib.rs b/state-chain/pallets/cf-swapping/src/lib.rs index a83e9ea57a..cca7067325 100644 --- a/state-chain/pallets/cf-swapping/src/lib.rs +++ b/state-chain/pallets/cf-swapping/src/lib.rs @@ -70,12 +70,12 @@ struct FeeTaken { } #[derive(CloneNoBound, DebugNoBound)] -struct SwapState { - swap: Swap, - network_fee_taken: Option, - broker_fee_taken: Option, - stable_amount: Option, - final_output: Option, +pub struct SwapState { + pub swap: Swap, + pub network_fee_taken: Option, + pub broker_fee_taken: Option, + pub stable_amount: Option, + pub final_output: Option, } impl SwapState { @@ -149,7 +149,7 @@ impl SwapState { #[repr(u8)] #[derive(Clone, DebugNoBound, PartialEq, Eq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] -enum FeeType { +pub enum FeeType { NetworkFee = 0, BrokerFee(Beneficiaries), } @@ -181,7 +181,7 @@ pub struct SwapLegInfo { } impl Swap { - fn new( + pub fn new( swap_id: SwapId, swap_request_id: SwapRequestId, from: Asset, @@ -202,7 +202,7 @@ impl Swap { } } -enum BatchExecutionError { +pub enum BatchExecutionError { SwapLegFailed { asset: Asset, direction: SwapLeg, @@ -1281,7 +1281,7 @@ pub mod pallet { } #[transactional] - fn try_execute_without_violations( + pub fn try_execute_without_violations( swaps: Vec>, ) -> Result>, BatchExecutionError> { let mut swaps: Vec<_> = swaps.into_iter().map(SwapState::new).collect(); @@ -2010,19 +2010,6 @@ pub mod pallet { }, }; } - - /// Swap some amount of an asset into the STABLE_ASSET with no fee deductions. - /// Used for fee estimation ONLY. - #[transactional] - pub fn swap_into_stable_without_fees( - from: Asset, - input_amount: AssetAmount, - ) -> Result { - match from { - STABLE_ASSET => Ok(input_amount), - _ => T::SwappingApi::swap_single_leg(from, STABLE_ASSET, input_amount), - } - } } impl SwapRequestHandler for Pallet { diff --git a/state-chain/primitives/src/lib.rs b/state-chain/primitives/src/lib.rs index a64b3fbcfa..b5c584df1a 100644 --- a/state-chain/primitives/src/lib.rs +++ b/state-chain/primitives/src/lib.rs @@ -275,18 +275,6 @@ pub struct SwapOutput { pub network_fee: AssetAmount, } -impl SwapOutput { - // Multiply each field by x. - // Uses Saturating arithmetics. - pub fn saturating_mul(self, x: AssetAmount) -> Self { - SwapOutput { - intermediary: self.intermediary.map(|intermediary| intermediary.saturating_mul(x)), - output: self.output.saturating_mul(x), - network_fee: self.network_fee.saturating_mul(x), - } - } -} - #[derive(PartialEq, Eq, Copy, Clone, Debug, Encode, Decode, TypeInfo)] pub enum SwapLeg { FromStable, @@ -437,9 +425,3 @@ pub struct DcaParameters { /// The interval in blocks between each swap. pub chunk_interval: u32, } - -/// Utility multiply an AssetAmount by a BasisPoint. -/// Only support up to 10_000 bps or 100%. -pub fn mul_bps(amount: AssetAmount, bps: BasisPoints) -> AssetAmount { - sp_arithmetic::Permill::from_rational(bps as u32, 10_000u32) * amount -} diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 5342838777..ae8301cebb 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -51,8 +51,8 @@ use cf_chains::{ Arbitrum, Bitcoin, DefaultRetryPolicy, ForeignChain, Polkadot, Solana, TransactionBuilder, }; use cf_primitives::{ - Affiliates, BasisPoints, Beneficiary, BroadcastId, DcaParameters, EpochIndex, - NetworkEnvironment, STABLE_ASSET, + BasisPoints, Beneficiary, BroadcastId, DcaParameters, EpochIndex, NetworkEnvironment, + STABLE_ASSET, }; use cf_runtime_utilities::NoopRuntimeUpgrade; use cf_traits::{ @@ -75,6 +75,7 @@ use pallet_cf_pools::{ AskBidMap, AssetPair, HistoricalEarnedFees, OrderId, PoolLiquidity, PoolOrderbook, PoolPriceV1, PoolPriceV2, UnidirectionalPoolDepth, }; +use pallet_cf_swapping::{BatchExecutionError, FeeType, Swap}; use crate::chainflip::EvmLimit; @@ -123,7 +124,7 @@ pub use sp_runtime::BuildStorage; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, MultiSignature, + ApplyExtrinsicResult, DispatchError, MultiSignature, }; pub use sp_runtime::{Perbill, Permill}; use sp_std::prelude::*; @@ -1551,7 +1552,7 @@ impl_runtime_apis! { amount: AssetAmount, additional_orders: Option>, ) -> Result { - Self::cf_pool_simulate_swap_v2(from, to, amount, additional_orders, Default::default(), None, None).map(Into::into) + Self::cf_pool_simulate_swap_v2(from, to, amount, Default::default(), None, additional_orders).map(Into::into) } /// Simulates a swap and return the intermediate (if any) and final output. @@ -1566,10 +1567,9 @@ impl_runtime_apis! { from: Asset, to: Asset, amount: AssetAmount, - additional_orders: Option>, broker_commission: BasisPoints, - affiliate_fees: Option>, dca_parameters: Option, + additional_orders: Option>, ) -> Result { if let Some(additional_orders) = additional_orders { for (index, additional_order) in additional_orders.into_iter().enumerate() { @@ -1644,46 +1644,63 @@ impl_runtime_apis! { let (amount_to_swap, ingress_fee) = remove_fees(IngressOrEgress::Ingress, from, amount); - fn accumulate_and_swap_fees_into_stable(asset: Asset, amount: AssetAmount, fees: BasisPoints, total_fees: &mut AssetAmount) -> Result { - let input = cf_primitives::mul_bps(amount, fees); - *total_fees += input; - Swapping::swap_into_stable_without_fees(asset, amount).map_err(Into::into) - } - - // Take broker and affiliate fees from the swap amount - let mut total_fees = 0; - - // Calculate broker fee - let broker_fee = accumulate_and_swap_fees_into_stable(from, amount_to_swap, broker_commission, &mut total_fees)?; - - // Calculate affiliate fees - let affiliate_fees = affiliate_fees.unwrap_or_default().into_iter().map(|Beneficiary {account, bps}|{ - let affiliate_fee = accumulate_and_swap_fees_into_stable(from, amount_to_swap, bps, &mut total_fees)?; - Ok((account, affiliate_fee)) - }).collect::, DispatchErrorWithMessage>>()?; - - let amount_to_swap_without_fees = amount_to_swap.saturating_sub(total_fees); - // Estimate swap result for a chunk, then extrapolate the result. // If no DCA parameter is given, swap the entire amount with 1 chunk. let number_of_chunks: u128 = dca_parameters.map(|dca|dca.number_of_chunks).unwrap_or(1u32).into(); - let amount_per_chunk = amount_to_swap_without_fees / number_of_chunks; - let swap_output = Swapping::swap_with_network_fee( - from, - to, - amount_per_chunk, - )?.saturating_mul(number_of_chunks); + let amount_per_chunk = amount_to_swap / number_of_chunks; + + let swap_output_per_chunk = Swapping::try_execute_without_violations( + vec![ + Swap::new( + Default::default(), + Default::default(), + from, + to, + amount_per_chunk, + None, + vec![ + FeeType::NetworkFee, + FeeType::BrokerFee( + vec![Beneficiary { + account: AccountId::new([0xbb; 32]), + bps: broker_commission, + }] + .try_into() + .expect("Beneficiary with a length of 1 must be within length bound.") + ) + ], + ) + ], + ).map_err(|e| DispatchErrorWithMessage::Other(match e { + BatchExecutionError::SwapLegFailed { .. } => DispatchError::Other("Swap leg failed."), + BatchExecutionError::PriceViolation { .. } => DispatchError::Other("Price Violation: Some swaps failed due to Price Impact Limitations."), + BatchExecutionError::DispatchError { error } => error, + }))?; + + let ( + network_fee, + broker_fee, + intermediary, + output, + ) = { + let output_per_chunk = swap_output_per_chunk[0].clone(); + ( + output_per_chunk.network_fee_taken.unwrap_or_default() * number_of_chunks, + output_per_chunk.broker_fee_taken.unwrap_or_default() * number_of_chunks, + output_per_chunk.stable_amount.map(|amount| amount * number_of_chunks), + output_per_chunk.final_output.unwrap_or_default() * number_of_chunks, + ) + }; - let (output, egress_fee) = remove_fees(IngressOrEgress::Egress, to, swap_output.output); + let (output, egress_fee) = remove_fees(IngressOrEgress::Egress, to, output); Ok(SimulatedSwapInformationV2 { - intermediary: swap_output.intermediary, + intermediary, output, - network_fee: swap_output.network_fee, + network_fee, ingress_fee, egress_fee, broker_fee, - affiliate_fees, }) } @@ -1692,9 +1709,7 @@ impl_runtime_apis! { } fn cf_lp_events() -> Vec> { - System::read_events_no_consensus().filter_map(|event_record| { - if let RuntimeEvent::LiquidityPools(pools_event) = event_record.event { Some(pools_event) } else { diff --git a/state-chain/runtime/src/runtime_apis.rs b/state-chain/runtime/src/runtime_apis.rs index a07b4c3b21..e7fc407dcf 100644 --- a/state-chain/runtime/src/runtime_apis.rs +++ b/state-chain/runtime/src/runtime_apis.rs @@ -7,9 +7,8 @@ use cf_chains::{ assets::any::AssetMap, eth::Address as EthereumAddress, Chain, ForeignChainAddress, }; use cf_primitives::{ - AccountRole, Affiliates, Asset, AssetAmount, BasisPoints, BlockNumber, BroadcastId, - DcaParameters, EpochIndex, FlipBalance, ForeignChain, NetworkEnvironment, - PrewitnessedDepositId, SemVer, + AccountRole, Asset, AssetAmount, BasisPoints, BlockNumber, BroadcastId, DcaParameters, + EpochIndex, FlipBalance, ForeignChain, NetworkEnvironment, PrewitnessedDepositId, SemVer, }; use cf_traits::SwapLimits; use codec::{Decode, Encode}; @@ -170,7 +169,6 @@ pub struct SimulatedSwapInformationV2 { pub ingress_fee: AssetAmount, pub egress_fee: AssetAmount, pub broker_fee: AssetAmount, - pub affiliate_fees: Vec<(AccountId32, AssetAmount)>, } #[derive(Debug, Decode, Encode, TypeInfo)] @@ -254,10 +252,9 @@ decl_runtime_apis!( from: Asset, to: Asset, amount: AssetAmount, - additional_limit_orders: Option>, broker_commission: BasisPoints, - affiliate_fees: Option>, dca_parameters: Option, + additional_limit_orders: Option>, ) -> Result; fn cf_pool_info( base_asset: Asset, From f0c0a71efae7cc028c00725a2127c8985b5b837e Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 7 Nov 2024 13:42:45 +0100 Subject: [PATCH 3/3] fix: - remove v3 types/methods - add versioning to runtime api - avoid clone --- state-chain/custom-rpc/src/lib.rs | 58 ++++++------------- ..._rpc__test__swap_output_serialization.snap | 4 +- state-chain/runtime/src/lib.rs | 30 ++++------ state-chain/runtime/src/runtime_apis.rs | 45 ++++++-------- 4 files changed, 49 insertions(+), 88 deletions(-) diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index 16b2ec3c33..f93866d063 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -420,29 +420,6 @@ pub struct RpcSwapOutputV2 { pub network_fee: RpcFee, pub ingress_fee: RpcFee, pub egress_fee: RpcFee, -} - -impl From for RpcSwapOutputV2 { - fn from(swap_output: RpcSwapOutputV3) -> Self { - Self { - intermediary: swap_output.intermediary.map(Into::into), - output: swap_output.output, - network_fee: swap_output.network_fee, - ingress_fee: swap_output.ingress_fee, - egress_fee: swap_output.egress_fee, - } - } -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct RpcSwapOutputV3 { - // Intermediary amount, if there's any - pub intermediary: Option, - // Final output of the swap - pub output: U256, - pub network_fee: RpcFee, - pub ingress_fee: RpcFee, - pub egress_fee: RpcFee, pub broker_commission: RpcFee, } @@ -781,7 +758,7 @@ pub trait CustomApi { dca_parameters: Option, additional_orders: Option>, at: Option, - ) -> RpcResult; + ) -> RpcResult; #[method(name = "required_asset_ratio_for_range_order")] fn cf_required_asset_ratio_for_range_order( &self, @@ -1427,10 +1404,10 @@ where dca_parameters: Option, additional_orders: Option>, at: Option, - ) -> RpcResult { + ) -> RpcResult { self.with_runtime_api(at, |api, hash| { Ok::<_, CfApiError>( - api.cf_pool_simulate_swap_v2( + api.cf_pool_simulate_swap( hash, from_asset, to_asset, @@ -1454,25 +1431,25 @@ where .into_iter() .map(|additional_order| { match additional_order { - SwapRateV2AdditionalOrder::LimitOrder { - base_asset, - quote_asset, - side, - tick, - sell_amount, - } => state_chain_runtime::runtime_apis::SimulateSwapAdditionalOrder::LimitOrder { - base_asset, - quote_asset, - side, - tick, - sell_amount: sell_amount.unique_saturated_into(), + SwapRateV2AdditionalOrder::LimitOrder { + base_asset, + quote_asset, + side, + tick, + sell_amount, + } => state_chain_runtime::runtime_apis::SimulateSwapAdditionalOrder::LimitOrder { + base_asset, + quote_asset, + side, + tick, + sell_amount: sell_amount.unique_saturated_into(), + } } - } }) .collect() }), )? - .map(|simulated_swap_info_v2| RpcSwapOutputV3 { + .map(|simulated_swap_info_v2| RpcSwapOutputV2 { intermediary: simulated_swap_info_v2.intermediary.map(Into::into), output: simulated_swap_info_v2.output.into(), network_fee: RpcFee { @@ -2307,6 +2284,7 @@ mod test { network_fee: RpcFee { asset: Asset::Usdc, amount: 1_000u128.into() }, ingress_fee: RpcFee { asset: Asset::Flip, amount: 500u128.into() }, egress_fee: RpcFee { asset: Asset::Eth, amount: 1_000_000u128.into() }, + broker_commission: RpcFee { asset: Asset::Usdc, amount: 100u128.into() }, }) .unwrap()); } diff --git a/state-chain/custom-rpc/src/snapshots/custom_rpc__test__swap_output_serialization.snap b/state-chain/custom-rpc/src/snapshots/custom_rpc__test__swap_output_serialization.snap index 157a5cc001..40e56c40d8 100644 --- a/state-chain/custom-rpc/src/snapshots/custom_rpc__test__swap_output_serialization.snap +++ b/state-chain/custom-rpc/src/snapshots/custom_rpc__test__swap_output_serialization.snap @@ -1,5 +1,5 @@ --- source: state-chain/custom-rpc/src/lib.rs -expression: "serde_json::to_value(swap_output).unwrap()" +expression: "serde_json::to_value(RpcSwapOutputV2\n{\n output: 1_000_000_000_000_000_000u128.into(), intermediary:\n Some(1_000_000u128.into()), network_fee: RpcFee\n { asset: Asset::Usdc, amount: 1_000u128.into() }, ingress_fee: RpcFee\n { asset: Asset::Flip, amount: 500u128.into() }, egress_fee: RpcFee\n { asset: Asset::Eth, amount: 1_000_000u128.into() }, broker_commission:\n RpcFee { asset: Asset::Usdc, amount: 100u128.into() },\n}).unwrap()" --- -{"egress_fee":{"amount":"0xf4240","asset":"ETH","chain":"Ethereum"},"ingress_fee":{"amount":"0x1f4","asset":"FLIP","chain":"Ethereum"},"intermediary":"0xf4240","network_fee":{"amount":"0x3e8","asset":"USDC","chain":"Ethereum"},"output":"0xde0b6b3a7640000"} +{"broker_commission":{"amount":"0x64","asset":"USDC","chain":"Ethereum"},"egress_fee":{"amount":"0xf4240","asset":"ETH","chain":"Ethereum"},"ingress_fee":{"amount":"0x1f4","asset":"FLIP","chain":"Ethereum"},"intermediary":"0xf4240","network_fee":{"amount":"0x3e8","asset":"USDC","chain":"Ethereum"},"output":"0xde0b6b3a7640000"} diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 21ed0ad226..096d45023f 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -24,11 +24,11 @@ use crate::{ PendingBroadcasts, PendingTssCeremonies, RedemptionsInfo, SolanaNonces, }, runtime_apis::{ - runtime_decl_for_custom_runtime_api::CustomRuntimeApiV1, AuctionState, BoostPoolDepth, + runtime_decl_for_custom_runtime_api::CustomRuntimeApi, AuctionState, BoostPoolDepth, BoostPoolDetails, BrokerInfo, DispatchErrorWithMessage, FailingWitnessValidators, LiquidityProviderBoostPoolInfo, LiquidityProviderInfo, RuntimeApiPenalty, - SimulateSwapAdditionalOrder, SimulatedSwapInformation, SimulatedSwapInformationV2, - TaintedTransactionEvents, ValidatorInfo, + SimulateSwapAdditionalOrder, SimulatedSwapInformation, TaintedTransactionEvents, + ValidatorInfo, }, }; use cf_amm::{ @@ -1487,15 +1487,6 @@ impl_runtime_apis! { LiquidityPools::pool_price(base_asset, quote_asset).map_err(Into::into) } - fn cf_pool_simulate_swap( - from: Asset, - to: Asset, - amount: AssetAmount, - additional_orders: Option>, - ) -> Result { - Self::cf_pool_simulate_swap_v2(from, to, amount, Default::default(), None, additional_orders).map(Into::into) - } - /// Simulates a swap and return the intermediate (if any) and final output. /// /// If no swap rate can be calculated, returns None. This can happen if the pools are not @@ -1504,14 +1495,14 @@ impl_runtime_apis! { /// /// Note: This function must only be called through RPC, because RPC has its own storage buffer /// layer and would not affect on-chain storage. - fn cf_pool_simulate_swap_v2( + fn cf_pool_simulate_swap( from: Asset, to: Asset, amount: AssetAmount, broker_commission: BasisPoints, dca_parameters: Option, additional_orders: Option>, - ) -> Result { + ) -> Result { if let Some(additional_orders) = additional_orders { for (index, additional_order) in additional_orders.into_iter().enumerate() { match additional_order { @@ -1624,18 +1615,17 @@ impl_runtime_apis! { intermediary, output, ) = { - let output_per_chunk = swap_output_per_chunk[0].clone(); ( - output_per_chunk.network_fee_taken.unwrap_or_default() * number_of_chunks, - output_per_chunk.broker_fee_taken.unwrap_or_default() * number_of_chunks, - output_per_chunk.stable_amount.map(|amount| amount * number_of_chunks), - output_per_chunk.final_output.unwrap_or_default() * number_of_chunks, + swap_output_per_chunk[0].network_fee_taken.unwrap_or_default() * number_of_chunks, + swap_output_per_chunk[0].broker_fee_taken.unwrap_or_default() * number_of_chunks, + swap_output_per_chunk[0].stable_amount.map(|amount| amount * number_of_chunks), + swap_output_per_chunk[0].final_output.unwrap_or_default() * number_of_chunks, ) }; let (output, egress_fee) = remove_fees(IngressOrEgress::Egress, to, output); - Ok(SimulatedSwapInformationV2 { + Ok(SimulatedSwapInformation { intermediary, output, network_fee, diff --git a/state-chain/runtime/src/runtime_apis.rs b/state-chain/runtime/src/runtime_apis.rs index 314a85c5c1..df3b9e1521 100644 --- a/state-chain/runtime/src/runtime_apis.rs +++ b/state-chain/runtime/src/runtime_apis.rs @@ -146,29 +146,6 @@ pub struct SimulatedSwapInformation { pub network_fee: AssetAmount, pub ingress_fee: AssetAmount, pub egress_fee: AssetAmount, -} - -impl From for SimulatedSwapInformation { - fn from(swap_into: SimulatedSwapInformationV2) -> Self { - Self { - intermediary: swap_into.intermediary, - output: swap_into.output, - network_fee: swap_into.network_fee, - ingress_fee: swap_into.ingress_fee, - egress_fee: swap_into.egress_fee, - } - } -} - -/// Struct that represents the estimated output of a Swap V2. -/// Adds additional output for Broker and affiliate fees. -#[derive(Encode, Decode, TypeInfo)] -pub struct SimulatedSwapInformationV2 { - pub intermediary: Option, - pub output: AssetAmount, - pub network_fee: AssetAmount, - pub ingress_fee: AssetAmount, - pub egress_fee: AssetAmount, pub broker_fee: AssetAmount, } @@ -241,8 +218,23 @@ pub struct TaintedTransactionEvents { pub btc_events: Vec>, } +// READ THIS BEFORE UPDATING THIS TRAIT: +// +// ## When changing an existing method: +// - Bump the api_version of the trait, for example from #[api_version(2)] to #[api_version(3)]. +// - Annotate the old method with #[changed_in($VERSION)] where $VERSION is the *new* api_version, +// for example #[changed_in(3)]. +// - Handle the old method in the custom rpc implementation using runtime_api().api_version(). +// +// ## When adding a new method: +// - Bump the api_version of the trait, for example from #[api_version(2)] to #[api_version(3)]. +// - Create a dummy method with the same name, but no args and no return value. +// - Annotate the dummy method with #[changed_in($VERSION)] where $VERSION is the *new* +// api_version. +// - Handle the dummy method gracefully in the custom rpc implementation using +// runtime_api().api_version(). decl_runtime_apis!( - /// Definition for all runtime API interfaces. + #[api_version(2)] pub trait CustomRuntimeApi { /// Returns true if the current phase is the auction phase. fn cf_is_auction_phase() -> bool; @@ -276,20 +268,21 @@ decl_runtime_apis!( base_asset: Asset, quote_asset: Asset, ) -> Result; + #[changed_in(2)] fn cf_pool_simulate_swap( from: Asset, to: Asset, amount: AssetAmount, additional_limit_orders: Option>, ) -> Result; - fn cf_pool_simulate_swap_v2( + fn cf_pool_simulate_swap( from: Asset, to: Asset, amount: AssetAmount, broker_commission: BasisPoints, dca_parameters: Option, additional_limit_orders: Option>, - ) -> Result; + ) -> Result; fn cf_pool_info( base_asset: Asset, quote_asset: Asset,