Skip to content

Commit a448aae

Browse files
authored
feat: v1 quote interface proposal (#837)
2 parents c99c684 + bccd3a4 commit a448aae

File tree

10 files changed

+1220
-19
lines changed

10 files changed

+1220
-19
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tycho-common/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] }
3535
bytes = "1.5.0"
3636
mockall = { workspace = true, optional = true }
3737
num-bigint = "0.4"
38-
deepsize.workspace = true # perf: make optional, as used only by the indexer for memory profiling
38+
deepsize.workspace = true# perf: make optional, as used only by the indexer for memory profiling
39+
itertools = "0.10.5"
3940

4041
[dev-dependencies]
4142
serde_json.workspace = true

tycho-common/src/dto.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -687,14 +687,21 @@ impl DeepSizeOf for ProtocolComponent {
687687
}
688688
}
689689

690-
impl From<models::protocol::ProtocolComponent> for ProtocolComponent {
691-
fn from(value: models::protocol::ProtocolComponent) -> Self {
690+
impl<T> From<models::protocol::ProtocolComponent<T>> for ProtocolComponent
691+
where
692+
T: Into<Address> + Clone,
693+
{
694+
fn from(value: models::protocol::ProtocolComponent<T>) -> Self {
692695
Self {
693696
id: value.id,
694697
protocol_system: value.protocol_system,
695698
protocol_type_name: value.protocol_type_name,
696699
chain: value.chain.into(),
697-
tokens: value.tokens,
700+
tokens: value
701+
.tokens
702+
.into_iter()
703+
.map(|t| t.into())
704+
.collect(),
698705
contract_ids: value.contract_addresses,
699706
static_attributes: value.static_attributes,
700707
change: value.change.into(),

tycho-common/src/models/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ impl ProtocolType {
280280
}
281281
}
282282

283-
#[derive(Debug, PartialEq, Default, Copy, Clone, Deserialize, Serialize, DeepSizeOf)]
283+
#[derive(Debug, PartialEq, Eq, Default, Copy, Clone, Deserialize, Serialize, DeepSizeOf)]
284284
pub enum ChangeType {
285285
#[default]
286286
Update,

tycho-common/src/models/protocol.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::collections::{HashMap, HashSet};
1+
use std::{
2+
collections::{HashMap, HashSet},
3+
sync::Arc,
4+
};
25

36
use chrono::NaiveDateTime;
47
use deepsize::{Context, DeepSizeOf};
@@ -7,8 +10,8 @@ use serde::{Deserialize, Serialize};
710

811
use crate::{
912
models::{
10-
Address, AttrStoreKey, Balance, Chain, ChangeType, ComponentId, MergeError, StoreVal,
11-
TxHash,
13+
token::Token, Address, AttrStoreKey, Balance, Chain, ChangeType, ComponentId, MergeError,
14+
StoreVal, TxHash,
1215
},
1316
Bytes,
1417
};
@@ -24,28 +27,31 @@ use crate::{
2427
///
2528
/// Every values of a `ProtocolComponent` must be static, they can't ever be changed after creation.
2629
/// The dynamic values associated to a component must be given using `ProtocolComponentState`.
27-
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
28-
pub struct ProtocolComponent {
30+
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
31+
pub struct ProtocolComponent<Token: Into<Address> + Clone = Address> {
2932
pub id: ComponentId,
3033
pub protocol_system: String,
3134
pub protocol_type_name: String,
3235
pub chain: Chain,
33-
pub tokens: Vec<Address>,
36+
pub tokens: Vec<Token>,
3437
pub contract_addresses: Vec<Address>,
3538
pub static_attributes: HashMap<AttrStoreKey, StoreVal>,
3639
pub change: ChangeType,
3740
pub creation_tx: TxHash,
3841
pub created_at: NaiveDateTime,
3942
}
4043

41-
impl ProtocolComponent {
44+
impl<T> ProtocolComponent<T>
45+
where
46+
T: Into<Address> + Clone,
47+
{
4248
#[allow(clippy::too_many_arguments)]
4349
pub fn new(
4450
id: &str,
4551
protocol_system: &str,
4652
protocol_type_name: &str,
4753
chain: Chain,
48-
tokens: Vec<Address>,
54+
tokens: Vec<T>,
4955
contract_addresses: Vec<Address>,
5056
static_attributes: HashMap<AttrStoreKey, StoreVal>,
5157
change: ChangeType,
@@ -67,6 +73,15 @@ impl ProtocolComponent {
6773
}
6874
}
6975

76+
impl ProtocolComponent<Arc<Token>> {
77+
pub fn get_token(&self, address: &Address) -> Option<Arc<Token>> {
78+
self.tokens
79+
.iter()
80+
.find(|t| &t.address == address)
81+
.map(Arc::clone)
82+
}
83+
}
84+
7085
impl DeepSizeOf for ProtocolComponent {
7186
fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
7287
self.id.deep_size_of_children(ctx) +

tycho-common/src/models/token.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
collections::HashMap,
33
hash::{Hash, Hasher},
4+
sync::Arc,
45
};
56

67
use deepsize::DeepSizeOf;
@@ -97,6 +98,12 @@ impl Hash for Token {
9798
}
9899
}
99100

101+
impl From<Arc<Token>> for Address {
102+
fn from(val: Arc<Token>) -> Self {
103+
val.address.clone()
104+
}
105+
}
106+
100107
impl TryFrom<ResponseToken> for Token {
101108
type Error = ();
102109

tycho-common/src/simulation/errors.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,14 @@ pub enum SimulationError {
2323
}
2424

2525
#[derive(Debug)]
26-
pub enum TransitionError<T> {
27-
OutOfOrder { state: T, event: T },
26+
pub enum TransitionError {
2827
MissingAttribute(String),
2928
DecodeError(String),
3029
InvalidEventType(),
3130
SimulationError(SimulationError),
3231
}
3332

34-
impl<T> From<SimulationError> for TransitionError<T> {
33+
impl From<SimulationError> for TransitionError {
3534
fn from(error: SimulationError) -> Self {
3635
TransitionError::SimulationError(error)
3736
}

tycho-common/src/simulation/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod errors;
22
pub mod indicatively_priced;
33
pub mod protocol_sim;
4+
pub mod swap;

tycho-common/src/simulation/protocol_sim.rs

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ use crate::{
88
simulation::{
99
errors::{SimulationError, TransitionError},
1010
indicatively_priced::IndicativelyPriced,
11+
swap::{
12+
self, LimitsParams, MarginalPriceParams, QuoteParams, SwapQuoter, TransitionParams,
13+
},
1114
},
1215
Bytes,
1316
};
1417

15-
#[derive(Default)]
18+
#[derive(Default, Debug, Clone)]
1619
pub struct Balances {
1720
pub component_balances: HashMap<String, HashMap<Bytes, Bytes>>,
1821
pub account_balances: HashMap<Bytes, HashMap<Bytes, Bytes>>,
@@ -347,7 +350,7 @@ pub trait ProtocolSim: fmt::Debug + Send + Sync + 'static {
347350
delta: ProtocolStateDelta,
348351
tokens: &HashMap<Bytes, Token>,
349352
balances: &Balances,
350-
) -> Result<(), TransitionError<String>>;
353+
) -> Result<(), TransitionError>;
351354

352355
/// Calculates the swap volume required to achieve the provided goal when trading against this
353356
/// pool.
@@ -403,6 +406,149 @@ impl Clone for Box<dyn ProtocolSim> {
403406
}
404407
}
405408

409+
impl<T> ProtocolSim for T
410+
where
411+
T: SwapQuoter + Clone + Send + Sync + Eq + 'static,
412+
{
413+
fn fee(&self) -> f64 {
414+
self.quotable_pairs()
415+
.iter()
416+
.map(|(t0, t1)| {
417+
let amount = BigUint::from(10u32).pow(t0.decimals);
418+
if let Ok(params) = QuoteParams::fixed_in(&t0.address, &t1.address, amount) {
419+
self.fee(params)
420+
.map(|f| f.fee())
421+
.unwrap_or(f64::MAX)
422+
} else {
423+
f64::MAX
424+
}
425+
})
426+
.reduce(f64::min)
427+
.unwrap_or(f64::MAX)
428+
}
429+
430+
fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
431+
self.marginal_price(MarginalPriceParams::new(&base.address, &quote.address))
432+
.map(|r| r.price())
433+
}
434+
435+
fn get_amount_out(
436+
&self,
437+
amount_in: BigUint,
438+
token_in: &Token,
439+
token_out: &Token,
440+
) -> Result<GetAmountOutResult, SimulationError> {
441+
#[allow(deprecated)]
442+
self.quote(
443+
QuoteParams::fixed_in(&token_in.address, &token_out.address, amount_in)?
444+
.with_new_state(),
445+
)
446+
.map(|r| {
447+
GetAmountOutResult::new(
448+
r.amount_out().clone(),
449+
r.gas().clone(),
450+
r.new_state()
451+
.expect("quote includes new state")
452+
.to_protocol_sim(),
453+
)
454+
})
455+
}
456+
457+
fn get_limits(
458+
&self,
459+
sell_token: Bytes,
460+
buy_token: Bytes,
461+
) -> Result<(BigUint, BigUint), SimulationError> {
462+
self.swap_limits(LimitsParams::new(&sell_token, &buy_token))
463+
.map(|r| (r.range_in().upper().clone(), r.range_out().upper().clone()))
464+
}
465+
466+
fn delta_transition(
467+
&mut self,
468+
delta: ProtocolStateDelta,
469+
tokens: &HashMap<Bytes, Token>,
470+
balances: &Balances,
471+
) -> Result<(), TransitionError> {
472+
self.delta_transition(TransitionParams::new(delta, tokens, balances))
473+
.map(|_| ())
474+
}
475+
476+
fn query_pool_swap(&self, params: &QueryPoolSwapParams) -> Result<PoolSwap, SimulationError> {
477+
let constraint = match params.swap_constraint.clone() {
478+
SwapConstraint::TradeLimitPrice { limit, tolerance, min_amount_in, max_amount_in } => {
479+
swap::SwapConstraint::TradeLimitPrice {
480+
limit,
481+
tolerance,
482+
min_amount_in,
483+
max_amount_in,
484+
}
485+
}
486+
SwapConstraint::PoolTargetPrice { target, tolerance, min_amount_in, max_amount_in } => {
487+
swap::SwapConstraint::PoolTargetPrice {
488+
target,
489+
tolerance,
490+
min_amount_in,
491+
max_amount_in,
492+
}
493+
}
494+
};
495+
#[allow(deprecated)]
496+
self.query_swap(swap::QuerySwapParams::new(
497+
&params.token_in.address,
498+
&params.token_out.address,
499+
constraint,
500+
))
501+
.map(|r| {
502+
PoolSwap::new(
503+
r.amount_in().clone(),
504+
r.amount_out().clone(),
505+
r.new_state().unwrap().to_protocol_sim(),
506+
r.price_points().as_ref().map(|points| {
507+
points
508+
.iter()
509+
.map(|p| {
510+
PricePoint::new(
511+
p.amount_in().clone(),
512+
p.amount_out().clone(),
513+
p.price(),
514+
)
515+
})
516+
.collect()
517+
}),
518+
)
519+
})
520+
}
521+
522+
fn clone_box(&self) -> Box<dyn ProtocolSim> {
523+
#[allow(deprecated)]
524+
self.to_protocol_sim()
525+
}
526+
527+
fn as_any(&self) -> &dyn Any {
528+
self
529+
}
530+
531+
fn as_any_mut(&mut self) -> &mut dyn Any {
532+
self
533+
}
534+
535+
fn eq(&self, other: &dyn ProtocolSim) -> bool {
536+
if let Some(other) = other.as_any().downcast_ref::<T>() {
537+
self == other
538+
} else {
539+
false
540+
}
541+
}
542+
543+
fn typetag_name(&self) -> &'static str {
544+
self.typetag_name()
545+
}
546+
547+
fn typetag_deserialize(&self) {
548+
self.typetag_deserialize()
549+
}
550+
}
551+
406552
#[cfg(test)]
407553
mod tests {
408554

@@ -466,7 +612,7 @@ mod tests {
466612
_delta: ProtocolStateDelta,
467613
_tokens: &HashMap<Bytes, Token>,
468614
_balances: &Balances,
469-
) -> Result<(), TransitionError<String>> {
615+
) -> Result<(), TransitionError> {
470616
todo!()
471617
}
472618
}

0 commit comments

Comments
 (0)