use std::{collections::HashMap, fmt, fmt::Debug, sync::Arc};
use itertools::Itertools;
use num_bigint::BigUint;
use crate::{
dto::ProtocolStateDelta,
models::{protocol::ProtocolComponent, token::Token},
simulation::{
errors::{SimulationError, TransitionError},
indicatively_priced::IndicativelyPriced,
protocol_sim::{Balances, Price, ProtocolSim},
},
Bytes,
};
pub type SimulationResult<T> = Result<T, SimulationError>;
pub type TokenAddress = Bytes;
macro_rules! params_with_context {
(
$(#[$meta:meta])*
$vis:vis struct $name:ident $(<$($gen:tt),*>)? {
$($field:ident: $ty:ty),* $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Clone)]
$vis struct $name $(<$($gen),*>)? {
context: Context,
$($field: $ty,)*
}
impl $(<$($gen),*>)? $name $(<$($gen),*>)? {
pub fn with_context(mut self, context: Context) -> Self {
self.context = context;
self
}
pub fn context(&self) -> &Context {
&self.context
}
}
};
}
#[derive(Debug, Clone)]
pub struct Context {}
impl Default for Context {
fn default() -> Self {
Self {}
}
}
params_with_context! {
pub struct QuoteParams<'a>{
token_in: &'a TokenAddress,
token_out: &'a TokenAddress,
amount: QuoteAmount,
return_new_state: bool,
}
}
#[derive(Debug, Clone)]
pub enum QuoteAmount {
FixedIn(BigUint),
FixedOut(BigUint),
}
impl<'a> QuoteParams<'a> {
pub fn fixed_in(
token_in: &'a TokenAddress,
token_out: &'a TokenAddress,
amount: BigUint,
) -> SimulationResult<Self> {
if token_out == token_in {
return Err(SimulationError::InvalidInput(
"Quote tokens have to differ!".to_string(),
None,
))
}
Ok(Self {
context: Context::default(),
token_in,
token_out,
amount: QuoteAmount::FixedIn(amount),
return_new_state: false,
})
}
pub fn fixed_out(
token_in: &'a TokenAddress,
token_out: &'a TokenAddress,
amount: BigUint,
) -> SimulationResult<Self> {
if token_out == token_in {
return Err(SimulationError::InvalidInput(
"Quote tokens have to differ!".to_string(),
None,
))
}
Ok(Self {
context: Context::default(),
token_in,
token_out,
amount: QuoteAmount::FixedOut(amount),
return_new_state: false,
})
}
pub fn amount(&self) -> &QuoteAmount {
&self.amount
}
pub fn with_new_state(mut self) -> Self {
self.return_new_state = true;
self
}
pub fn token_in(&self) -> &TokenAddress {
self.token_in
}
pub fn token_out(&self) -> &TokenAddress {
self.token_out
}
pub fn should_return_new_state(&self) -> bool {
self.return_new_state
}
}
params_with_context! {
pub struct LimitsParams<'a> {
token_in: &'a TokenAddress,
token_out: &'a TokenAddress,
}
}
impl<'a> LimitsParams<'a> {
pub fn new(token_in: &'a TokenAddress, token_out: &'a TokenAddress) -> Self {
Self { context: Context::default(), token_in, token_out }
}
pub fn token_in(&self) -> &TokenAddress {
self.token_in
}
pub fn token_out(&self) -> &TokenAddress {
self.token_out
}
}
params_with_context! {
pub struct MarginalPriceParams<'a> {
token_in: &'a TokenAddress,
token_out: &'a TokenAddress,
}
}
impl<'a> MarginalPriceParams<'a> {
pub fn new(token_in: &'a TokenAddress, token_out: &'a TokenAddress) -> Self {
Self { context: Context::default(), token_in, token_out }
}
pub fn token_in(&self) -> &TokenAddress {
self.token_in
}
pub fn token_out(&self) -> &TokenAddress {
self.token_out
}
}
pub struct SwapFee {
fee: f64,
}
impl SwapFee {
pub fn new(fee: f64) -> Self {
Self { fee }
}
pub fn fee(&self) -> f64 {
self.fee
}
}
pub struct MarginalPrice {
price: f64,
}
impl MarginalPrice {
pub fn new(price: f64) -> Self {
Self { price }
}
pub fn price(&self) -> f64 {
self.price
}
}
pub struct Quote {
amount_out: BigUint,
gas: BigUint,
new_state: Option<Arc<dyn SwapQuoter>>,
}
impl Quote {
pub fn new(amount_out: BigUint, gas: BigUint, new_state: Option<Arc<dyn SwapQuoter>>) -> Self {
Self { amount_out, gas, new_state }
}
pub fn amount_out(&self) -> &BigUint {
&self.amount_out
}
pub fn gas(&self) -> &BigUint {
&self.gas
}
pub fn new_state(&self) -> Option<Arc<dyn SwapQuoter>> {
self.new_state.clone()
}
}
pub struct Range {
lower: BigUint,
upper: BigUint,
}
impl Range {
pub fn new(lower: BigUint, upper: BigUint) -> SimulationResult<Self> {
if lower > upper {
return Err(SimulationError::InvalidInput(
"Invalid range! Argument lower > upper".to_string(),
None,
))
}
Ok(Self { lower, upper })
}
pub fn lower(&self) -> &BigUint {
&self.lower
}
pub fn upper(&self) -> &BigUint {
&self.upper
}
}
pub struct SwapLimits {
range_in: Range,
range_out: Range,
}
impl SwapLimits {
pub fn new(range_in: Range, range_out: Range) -> Self {
Self { range_in, range_out }
}
pub fn range_in(&self) -> &Range {
&self.range_in
}
pub fn range_out(&self) -> &Range {
&self.range_out
}
}
params_with_context! {
pub struct TransitionParams<'a> {
delta: ProtocolStateDelta,
tokens: &'a HashMap<Bytes, Token>,
balances: &'a Balances,
}
}
impl<'a> TransitionParams<'a> {
pub fn new(
delta: ProtocolStateDelta,
tokens: &'a HashMap<Bytes, Token>,
balances: &'a Balances,
) -> Self {
Self { context: Context::default(), delta, tokens, balances }
}
pub fn delta(&self) -> &ProtocolStateDelta {
&self.delta
}
pub fn tokens(&self) -> &HashMap<Bytes, Token> {
self.tokens
}
pub fn balances(&self) -> &Balances {
self.balances
}
}
pub struct Transition {}
impl Default for Transition {
fn default() -> Self {
Self {}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SwapConstraint {
#[non_exhaustive]
TradeLimitPrice {
limit: Price,
tolerance: f64,
min_amount_in: Option<BigUint>,
max_amount_in: Option<BigUint>,
},
#[non_exhaustive]
PoolTargetPrice {
target: Price,
tolerance: f64,
min_amount_in: Option<BigUint>,
max_amount_in: Option<BigUint>,
},
}
impl SwapConstraint {
pub fn trade_limit_price(limit: Price, tolerance: f64) -> Self {
SwapConstraint::TradeLimitPrice {
limit,
tolerance,
min_amount_in: None,
max_amount_in: None,
}
}
pub fn pool_target_price(target: Price, tolerance: f64) -> Self {
SwapConstraint::PoolTargetPrice {
target,
tolerance,
min_amount_in: None,
max_amount_in: None,
}
}
pub fn with_lower_bound(mut self, lower: BigUint) -> SimulationResult<Self> {
match &mut self {
SwapConstraint::PoolTargetPrice { min_amount_in, .. } => {
*min_amount_in = Some(lower);
Ok(self)
}
SwapConstraint::TradeLimitPrice { min_amount_in, .. } => {
*min_amount_in = Some(lower);
Ok(self)
}
}
}
pub fn with_upper_bound(mut self, upper: BigUint) -> SimulationResult<Self> {
match &mut self {
SwapConstraint::PoolTargetPrice { max_amount_in, .. } => {
*max_amount_in = Some(upper);
Ok(self)
}
SwapConstraint::TradeLimitPrice { max_amount_in, .. } => {
*max_amount_in = Some(upper);
Ok(self)
}
}
}
}
params_with_context! {
pub struct QuerySwapParams<'a> {
token_in: &'a TokenAddress,
token_out: &'a TokenAddress,
swap_constraint: SwapConstraint,
}
}
impl<'a> QuerySwapParams<'a> {
pub fn new(
token_in: &'a TokenAddress,
token_out: &'a TokenAddress,
swap_constraint: SwapConstraint,
) -> Self {
Self { context: Context::default(), token_in, token_out, swap_constraint }
}
pub fn token_in(&self) -> &'a TokenAddress {
self.token_in
}
pub fn token_out(&self) -> &'a TokenAddress {
self.token_out
}
pub fn swap_constraint(&self) -> &SwapConstraint {
&self.swap_constraint
}
}
pub struct Swap {
amount_in: BigUint,
amount_out: BigUint,
new_state: Option<Arc<dyn SwapQuoter>>,
price_points: Option<Vec<PricePoint>>,
}
#[derive(Debug, Clone)]
pub struct PricePoint {
amount_in: BigUint,
amount_out: BigUint,
price: f64,
}
impl PricePoint {
pub fn amount_in(&self) -> &BigUint {
&self.amount_in
}
pub fn amount_out(&self) -> &BigUint {
&self.amount_out
}
pub fn price(&self) -> f64 {
self.price
}
}
impl Swap {
pub fn new(
amount_in: BigUint,
amount_out: BigUint,
new_state: Option<Arc<dyn SwapQuoter>>,
price_points: Option<Vec<PricePoint>>,
) -> Self {
Self { amount_in, amount_out, new_state, price_points }
}
pub fn amount_in(&self) -> &BigUint {
&self.amount_in
}
pub fn amount_out(&self) -> &BigUint {
&self.amount_out
}
pub fn new_state(&self) -> Option<Arc<dyn SwapQuoter>> {
self.new_state.clone()
}
pub fn price_points(&self) -> &Option<Vec<PricePoint>> {
&self.price_points
}
}
#[typetag::serde(tag = "protocol", content = "state")]
pub trait SwapQuoter: fmt::Debug + Send + Sync + 'static {
fn component(&self) -> Arc<ProtocolComponent<Arc<Token>>>;
fn quotable_pairs(&self) -> Vec<(Arc<Token>, Arc<Token>)> {
let component = self.component();
component
.tokens
.iter()
.permutations(2)
.map(|token| (token[0].clone(), token[1].clone()))
.collect()
}
fn fee(&self, params: QuoteParams) -> SimulationResult<SwapFee>;
fn marginal_price(&self, params: MarginalPriceParams) -> SimulationResult<MarginalPrice>;
fn quote(&self, params: QuoteParams) -> SimulationResult<Quote>;
fn swap_limits(&self, params: LimitsParams) -> SimulationResult<SwapLimits>;
fn query_swap(&self, params: QuerySwapParams) -> SimulationResult<Swap>;
fn delta_transition(&mut self, params: TransitionParams)
-> Result<Transition, TransitionError>;
fn clone_box(&self) -> Box<dyn SwapQuoter>;
fn as_indicatively_priced(&self) -> Result<&dyn IndicativelyPriced, SimulationError> {
Err(SimulationError::FatalError("Pool State does not implement IndicativelyPriced".into()))
}
#[deprecated(note = "ProtocolSim is deprecated. This method will be removed in v1.0.0")]
fn to_protocol_sim(&self) -> Box<dyn ProtocolSim>;
}
#[cfg(test)]
pub trait SwapQuoterTestExt {
fn eq(&self, other: &dyn SwapQuoter) -> bool;
}