Skip to content

Commit

Permalink
feat: cf_pool_swap_rate_v3 with broker fee and DCA support (#5386)
Browse files Browse the repository at this point in the history
* 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

* Removed Affiliate fees
Used `try_execute_without_violations` instead of `swap_with_network_fee`
to estimate swap result.

* fix:

- remove v3 types/methods
- add versioning to runtime api
- avoid clone

---------

Co-authored-by: Daniel <daniel@chainflip.io>
  • Loading branch information
kylezs and dandanlen committed Nov 7, 2024
1 parent f58608c commit e755a80
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 50 deletions.
85 changes: 63 additions & 22 deletions state-chain/custom-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, Asset, AssetAmount, BasisPoints, BlockNumber, BroadcastId, DcaParameters,
EpochIndex, ForeignChain, NetworkEnvironment, SemVer, SwapId, SwapRequestId,
};
use cf_utilities::rpc::NumberOrHex;
use core::ops::Range;
Expand Down Expand Up @@ -420,6 +420,7 @@ pub struct RpcSwapOutputV2 {
pub network_fee: RpcFee,
pub ingress_fee: RpcFee,
pub egress_fee: RpcFee,
pub broker_commission: RpcFee,
}

#[derive(Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -747,6 +748,17 @@ pub trait CustomApi {
additional_orders: Option<Vec<SwapRateV2AdditionalOrder>>,
at: Option<state_chain_runtime::Hash>,
) -> RpcResult<RpcSwapOutputV2>;
#[method(name = "swap_rate_v3")]
fn cf_pool_swap_rate_v3(
&self,
from_asset: Asset,
to_asset: Asset,
amount: U256,
broker_commission: BasisPoints,
dca_parameters: Option<DcaParameters>,
additional_orders: Option<Vec<SwapRateV2AdditionalOrder>>,
at: Option<state_chain_runtime::Hash>,
) -> RpcResult<RpcSwapOutputV2>;
#[method(name = "required_asset_ratio_for_range_order")]
fn cf_required_asset_ratio_for_range_order(
&self,
Expand Down Expand Up @@ -1353,6 +1365,28 @@ where
amount: U256,
additional_orders: Option<Vec<SwapRateV2AdditionalOrder>>,
at: Option<state_chain_runtime::Hash>,
) -> RpcResult<RpcSwapOutputV2> {
self.cf_pool_swap_rate_v3(
from_asset,
to_asset,
amount,
Default::default(),
None,
additional_orders,
at,
)
.map(Into::into)
}

fn cf_pool_swap_rate_v3(
&self,
from_asset: Asset,
to_asset: Asset,
amount: U256,
broker_commission: BasisPoints,
dca_parameters: Option<DcaParameters>,
additional_orders: Option<Vec<SwapRateV2AdditionalOrder>>,
at: Option<state_chain_runtime::Hash>,
) -> RpcResult<RpcSwapOutputV2> {
self.with_runtime_api(at, |api, hash| {
Ok::<_, CfApiError>(
Expand All @@ -1363,7 +1397,7 @@ where
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 {
Expand All @@ -1373,43 +1407,49 @@ where
.map_err(|s| {
ErrorObject::owned(ErrorCode::InvalidParams.code(), s, None::<()>)
})?,
broker_commission,
dca_parameters,
additional_orders.map(|additional_orders| {
additional_orders
.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| RpcSwapOutputV2 {
intermediary: simulated_swap_info.intermediary.map(Into::into),
output: simulated_swap_info.output.into(),
.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 {
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(),
},
})?,
)
Expand Down Expand Up @@ -2227,6 +2267,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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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"}
20 changes: 10 additions & 10 deletions state-chain/pallets/cf-swapping/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ struct FeeTaken {
}

#[derive(CloneNoBound, DebugNoBound)]
struct SwapState<T: Config> {
swap: Swap<T>,
network_fee_taken: Option<AssetAmount>,
broker_fee_taken: Option<AssetAmount>,
stable_amount: Option<AssetAmount>,
final_output: Option<AssetAmount>,
pub struct SwapState<T: Config> {
pub swap: Swap<T>,
pub network_fee_taken: Option<AssetAmount>,
pub broker_fee_taken: Option<AssetAmount>,
pub stable_amount: Option<AssetAmount>,
pub final_output: Option<AssetAmount>,
}

impl<T: Config> SwapState<T> {
Expand Down Expand Up @@ -148,7 +148,7 @@ impl<T: Config> SwapState<T> {
#[repr(u8)]
#[derive(Clone, DebugNoBound, PartialEq, Eq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
enum FeeType<T: Config> {
pub enum FeeType<T: Config> {
NetworkFee = 0,
BrokerFee(Beneficiaries<T::AccountId>),
}
Expand Down Expand Up @@ -180,7 +180,7 @@ pub struct SwapLegInfo {
}

impl<T: Config> Swap<T> {
fn new(
pub fn new(
swap_id: SwapId,
swap_request_id: SwapRequestId,
from: Asset,
Expand All @@ -201,7 +201,7 @@ impl<T: Config> Swap<T> {
}
}

enum BatchExecutionError<T: Config> {
pub enum BatchExecutionError<T: Config> {
SwapLegFailed {
asset: Asset,
direction: SwapLeg,
Expand Down Expand Up @@ -1204,7 +1204,7 @@ pub mod pallet {
}

#[transactional]
fn try_execute_without_violations(
pub fn try_execute_without_violations(
swaps: Vec<Swap<T>>,
) -> Result<Vec<SwapState<T>>, BatchExecutionError<T>> {
let mut swaps: Vec<_> = swaps.into_iter().map(SwapState::new).collect();
Expand Down
72 changes: 59 additions & 13 deletions state-chain/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ 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, TaintedTransactionEvents,
Expand All @@ -50,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::{
BasisPoints, Beneficiary, BroadcastId, DcaParameters, EpochIndex, NetworkEnvironment,
STABLE_ASSET,
};
use cf_runtime_upgrade_utilities::VersionedMigration;
use cf_traits::{
AdjustedFeeEstimationApi, AssetConverter, BalanceApi, DummyEgressSuccessWitnesser,
Expand All @@ -72,6 +75,7 @@ use pallet_cf_pools::{
AskBidMap, AssetPair, HistoricalEarnedFees, OrderId, PoolLiquidity, PoolOrderbook, PoolPriceV1,
PoolPriceV2, UnidirectionalPoolDepth,
};
use pallet_cf_swapping::{BatchExecutionError, FeeType, Swap};
use runtime_apis::ChainAccounts;

use crate::{chainflip::EvmLimit, runtime_apis::TaintedTransactionEvent};
Expand Down Expand Up @@ -121,7 +125,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::*;
Expand Down Expand Up @@ -1527,6 +1531,8 @@ impl_runtime_apis! {
from: Asset,
to: Asset,
amount: AssetAmount,
broker_commission: BasisPoints,
dca_parameters: Option<DcaParameters>,
additional_orders: Option<Vec<SimulateSwapAdditionalOrder>>,
) -> Result<SimulatedSwapInformation, DispatchErrorWithMessage> {
if let Some(additional_orders) = additional_orders {
Expand Down Expand Up @@ -1602,20 +1608,62 @@ impl_runtime_apis! {

let (amount_to_swap, ingress_fee) = remove_fees(IngressOrEgress::Ingress, from, amount);

let swap_output = Swapping::swap_with_network_fee(
from,
to,
amount_to_swap,
)?;
// 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 / 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,
) = {
(
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, swap_output.output);
let (output, egress_fee) = remove_fees(IngressOrEgress::Egress, to, output);

Ok(SimulatedSwapInformation {
intermediary: swap_output.intermediary,
intermediary,
output,
network_fee: swap_output.network_fee,
network_fee,
ingress_fee,
egress_fee,
broker_fee,
})
}

Expand All @@ -1624,9 +1672,7 @@ impl_runtime_apis! {
}

fn cf_lp_events() -> Vec<pallet_cf_pools::Event<Runtime>> {

System::read_events_no_consensus().filter_map(|event_record| {

if let RuntimeEvent::LiquidityPools(pools_event) = event_record.event {
Some(pools_event)
} else {
Expand Down
Loading

0 comments on commit e755a80

Please sign in to comment.