use gmsol_model::{
action::{deposit::DepositReport, withdraw::WithdrawReport},
LiquidityMarketMutExt, MarketAction,
};
use gmsol_programs::{gmsol_store::types::CreateShiftParams, model::SwapPricingKind};
use solana_sdk::pubkey::Pubkey;
use typed_builder::TypedBuilder;
use super::{SimulationOptions, Simulator};
#[derive(Debug)]
pub struct ShiftSimulationOutput {
pub(crate) withdraw: Box<WithdrawReport<u128>>,
pub(crate) deposit: Box<DepositReport<u128, i128>>,
}
impl ShiftSimulationOutput {
pub fn withdraw_report(&self) -> &WithdrawReport<u128> {
&self.withdraw
}
pub fn deposit_report(&self) -> &DepositReport<u128, i128> {
&self.deposit
}
}
#[derive(Debug, TypedBuilder)]
pub struct ShiftSimulation<'a> {
simulator: &'a mut Simulator,
params: &'a CreateShiftParams,
from_market_token: &'a Pubkey,
to_market_token: &'a Pubkey,
}
impl ShiftSimulation<'_> {
pub fn execute_with_options(
self,
options: SimulationOptions,
) -> crate::Result<ShiftSimulationOutput> {
let Self {
simulator,
params,
from_market_token,
to_market_token,
} = self;
if params.from_market_token_amount == 0 {
return Err(crate::Error::custom("[sim] empty shift"));
}
let (from_market, prices_for_from_market) =
simulator.get_market_with_prices(from_market_token)?;
let (to_market, prices_for_to_market) =
simulator.get_market_with_prices(to_market_token)?;
if from_market.meta.long_token_mint != to_market.meta.long_token_mint
|| from_market.meta.short_token_mint != to_market.meta.short_token_mint
{
return Err(crate::Error::custom(format!(
"[sim] shift from `{from_market_token}` to `{to_market_token}` is impossible"
)));
}
let withdraw = {
let (from_market, maybe_vi_map) = if options.disable_vis {
(
simulator
.get_market_mut(from_market_token)
.expect("must exist"),
None,
)
} else {
let (market, vi_map) = simulator.get_market_and_vis_mut(from_market_token)?;
(market, Some(vi_map))
};
from_market.with_swap_pricing(SwapPricingKind::Shift, |market| match maybe_vi_map {
None => market.with_vis_disabled(|market| {
market
.withdraw(
params.from_market_token_amount.into(),
prices_for_from_market,
)?
.execute()
}),
Some(vi_map) => market.with_vi_models(vi_map, |market| {
market
.withdraw(
params.from_market_token_amount.into(),
prices_for_from_market,
)?
.execute()
}),
})?
};
let (long_token_amount, short_token_amount) = (
*withdraw.long_token_output(),
*withdraw.short_token_output(),
);
if long_token_amount == 0 && short_token_amount == 0 {
return Err(crate::Error::custom(
"[sim] shift cannot be completed due to empty withdrawal output",
));
}
let deposit = {
let (to_market, maybe_vi_map) = if options.disable_vis {
(
simulator
.get_market_mut(to_market_token)
.expect("must exist"),
None,
)
} else {
let (market, vi_map) = simulator.get_market_and_vis_mut(to_market_token)?;
(market, Some(vi_map))
};
to_market.with_swap_pricing(SwapPricingKind::Shift, |market| match maybe_vi_map {
None => market.with_vis_disabled(|market| {
market
.deposit(long_token_amount, short_token_amount, prices_for_to_market)?
.execute()
}),
Some(vi_map) => market.with_vi_models(vi_map, |market| {
market
.deposit(long_token_amount, short_token_amount, prices_for_to_market)?
.execute()
}),
})?
};
let minted = deposit.minted();
let min_to_market_token_amount = params.min_to_market_token_amount;
if *minted < u128::from(min_to_market_token_amount) {
return Err(crate::Error::custom(format!(
"[sim] insufficient output amount: {minted} < {min_to_market_token_amount}",
)));
}
Ok(ShiftSimulationOutput {
withdraw: Box::new(withdraw),
deposit: Box::new(deposit),
})
}
}