Skip to content

Commit

Permalink
chore: update dependencies and refactor for no-std (#132)
Browse files Browse the repository at this point in the history
Streamline dependencies and improve error handling by replacing generic with specific error types. Restructure feature flags (`parse_price`, `extensions`) to improve modularity and `no-std` compatibility. Bump SDK version to `3.6.0` and consolidate documentation for better clarity.
  • Loading branch information
shuhuiluo authored Feb 6, 2025
1 parent 426ccc3 commit 23218ec
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 99 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ jobs:
${{ runner.os }}-cargo-registry-
- name: Build
run: cargo build
- name: Build with std feature
run: cargo build --features std
- name: Build with extensions
run: cargo build --features extensions
- name: Run tests for core library
Expand Down
48 changes: 34 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "uniswap-v3-sdk"
version = "3.5.1"
version = "3.6.0"
edition = "2021"
authors = ["Shuhui Luo <twitter.com/aureliano_law>"]
description = "Uniswap V3 SDK for Rust"
Expand All @@ -16,26 +16,46 @@ all-features = true

[dependencies]
alloy = { version = "0.11", optional = true, default-features = false, features = ["contract"] }
alloy-primitives = "0.8"
alloy-sol-types = "0.8"
alloy-primitives = { version = "0.8", default-features = false }
alloy-sol-types = { version = "0.8", default-features = false }
anyhow = { version = "1.0", optional = true }
base64 = { version = "0.22", optional = true }
bigdecimal = "0.4.7"
derive_more = { version = "1.0.0", features = ["deref", "from"] }
num-bigint = "0.4"
num-integer = "0.1"
num-traits = "0.2"
once_cell = "1.20"
base64 = { version = "0.22", optional = true, default-features = false }
derive_more = { version = "2", default-features = false, features = ["deref", "from"] }
num-bigint = { version = "0.4", default-features = false }
num-integer = { version = "0.1", default-features = false }
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
once_cell = { version = "1.20", default-features = false, features = ["critical-section"] }
regex = { version = "1.11", optional = true }
serde_json = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true, default-features = false }
thiserror = { version = "2", default-features = false }
uniswap-lens = { version = "0.11.1", optional = true }
uniswap-sdk-core = "3.4.0"
uniswap-sdk-core = "3.5.0"

[features]
default = []
extensions = ["alloy", "anyhow", "base64", "regex", "serde_json", "uniswap-lens"]
std = ["alloy?/std", "thiserror/std", "uniswap-sdk-core/std", "uniswap-lens?/std"]
extensions = [
"alloy",
"base64",
"serde_json",
"uniswap-lens"
]
parse_price = [
"anyhow",
"extensions",
"regex",
"std"
]
std = [
"alloy-sol-types/std",
"alloy?/std",
"base64?/std",
"derive_more/std",
"once_cell/std",
"serde_json?/std",
"thiserror/std",
"uniswap-lens?/std",
"uniswap-sdk-core/std"
]

[dev-dependencies]
alloy = { version = "0.11", default-features = false, features = ["provider-anvil-node", "signer-local"] }
Expand Down
34 changes: 15 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,21 @@ By default, this library does not depend on the standard library (`std`). Howeve

The code below shows an example of creating a pool with a tick map data provider and simulating a swap with it.

```rust
#[tokio::main]
async fn main() {
// Create a pool with a tick map data provider
let pool = Pool::<EphemeralTickMapDataProvider>::from_pool_key_with_tick_data_provider(
1,
FACTORY_ADDRESS,
wbtc.address(),
weth.address(),
FeeAmount::LOW,
provider.clone(),
block_id,
)
.await
.unwrap();
// Get the output amount from the pool
let amount_in = CurrencyAmount::from_raw_amount(wbtc.clone(), 100000000).unwrap();
let amount_out = pool.get_output_amount(&amount_in, None).unwrap();
}
```rust,ignore
let pool = Pool::<EphemeralTickMapDataProvider>::from_pool_key_with_tick_data_provider(
1,
FACTORY_ADDRESS,
wbtc.address(),
weth.address(),
FeeAmount::LOW,
provider.clone(),
block_id,
)
.await
.unwrap();
// Get the output amount from the pool
let amount_in = CurrencyAmount::from_raw_amount(wbtc.clone(), 100000000).unwrap();
let amount_out = pool.get_output_amount(&amount_in, None).unwrap();
```

For runnable examples, see the [examples](./examples) directory.
Expand Down
2 changes: 0 additions & 2 deletions src/entities/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,6 @@ impl<TP: Clone + TickDataProvider> Pool<TP> {
self.sqrt_ratio_x96 = sqrt_price_x96;
self.tick_current = TP::Index::from_i24(sqrt_price_x96.get_tick_at_sqrt_ratio()?);
self.liquidity = liquidity;
// TODO: update tick data provider
CurrencyAmount::from_raw_amount(output_token.clone(), -output_amount.to_big_int())
.map_err(Error::Core)
}
Expand Down Expand Up @@ -440,7 +439,6 @@ impl<TP: Clone + TickDataProvider> Pool<TP> {
self.sqrt_ratio_x96 = sqrt_price_x96;
self.tick_current = TP::Index::from_i24(sqrt_price_x96.get_tick_at_sqrt_ratio()?);
self.liquidity = liquidity;
// TODO: update tick data provider
CurrencyAmount::from_raw_amount(input_token.clone(), input_amount.to_big_int())
.map_err(Error::Core)
}
Expand Down
1 change: 1 addition & 0 deletions src/entities/tick_list_data_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl<I: TickIndex> TickListDataProvider<I> {
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use once_cell::sync::Lazy;

static PROVIDER: Lazy<TickListDataProvider> =
Expand Down
6 changes: 3 additions & 3 deletions src/entities/trade.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::prelude::{Error, *};
use alloc::vec;
use alloy_primitives::map::rustc_hash::FxHashSet;
use alloy_primitives::map::HashSet;
use core::cmp::Ordering;
use uniswap_sdk_core::prelude::{sorted_insert, *};
use uniswap_sdk_core::prelude::*;

/// Trades comparator, an extension of the input output comparator that also considers other
/// dimensions of the trade in ranking them
Expand Down Expand Up @@ -188,7 +188,7 @@ where
.iter()
.flat_map(|swap| swap.route.pools.iter())
.map(|pool| pool.address(None, None));
let pool_address_set = FxHashSet::from_iter(pool_addresses);
let pool_address_set: HashSet<Address> = HashSet::from_iter(pool_addresses);
assert_eq!(num_pools, pool_address_set.len(), "POOLS_DUPLICATED");
Ok(Self {
swaps,
Expand Down
23 changes: 13 additions & 10 deletions src/extensions/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ use alloy::{
eips::{BlockId, BlockNumberOrTag},
network::Network,
providers::Provider,
transports::{TransportError, TransportErrorKind},
};
use alloy_primitives::{Address, ChainId, U256};
use anyhow::Result;
use base64::{engine::general_purpose, Engine};
use num_bigint::ToBigInt;
use uniswap_lens::{
bindings::{
Expand Down Expand Up @@ -249,7 +248,6 @@ where
///
/// ## Arguments
///
/// * `chain_id`: The chain id
/// * `nonfungible_position_manager`: The nonfungible position manager address
/// * `token_id`: The token id
/// * `provider`: The alloy provider
Expand All @@ -260,12 +258,11 @@ where
/// A tuple of the collectable token amounts.
#[inline]
pub async fn get_collectable_token_amounts<N, P>(
_chain_id: ChainId,
nonfungible_position_manager: Address,
token_id: U256,
provider: P,
block_id: Option<BlockId>,
) -> Result<(U256, U256)>
) -> Result<(U256, U256), Error>
where
N: Network,
P: Provider<N>,
Expand Down Expand Up @@ -363,7 +360,7 @@ pub async fn get_token_svg<N, P>(
token_id: U256,
provider: P,
block_id: Option<BlockId>,
) -> Result<String>
) -> Result<String, Error>
where
N: Network,
P: Provider<N>,
Expand All @@ -374,9 +371,15 @@ where
.call()
.await?
._0;
let json_uri =
general_purpose::URL_SAFE.decode(uri.replace("data:application/json;base64,", ""))?;
let image = serde_json::from_slice::<serde_json::Value>(&json_uri)?
let json_uri = base64::Engine::decode(
&base64::engine::general_purpose::URL_SAFE,
uri.replace("data:application/json;base64,", ""),
)
.map_err(|e| {
TransportError::Transport(TransportErrorKind::Custom(alloc::boxed::Box::new(e)))
})?;
let image = serde_json::from_slice::<serde_json::Value>(&json_uri)
.map_err(TransportError::SerError)?
.get("image")
.unwrap()
.to_string();
Expand Down Expand Up @@ -553,7 +556,7 @@ mod tests {
#[tokio::test]
async fn test_get_collectable_token_amounts() {
let (tokens_owed_0, tokens_owed_1) =
get_collectable_token_amounts(1, NPM, uint!(4_U256), PROVIDER.clone(), BLOCK_ID)
get_collectable_token_amounts(NPM, uint!(4_U256), PROVIDER.clone(), BLOCK_ID)
.await
.unwrap();
assert_eq!(tokens_owed_0, uint!(3498422_U256));
Expand Down
21 changes: 9 additions & 12 deletions src/extensions/price_tick_conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@
//! [`Price`] prices. Ported from [uniswap-v3-automation-sdk](https://github.com/Aperture-Finance/uniswap-v3-automation-sdk/blob/8bc54456753f454848d25029631f4e64ff573e12/price.ts).
use crate::prelude::{Error, *};
use alloc::format;
use alloy_primitives::{aliases::I24, U160};
use anyhow::{bail, Result};
use core::str::FromStr;
use num_bigint::ToBigInt;
use num_traits::{Signed, Zero};
use once_cell::sync::Lazy;
use regex::Regex;
use uniswap_sdk_core::prelude::*;

pub static MIN_PRICE: Lazy<Fraction> =
Expand Down Expand Up @@ -47,30 +43,32 @@ pub static MAX_PRICE: Lazy<Fraction> = Lazy::new(|| {
/// )
/// .unwrap();
/// ```
#[cfg(feature = "parse_price")]
#[inline]
pub fn parse_price<TBase, TQuote>(
base_token: TBase,
quote_token: TQuote,
price: &str,
) -> Result<Price<TBase, TQuote>>
) -> anyhow::Result<Price<TBase, TQuote>>
where
TBase: BaseCurrency,
TQuote: BaseCurrency,
{
// Check whether `price` is a valid string of decimal number.
// This regex matches any number of digits optionally followed by '.' which is then followed by
// at least one digit.
let re = Regex::new(r"^\d*\.?\d+$").unwrap();
let re = regex::Regex::new(r"^\d*\.?\d+$")?;
if !re.is_match(price) {
bail!("Invalid price string");
anyhow::bail!("Invalid price string");
}

let (whole, fraction) = match price.split_once('.') {
Some((whole, fraction)) => (whole, fraction),
None => (price, ""),
};
let decimals = fraction.len();
let without_decimals = BigInt::from_str(&format!("{}{}", whole, fraction))?;
let without_decimals =
<BigInt as core::str::FromStr>::from_str(&alloc::format!("{}{}", whole, fraction))?;
let numerator = without_decimals * BigInt::from(10).pow(quote_token.decimals() as u32);
let denominator = BigInt::from(10).pow(decimals as u32 + base_token.decimals() as u32);
Ok(Price::new(base_token, quote_token, denominator, numerator))
Expand Down Expand Up @@ -201,7 +199,6 @@ pub fn price_to_closest_usable_tick(
///
/// ```
/// use alloy_primitives::aliases::I24;
/// use bigdecimal::BigDecimal;
/// use num_traits::{FromPrimitive, Pow, ToPrimitive};
/// use uniswap_v3_sdk::prelude::*;
///
Expand Down Expand Up @@ -242,10 +239,9 @@ where
/// ## Examples
///
/// ```
/// use bigdecimal::BigDecimal;
/// use uniswap_v3_sdk::prelude::*;
///
/// let price: BigDecimal = tick_to_big_price(MAX_TICK).unwrap();
/// let price = tick_to_big_price(MAX_TICK).unwrap();
/// assert_eq!(price_to_sqrt_ratio_x96(&price), MAX_SQRT_RATIO);
/// ```
#[inline]
Expand Down Expand Up @@ -378,7 +374,7 @@ pub fn token0_price_to_ratio(
///
/// ```
/// use alloy_primitives::aliases::I24;
/// use bigdecimal::BigDecimal;
/// use uniswap_sdk_core::prelude::BigDecimal;
/// use uniswap_v3_sdk::prelude::*;
///
/// let tick_current = I24::from_limbs([200000]);
Expand Down Expand Up @@ -431,6 +427,7 @@ pub fn tick_range_from_width_and_ratio(
#[cfg(test)]
mod tests {
use super::*;
use core::str::FromStr;

#[test]
fn test_token0_ratio_to_price_conversion() {
Expand Down
34 changes: 2 additions & 32 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,5 @@
//! # uniswap-v3-sdk
//!
//! A Rust SDK for building applications on top of Uniswap V3.
//! Migration from the TypeScript [Uniswap/v3-sdk](https://github.com/Uniswap/v3-sdk).
//!
//! ## Features
//!
//! - Opinionated Rust implementation of the Uniswap V3 SDK with a focus on readability and
//! performance.
//! - Usage of [alloy-rs](https://github.com/alloy-rs) types.
//! - Reimplementation of the math libraries in [Uniswap V3 Math In Rust](https://github.com/0xKitsune/uniswap-v3-math)
//! based on optimizations presented in [Uni V3 Lib](https://github.com/Aperture-Finance/uni-v3-lib).
//! - Extensive unit tests and benchmarks.
//! - An [`extensions`](./src/extensions) feature for additional functionalities related to Uniswap
//! V3, including:
//!
//! - [`pool`](./src/extensions/pool.rs) module for creating a `Pool` struct from a pool key and
//! fetching the liquidity map within a tick range for the specified pool, using RPC client.
//! - [`position`](./src/extensions/position.rs) module for creating a `Position` struct from a
//! token id and fetching the state and pool for all positions of the specified owner, using
//! RPC client, etc.
//! - [`price_tick_conversions`](./src/extensions/price_tick_conversions.rs) module for
//! converting between prices and ticks.
//! - [`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`
#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(any(feature = "std", all(test, feature = "extensions"))), no_std)]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
Expand Down
1 change: 1 addition & 0 deletions src/multicall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl_multicall!(Vec<Bytes>, Vec<Vec<u8>>);
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloy_primitives::hex;

mod encode {
Expand Down
1 change: 1 addition & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::prelude::{
tick_math::{MAX_TICK, MIN_TICK},
*,
};
pub(crate) use alloc::vec;
use alloy_primitives::U160;
use once_cell::sync::Lazy;
use uniswap_sdk_core::{prelude::*, token};
Expand Down
1 change: 1 addition & 0 deletions src/utils/sqrt_price_math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ pub fn get_amount_1_delta_signed<const BITS: usize, const LIMBS: usize>(
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
use alloy_primitives::{keccak256, U160};
use alloy_sol_types::SolValue;
use uniswap_v3_math::{error::UniswapV3MathError, sqrt_price_math};
Expand Down
Loading

0 comments on commit 23218ec

Please sign in to comment.