tycho-simulation 0.255.1

Provides tools for interacting with protocol states, calculating spot prices, and quoting token swaps.
Documentation
use std::{borrow::Cow, collections::HashMap};

use ekubo_sdk::quoting::types::{Tick, TimeRateDelta};
use itertools::Itertools;
use tycho_common::Bytes;

pub fn ticks_from_attributes<'a, T: IntoIterator<Item = (impl AsRef<str>, Cow<'a, Bytes>)>>(
    attributes: T,
) -> Result<Vec<Tick>, String> {
    attributes
        .into_iter()
        .filter_map(|(key, value)| {
            let key = key.as_ref();
            // Support both new "tick/" and legacy "ticks/" prefix
            (key.starts_with("tick/") || key.starts_with("ticks/")).then(|| {
                key.split('/')
                    .nth(1)
                    .ok_or_else(|| "expected key name to contain tick index".to_string())?
                    .parse::<i32>()
                    .map_or_else(
                        |err| Err(format!("tick index can't be parsed as i32: {err}")),
                        |index| Ok(Tick { index, liquidity_delta: value.into_owned().into() }),
                    )
            })
        })
        .try_collect()
}

pub fn rate_deltas_from_attributes<
    'a,
    T: IntoIterator<Item = (impl AsRef<str>, Cow<'a, Bytes>)>,
>(
    attributes: T,
    last_execution_time: u64,
) -> Result<impl Iterator<Item = TimeRateDelta>, String> {
    Ok(attributes
        .into_iter()
        .filter_map(|(key, value)| {
            let key = key.as_ref();

            // Support both new "rate_delta/" and legacy "orders/" prefix
            if !key.starts_with("rate_delta/") && !key.starts_with("orders/") {
                return None;
            }

            let Some((token_str, time_str)) = key.split('/').skip(1).collect_tuple() else {
                return Some(Err(format!(
                    "failed to parse rate delta attribute segments of \"{key}\""
                )));
            };

            let time: u64 = match time_str.parse() {
                Ok(time) => time,
                Err(err) => {
                    return Some(Err(format!("rate_delta time can't be parsed as u64: {err}")))
                }
            };

            if time <= last_execution_time {
                return None;
            }

            let is_token1 = match token_str {
                "token0" => false,
                "token1" => true,
                token => {
                    return Some(Err(format!(
                        r#"expected "token0" or "token1" but received "{token}""#
                    )))
                }
            };

            let delta = value.into_owned().into();

            Some(Ok((time, is_token1, delta)))
        })
        .try_collect::<_, Vec<_>, _>()?
        .into_iter()
        .fold(HashMap::new(), |mut map, (time, is_token1, value)| {
            let delta = map
                .entry(time)
                .or_insert(TimeRateDelta { time, rate_delta0: 0, rate_delta1: 0 });

            *(if is_token1 { &mut delta.rate_delta1 } else { &mut delta.rate_delta0 }) = value;

            map
        })
        .into_values())
}