Skip to content

Commit

Permalink
Hacky native price workaround (#3278)
Browse files Browse the repository at this point in the history
# Description
Implementation of an option to set alternative token price
(approximated) for any other native token.
This feature is a workaround for obtaining native price for tokens which
are not supported in our currently available sources, and it is safe to
use such simple token substitution (like mapping csUSDL to DAI).

# Changes
- Added hash map of the tokens map in `CachingNativePriceEstimator`,
used `DashMap` for optimal performance (this will be read only hash map)
- Added code to parse token list from arguments line in
`price_estimation::Arguments`
- Added tests

## How to test
Newly added tests.

---------

Co-authored-by: MartinquaXD <[email protected]>
  • Loading branch information
mstrug and MartinquaXD authored Feb 13, 2025
1 parent ce951e3 commit 7333e77
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 1 deletion.
80 changes: 80 additions & 0 deletions crates/autopilot/src/solvable_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,7 @@ mod tests {
None,
Default::default(),
3,
Default::default(),
);
let metrics = Metrics::instance(observe::metrics::get_storage_registry()).unwrap();

Expand Down Expand Up @@ -996,6 +997,7 @@ mod tests {
None,
Default::default(),
1,
Default::default(),
);
let metrics = Metrics::instance(observe::metrics::get_storage_registry()).unwrap();

Expand Down Expand Up @@ -1037,6 +1039,84 @@ mod tests {
);
}

#[tokio::test]
async fn check_native_price_approximations() {
let token1 = H160([1; 20]);
let token2 = H160([2; 20]);
let token3 = H160([3; 20]);

let token_approx1 = H160([4; 20]);
let token_approx2 = H160([5; 20]);

let orders = vec![
OrderBuilder::default()
.with_sell_token(token1)
.with_buy_token(token2)
.with_buy_amount(1.into())
.with_sell_amount(1.into())
.build(),
OrderBuilder::default()
.with_sell_token(token1)
.with_buy_token(token2)
.with_buy_amount(1.into())
.with_sell_amount(1.into())
.build(),
OrderBuilder::default()
.with_sell_token(token1)
.with_buy_token(token3)
.with_buy_amount(1.into())
.with_sell_amount(1.into())
.build(),
];

let mut native_price_estimator = MockNativePriceEstimating::new();
native_price_estimator
.expect_estimate_native_price()
.times(1)
.withf(move |token| *token == token3)
.returning(|_| async { Ok(3.) }.boxed());
native_price_estimator
.expect_estimate_native_price()
.times(1)
.withf(move |token| *token == token_approx1)
.returning(|_| async { Ok(40.) }.boxed());
native_price_estimator
.expect_estimate_native_price()
.times(1)
.withf(move |token| *token == token_approx2)
.returning(|_| async { Ok(50.) }.boxed());

let native_price_estimator = CachingNativePriceEstimator::new(
Box::new(native_price_estimator),
Duration::from_secs(10),
Duration::MAX,
None,
Default::default(),
3,
// Set to use native price approximations for the following tokens
HashMap::from([(token1, token_approx1), (token2, token_approx2)]),
);
let metrics = Metrics::instance(observe::metrics::get_storage_registry()).unwrap();

let (filtered_orders, prices) = get_orders_with_native_prices(
orders.clone(),
&native_price_estimator,
metrics,
vec![],
Duration::from_secs(10),
)
.await;
assert_eq!(filtered_orders, orders);
assert_eq!(
prices,
btreemap! {
token1 => U256::from(40_000_000_000_000_000_000_u128),
token2 => U256::from(50_000_000_000_000_000_000_u128),
token3 => U256::from(3_000_000_000_000_000_000_u128),
}
);
}

#[tokio::test]
async fn filters_banned_users() {
let banned_users = hashset!(H160([0xba; 20]), H160([0xbb; 20]));
Expand Down
5 changes: 5 additions & 0 deletions crates/shared/src/price_estimation/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@ impl<'a> PriceEstimatorFactory<'a> {
Some(self.args.native_price_cache_max_update_size),
self.args.native_price_prefetch_time,
self.args.native_price_cache_concurrent_requests,
self.args
.native_price_approximation_tokens
.clone()
.into_iter()
.collect(),
));
Ok(native_estimator)
}
Expand Down
87 changes: 87 additions & 0 deletions crates/shared/src/price_estimation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use {
serde::{Deserialize, Serialize},
std::{
cmp::{Eq, PartialEq},
error::Error,
fmt::{self, Display, Formatter},
future::Future,
hash::Hash,
Expand Down Expand Up @@ -245,6 +246,38 @@ pub struct Arguments {

#[clap(flatten)]
pub balance_overrides: balance_overrides::Arguments,

/// List of mappings of native price tokens substitutions with approximated
/// value from other token:
/// "<token1>|<approx_token1>,<token2>|<approx_token2>"
/// - token1 is a token address for which we get the native token price
/// - approx_token1 is a token address used for the price approximation
///
/// It is very important that both tokens in the pair have the same number
/// of decimals.
#[clap(
long,
env,
value_delimiter = ',',
value_parser = parse_tuple::<H160, H160>
)]
pub native_price_approximation_tokens: Vec<(H160, H160)>,
}

/// Custom Clap parser for tuple pair
fn parse_tuple<T, U>(input: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
where
T: std::str::FromStr,
T::Err: Error + Send + Sync + 'static,
U: std::str::FromStr,
U::Err: Error + Send + Sync + 'static,
{
let pos = input.find('|').ok_or_else(|| {
format!(
"invalid pair values delimiter character, expected: 'value1|value2', got: '{input}'"
)
})?;
Ok((input[..pos].parse()?, input[pos + 1..].parse()?))
}

#[derive(clap::Parser)]
Expand Down Expand Up @@ -325,6 +358,7 @@ impl Display for Arguments {
quote_verification,
quote_timeout,
balance_overrides,
native_price_approximation_tokens,
} = self;

display_option(
Expand Down Expand Up @@ -403,6 +437,11 @@ impl Display for Arguments {
writeln!(f, "quote_verification: {:?}", quote_verification)?;
writeln!(f, "quote_timeout: {:?}", quote_timeout)?;
write!(f, "{}", balance_overrides)?;
writeln!(
f,
"native_price_approximation_tokens: {:?}",
native_price_approximation_tokens
)?;

Ok(())
}
Expand Down Expand Up @@ -670,4 +709,52 @@ mod tests {

assert!(result.is_err());
}

#[test]
fn test_parse_tuple() {
let result = parse_tuple::<H160, H160>(
"0102030405060708091011121314151617181920|a1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0",
)
.unwrap();
assert_eq!(
result.0,
H160::from_str("0102030405060708091011121314151617181920").unwrap()
);
assert_eq!(
result.1,
H160::from_str("a1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0").unwrap()
);

let result = parse_tuple::<H160, H160>(
"0102030405060708091011121314151617181920 a1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0",
);
assert!(result.is_err());

// test parsing with delimiter
#[derive(Parser)]
struct Cli {
#[arg(value_delimiter = ',', value_parser = parse_tuple::<H160, H160>)]
param: Vec<(H160, H160)>,
}
let cli = Cli::parse_from(vec![
"",
r#"0102030405060708091011121314151617181920|a1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0,
f102030405060708091011121314151617181920|f1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0"#,
]);

assert_eq!(
cli.param[0],
(
H160::from_str("0102030405060708091011121314151617181920").unwrap(),
H160::from_str("a1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0").unwrap()
)
);
assert_eq!(
cli.param[1],
(
H160::from_str("f102030405060708091011121314151617181920").unwrap(),
H160::from_str("f1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0").unwrap()
)
);
}
}
Loading

0 comments on commit 7333e77

Please sign in to comment.