Skip to content

Commit

Permalink
refactor: TickDataProvider (#80)
Browse files Browse the repository at this point in the history
* refactor: remove `Clone` as super trait of `TickDataProvider`

Removed the `Clone` super trait from `TickDataProvider`. Updated relevant functions and structs to explicitly require `Clone` where necessary. This change improves the code by isolating the `Clone` requirement to specific contexts, thereby reducing unnecessary constraints.

* refactor: impl `TickDataProvider` with `Deref`

Removed redundant trait implementations for `TickDataProvider` by using `Deref` trait. This improves code readability and reduces duplication across various data provider implementations. Also refactored `TickList` implementations using a macro for conciseness.
  • Loading branch information
shuhuiluo committed Sep 14, 2024
1 parent 20cd90e commit 5b0ea90
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 236 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ It is feature-complete with unit tests matching the TypeScript SDK.
- [`ephemeral_tick_data_provider`](./src/extensions/ephemeral_tick_data_provider.rs) module for fetching ticks using
an [ephemeral contract](https://github.com/Aperture-Finance/Aperture-Lens/blob/904101e4daed59e02fd4b758b98b0749e70b583b/contracts/EphemeralGetPopulatedTicksInRange.sol)
in a single `eth_call`
- [`ephemeral_tick_map_data_provider`](./src/extensions/ephemeral_tick_map_data_provider.rs) fetches ticks in a
single `eth_call` and creates a `TickMap`
- [`tick_map`](./src/extensions/tick_map.rs) provides a way to access tick data directly from a hashmap, supposedly
more efficient than `TickList`

<details>
<summary>Expand to see the benchmarks</summary>
Expand All @@ -44,7 +48,7 @@ It is feature-complete with unit tests matching the TypeScript SDK.
Add the following to your `Cargo.toml` file:

```toml
uniswap-v3-sdk = { version = "0.37.0", features = ["extensions", "std"] }
uniswap-v3-sdk = { version = "0.40.0", features = ["extensions", "std"] }
```

### Usage
Expand Down
218 changes: 110 additions & 108 deletions src/entities/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,114 +269,6 @@ impl<TP: TickDataProvider> Pool<TP> {
})
}

/// Given an input amount of a token, return the computed output amount, and a pool with state
/// updated after the trade
///
/// ## Arguments
///
/// * `input_amount`: The input amount for which to quote the output amount
/// * `sqrt_price_limit_x96`: The Q64.96 sqrt price limit
///
/// returns: The output amount and the pool with updated state
#[inline]
pub fn get_output_amount(
&self,
input_amount: &CurrencyAmount<Token>,
sqrt_price_limit_x96: Option<U160>,
) -> Result<(CurrencyAmount<Token>, Self), Error> {
assert!(self.involves_token(&input_amount.currency), "TOKEN");

let zero_for_one = input_amount.currency.equals(&self.token0);

let SwapState {
amount_specified_remaining,
amount_calculated: output_amount,
sqrt_price_x96,
liquidity,
..
} = self._swap(
zero_for_one,
I256::from_big_int(input_amount.quotient()),
sqrt_price_limit_x96,
)?;

if !amount_specified_remaining.is_zero() && sqrt_price_limit_x96.is_none() {
return Err(Error::InsufficientLiquidity);
}

let output_token = if zero_for_one {
self.token1.clone()
} else {
self.token0.clone()
};
Ok((
CurrencyAmount::from_raw_amount(output_token, -output_amount.to_big_int())?,
Pool::new_with_tick_data_provider(
self.token0.clone(),
self.token1.clone(),
self.fee,
sqrt_price_x96,
liquidity,
self.tick_data_provider.clone(),
)?,
))
}

/// Given a desired output amount of a token, return the computed input amount and a pool with
/// state updated after the trade
///
/// ## Arguments
///
/// * `output_amount`: the output amount for which to quote the input amount
/// * `sqrt_price_limit_x96`: The Q64.96 sqrt price limit. If zero for one, the price cannot be
/// less than this value after the swap. If one for zero, the price cannot be greater than
/// this value after the swap
///
/// returns: The input amount and the pool with updated state
#[inline]
pub fn get_input_amount(
&self,
output_amount: &CurrencyAmount<Token>,
sqrt_price_limit_x96: Option<U160>,
) -> Result<(CurrencyAmount<Token>, Self), Error> {
assert!(self.involves_token(&output_amount.currency), "TOKEN");

let zero_for_one = output_amount.currency.equals(&self.token1);

let SwapState {
amount_specified_remaining,
amount_calculated: input_amount,
sqrt_price_x96,
liquidity,
..
} = self._swap(
zero_for_one,
I256::from_big_int(-output_amount.quotient()),
sqrt_price_limit_x96,
)?;

if !amount_specified_remaining.is_zero() && sqrt_price_limit_x96.is_none() {
return Err(Error::InsufficientLiquidity);
}

let input_token = if zero_for_one {
self.token0.clone()
} else {
self.token1.clone()
};
Ok((
CurrencyAmount::from_raw_amount(input_token, input_amount.to_big_int())?,
Pool::new_with_tick_data_provider(
self.token0.clone(),
self.token1.clone(),
self.fee,
sqrt_price_x96,
liquidity,
self.tick_data_provider.clone(),
)?,
))
}

fn _swap(
&self,
zero_for_one: bool,
Expand Down Expand Up @@ -494,6 +386,116 @@ impl<TP: TickDataProvider> Pool<TP> {
}
}

impl<TP: Clone + TickDataProvider> Pool<TP> {
/// Given an input amount of a token, return the computed output amount, and a pool with state
/// updated after the trade
///
/// ## Arguments
///
/// * `input_amount`: The input amount for which to quote the output amount
/// * `sqrt_price_limit_x96`: The Q64.96 sqrt price limit
///
/// returns: The output amount and the pool with updated state
#[inline]
pub fn get_output_amount(
&self,
input_amount: &CurrencyAmount<Token>,
sqrt_price_limit_x96: Option<U160>,
) -> Result<(CurrencyAmount<Token>, Self), Error> {
assert!(self.involves_token(&input_amount.currency), "TOKEN");

let zero_for_one = input_amount.currency.equals(&self.token0);

let SwapState {
amount_specified_remaining,
amount_calculated: output_amount,
sqrt_price_x96,
liquidity,
..
} = self._swap(
zero_for_one,
I256::from_big_int(input_amount.quotient()),
sqrt_price_limit_x96,
)?;

if !amount_specified_remaining.is_zero() && sqrt_price_limit_x96.is_none() {
return Err(Error::InsufficientLiquidity);
}

let output_token = if zero_for_one {
self.token1.clone()
} else {
self.token0.clone()
};
Ok((
CurrencyAmount::from_raw_amount(output_token, -output_amount.to_big_int())?,
Pool::new_with_tick_data_provider(
self.token0.clone(),
self.token1.clone(),
self.fee,
sqrt_price_x96,
liquidity,
self.tick_data_provider.clone(),
)?,
))
}

/// Given a desired output amount of a token, return the computed input amount and a pool with
/// state updated after the trade
///
/// ## Arguments
///
/// * `output_amount`: the output amount for which to quote the input amount
/// * `sqrt_price_limit_x96`: The Q64.96 sqrt price limit. If zero for one, the price cannot be
/// less than this value after the swap. If one for zero, the price cannot be greater than
/// this value after the swap
///
/// returns: The input amount and the pool with updated state
#[inline]
pub fn get_input_amount(
&self,
output_amount: &CurrencyAmount<Token>,
sqrt_price_limit_x96: Option<U160>,
) -> Result<(CurrencyAmount<Token>, Self), Error> {
assert!(self.involves_token(&output_amount.currency), "TOKEN");

let zero_for_one = output_amount.currency.equals(&self.token1);

let SwapState {
amount_specified_remaining,
amount_calculated: input_amount,
sqrt_price_x96,
liquidity,
..
} = self._swap(
zero_for_one,
I256::from_big_int(-output_amount.quotient()),
sqrt_price_limit_x96,
)?;

if !amount_specified_remaining.is_zero() && sqrt_price_limit_x96.is_none() {
return Err(Error::InsufficientLiquidity);
}

let input_token = if zero_for_one {
self.token0.clone()
} else {
self.token1.clone()
};
Ok((
CurrencyAmount::from_raw_amount(input_token, input_amount.to_big_int())?,
Pool::new_with_tick_data_provider(
self.token0.clone(),
self.token1.clone(),
self.fee,
sqrt_price_x96,
liquidity,
self.tick_data_provider.clone(),
)?,
))
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
28 changes: 27 additions & 1 deletion src/entities/tick_data_provider.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::prelude::*;
use core::ops::Deref;

/// Provides information about ticks
pub trait TickDataProvider: Clone {
pub trait TickDataProvider {
type Index: TickIndex;

/// Return information corresponding to a specific tick
Expand Down Expand Up @@ -30,6 +31,31 @@ pub trait TickDataProvider: Clone {
) -> Result<(Self::Index, bool), Error>;
}

/// Implements the [`TickDataProvider`] trait for any type that dereferences to a
/// [`TickDataProvider`]
impl<TP> TickDataProvider for TP
where
TP: Deref<Target: TickDataProvider>,
{
type Index = <<TP as Deref>::Target as TickDataProvider>::Index;

#[inline]
fn get_tick(&self, tick: Self::Index) -> Result<&Tick<Self::Index>, Error> {
self.deref().get_tick(tick)
}

#[inline]
fn next_initialized_tick_within_one_word(
&self,
tick: Self::Index,
lte: bool,
tick_spacing: Self::Index,
) -> Result<(Self::Index, bool), Error> {
self.deref()
.next_initialized_tick_within_one_word(tick, lte, tick_spacing)
}
}

/// This tick data provider does not know how to fetch any tick data. It throws whenever it is
/// required. Useful if you do not need to load tick data for your use case.
#[derive(Clone, Copy, Debug)]
Expand Down
21 changes: 5 additions & 16 deletions src/entities/tick_list_data_provider.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::prelude::*;
use core::ops::Deref;

/// A data provider for ticks that is backed by an in-memory array of ticks.
#[derive(Clone, Debug, Default, PartialEq)]
Expand All @@ -12,24 +13,12 @@ impl<I: TickIndex> TickListDataProvider<I> {
}
}

impl<I: TickIndex> TickDataProvider for TickListDataProvider<I> {
type Index = I;
impl<I> Deref for TickListDataProvider<I> {
type Target = Vec<Tick<I>>;

#[inline]
fn get_tick(&self, tick: I) -> Result<&Tick<I>, Error> {
Ok(self.0.get_tick(tick))
}

#[inline]
fn next_initialized_tick_within_one_word(
&self,
tick: I,
lte: bool,
tick_spacing: I,
) -> Result<(I, bool), Error> {
Ok(self
.0
.next_initialized_tick_within_one_word(tick, lte, tick_spacing))
fn deref(&self) -> &Self::Target {
&self.0
}
}

Expand Down
12 changes: 8 additions & 4 deletions src/entities/trade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,14 @@ where
self.minimum_amount_out_cached(slippage_tolerance, None)?,
))
}
}

impl<TInput, TOutput, TP> Trade<TInput, TOutput, TP>
where
TInput: Currency,
TOutput: Currency,
TP: Clone + TickDataProvider,
{
/// Constructs an exact in trade with the given amount in and route
///
/// ## Arguments
Expand Down Expand Up @@ -786,10 +793,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::{
tests::*,
utils::tick_math::{MAX_TICK, MIN_TICK},
};
use crate::tests::*;
use once_cell::sync::Lazy;

fn v2_style_pool(
Expand Down
21 changes: 5 additions & 16 deletions src/extensions/ephemeral_tick_data_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::prelude::*;
use alloy::{eips::BlockId, providers::Provider, transports::Transport};
use alloy_primitives::{aliases::I24, Address};
use core::ops::Deref;
use uniswap_lens::pool_lens;

/// A data provider that fetches ticks using an ephemeral contract in a single `eth_call`.
Expand Down Expand Up @@ -58,24 +59,12 @@ impl<I: TickIndex> EphemeralTickDataProvider<I> {
}
}

impl<I: TickIndex> TickDataProvider for EphemeralTickDataProvider<I> {
type Index = I;
impl<I> Deref for EphemeralTickDataProvider<I> {
type Target = Vec<Tick<I>>;

#[inline]
fn get_tick(&self, tick: I) -> Result<&Tick<I>, Error> {
Ok(self.ticks.get_tick(tick))
}

#[inline]
fn next_initialized_tick_within_one_word(
&self,
tick: I,
lte: bool,
tick_spacing: I,
) -> Result<(I, bool), Error> {
Ok(self
.ticks
.next_initialized_tick_within_one_word(tick, lte, tick_spacing))
fn deref(&self) -> &Self::Target {
&self.ticks
}
}

Expand Down
Loading

0 comments on commit 5b0ea90

Please sign in to comment.