use std::{any::Any, collections::HashMap, fmt};
use num_bigint::BigUint;
use crate::{
dto::ProtocolStateDelta,
models::token::Token,
simulation::{
errors::{SimulationError, TransitionError},
indicatively_priced::IndicativelyPriced,
swap::{
self, LimitsParams, MarginalPriceParams, QuoteParams, SwapQuoter, TransitionParams,
},
},
Bytes,
};
#[derive(Default, Debug, Clone)]
pub struct Balances {
pub component_balances: HashMap<String, HashMap<Bytes, Bytes>>,
pub account_balances: HashMap<Bytes, HashMap<Bytes, Bytes>>,
}
#[derive(Debug)]
pub struct GetAmountOutResult {
pub amount: BigUint,
pub gas: BigUint,
pub new_state: Box<dyn ProtocolSim>,
}
impl GetAmountOutResult {
pub fn new(amount: BigUint, gas: BigUint, new_state: Box<dyn ProtocolSim>) -> Self {
GetAmountOutResult { amount, gas, new_state }
}
pub fn aggregate(&mut self, other: &Self) {
self.amount = other.amount.clone();
self.gas += &other.gas;
}
}
impl fmt::Display for GetAmountOutResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "amount = {}, gas = {}", self.amount, self.gas)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Price {
pub numerator: BigUint,
pub denominator: BigUint,
}
impl Price {
pub fn new(numerator: BigUint, denominator: BigUint) -> Self {
if denominator == BigUint::ZERO {
panic!("Price denominator cannot be zero");
} else if numerator == BigUint::ZERO {
panic!("Price numerator cannot be zero");
}
Self { numerator, denominator }
}
}
#[derive(Debug, Clone)]
pub struct PricePoint {
pub amount_in: BigUint,
pub amount_out: BigUint,
pub price: f64,
}
impl PricePoint {
pub fn new(amount_in: BigUint, amount_out: BigUint, price: f64) -> Self {
Self { amount_in, amount_out, price }
}
}
#[derive(Debug, Clone)]
pub struct PoolSwap {
amount_in: BigUint,
amount_out: BigUint,
new_state: Box<dyn ProtocolSim>,
price_points: Option<Vec<PricePoint>>,
}
impl PoolSwap {
pub fn new(
amount_in: BigUint,
amount_out: BigUint,
new_state: Box<dyn ProtocolSim>,
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) -> &dyn ProtocolSim {
self.new_state.as_ref()
}
pub fn price_points(&self) -> &Option<Vec<PricePoint>> {
&self.price_points
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SwapConstraint {
TradeLimitPrice {
limit: Price,
tolerance: f64,
min_amount_in: Option<BigUint>,
max_amount_in: Option<BigUint>,
},
PoolTargetPrice {
target: Price,
tolerance: f64,
min_amount_in: Option<BigUint>,
max_amount_in: Option<BigUint>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryPoolSwapParams {
token_in: Token,
token_out: Token,
swap_constraint: SwapConstraint,
}
impl QueryPoolSwapParams {
pub fn new(token_in: Token, token_out: Token, swap_constraint: SwapConstraint) -> Self {
Self { token_in, token_out, swap_constraint }
}
pub fn token_in(&self) -> &Token {
&self.token_in
}
pub fn token_out(&self) -> &Token {
&self.token_out
}
pub fn swap_constraint(&self) -> &SwapConstraint {
&self.swap_constraint
}
}
#[typetag::serde(tag = "protocol", content = "state")]
pub trait ProtocolSim: fmt::Debug + Send + Sync + 'static {
fn fee(&self) -> f64;
fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError>;
fn get_amount_out(
&self,
amount_in: BigUint,
token_in: &Token,
token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError>;
fn get_limits(
&self,
sell_token: Bytes,
buy_token: Bytes,
) -> Result<(BigUint, BigUint), SimulationError>;
fn delta_transition(
&mut self,
delta: ProtocolStateDelta,
tokens: &HashMap<Bytes, Token>,
balances: &Balances,
) -> Result<(), TransitionError>;
#[allow(unused)]
fn query_pool_swap(&self, params: &QueryPoolSwapParams) -> Result<PoolSwap, SimulationError> {
Err(SimulationError::FatalError("query_pool_swap not implemented".into()))
}
fn clone_box(&self) -> Box<dyn ProtocolSim>;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn eq(&self, other: &dyn ProtocolSim) -> bool;
fn as_indicatively_priced(&self) -> Result<&dyn IndicativelyPriced, SimulationError> {
Err(SimulationError::FatalError("Pool State does not implement IndicativelyPriced".into()))
}
}
impl Clone for Box<dyn ProtocolSim> {
fn clone(&self) -> Box<dyn ProtocolSim> {
self.clone_box()
}
}
impl<T> ProtocolSim for T
where
T: SwapQuoter + Clone + Send + Sync + Eq + 'static,
{
fn fee(&self) -> f64 {
self.quotable_pairs()
.iter()
.map(|(t0, t1)| {
let amount = BigUint::from(10u32).pow(t0.decimals);
if let Ok(params) = QuoteParams::fixed_in(&t0.address, &t1.address, amount) {
self.fee(params)
.map(|f| f.fee())
.unwrap_or(f64::MAX)
} else {
f64::MAX
}
})
.reduce(f64::min)
.unwrap_or(f64::MAX)
}
fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
self.marginal_price(MarginalPriceParams::new(&base.address, "e.address))
.map(|r| r.price())
}
fn get_amount_out(
&self,
amount_in: BigUint,
token_in: &Token,
token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError> {
#[allow(deprecated)]
self.quote(
QuoteParams::fixed_in(&token_in.address, &token_out.address, amount_in)?
.with_new_state(),
)
.map(|r| {
GetAmountOutResult::new(
r.amount_out().clone(),
r.gas().clone(),
r.new_state()
.expect("quote includes new state")
.to_protocol_sim(),
)
})
}
fn get_limits(
&self,
sell_token: Bytes,
buy_token: Bytes,
) -> Result<(BigUint, BigUint), SimulationError> {
self.swap_limits(LimitsParams::new(&sell_token, &buy_token))
.map(|r| (r.range_in().upper().clone(), r.range_out().upper().clone()))
}
fn delta_transition(
&mut self,
delta: ProtocolStateDelta,
tokens: &HashMap<Bytes, Token>,
balances: &Balances,
) -> Result<(), TransitionError> {
self.delta_transition(TransitionParams::new(delta, tokens, balances))
.map(|_| ())
}
fn query_pool_swap(&self, params: &QueryPoolSwapParams) -> Result<PoolSwap, SimulationError> {
let constraint = match params.swap_constraint.clone() {
SwapConstraint::TradeLimitPrice { limit, tolerance, min_amount_in, max_amount_in } => {
swap::SwapConstraint::TradeLimitPrice {
limit,
tolerance,
min_amount_in,
max_amount_in,
}
}
SwapConstraint::PoolTargetPrice { target, tolerance, min_amount_in, max_amount_in } => {
swap::SwapConstraint::PoolTargetPrice {
target,
tolerance,
min_amount_in,
max_amount_in,
}
}
};
#[allow(deprecated)]
self.query_swap(swap::QuerySwapParams::new(
¶ms.token_in.address,
¶ms.token_out.address,
constraint,
))
.map(|r| {
PoolSwap::new(
r.amount_in().clone(),
r.amount_out().clone(),
r.new_state().unwrap().to_protocol_sim(),
r.price_points().as_ref().map(|points| {
points
.iter()
.map(|p| {
PricePoint::new(
p.amount_in().clone(),
p.amount_out().clone(),
p.price(),
)
})
.collect()
}),
)
})
}
fn clone_box(&self) -> Box<dyn ProtocolSim> {
#[allow(deprecated)]
self.to_protocol_sim()
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn eq(&self, other: &dyn ProtocolSim) -> bool {
if let Some(other) = other.as_any().downcast_ref::<T>() {
self == other
} else {
false
}
}
fn typetag_name(&self) -> &'static str {
self.typetag_name()
}
fn typetag_deserialize(&self) {
self.typetag_deserialize()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
struct DummyProtocol {
reserve_0: u64,
reserve_1: u64,
}
#[typetag::serde]
impl ProtocolSim for DummyProtocol {
fn clone_box(&self) -> Box<dyn ProtocolSim> {
todo!()
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
todo!()
}
fn eq(&self, other: &dyn ProtocolSim) -> bool {
if let Some(other) = other.as_any().downcast_ref::<Self>() {
self.reserve_0 == other.reserve_0 && self.reserve_1 == other.reserve_1
} else {
false
}
}
fn fee(&self) -> f64 {
todo!()
}
fn spot_price(&self, _base: &Token, _quote: &Token) -> Result<f64, SimulationError> {
todo!()
}
fn get_amount_out(
&self,
_amount_in: BigUint,
_token_in: &Token,
_token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError> {
todo!()
}
fn get_limits(
&self,
_sell_token: Bytes,
_buy_token: Bytes,
) -> Result<(BigUint, BigUint), SimulationError> {
todo!()
}
fn delta_transition(
&mut self,
_delta: ProtocolStateDelta,
_tokens: &HashMap<Bytes, Token>,
_balances: &Balances,
) -> Result<(), TransitionError> {
todo!()
}
}
let state = DummyProtocol { reserve_0: 1, reserve_1: 2 };
assert_eq!(serde_json::to_string(&state).unwrap(), r#"{"reserve_0":1,"reserve_1":2}"#);
assert_eq!(
serde_json::to_string(&state as &dyn ProtocolSim).unwrap(),
r#"{"protocol":"DummyProtocol","state":{"reserve_0":1,"reserve_1":2}}"#
);
let deserialized: Box<dyn ProtocolSim> = serde_json::from_str(
r#"{"protocol":"DummyProtocol","state":{"reserve_0":1,"reserve_1":2}}"#,
)
.unwrap();
assert!(deserialized.eq(&state));
}
}