Skip to content

Commit

Permalink
Merge pull request #86 from shuhuiluo/sync
Browse files Browse the repository at this point in the history
feat: sync with TypeScript SDK
  • Loading branch information
shuhuiluo authored Oct 7, 2024
2 parents f88f0aa + 7667316 commit be1c802
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 153 deletions.
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "uniswap-v3-sdk"
version = "1.1.0"
version = "1.2.0"
edition = "2021"
authors = ["Shuhui Luo <twitter.com/aureliano_law>"]
description = "Uniswap V3 SDK for Rust"
Expand All @@ -14,7 +14,7 @@ exclude = [".github", ".gitignore", "rustfmt.toml"]
all-features = true

[dependencies]
alloy = { version = "0.3", optional = true, features = ["contract"] }
alloy = { version = "0.4", optional = true, features = ["contract"] }
alloy-primitives = "0.8"
alloy-sol-types = "0.8"
anyhow = { version = "1.0", optional = true }
Expand All @@ -23,13 +23,13 @@ bigdecimal = "0.4.5"
num-bigint = "0.4"
num-integer = "0.1"
num-traits = "0.2"
once_cell = "1.19"
regex = { version = "1.10", optional = true }
once_cell = "1.20"
regex = { version = "1.11", optional = true }
rustc-hash = "2.0"
serde_json = { version = "1.0", optional = true }
thiserror = { version = "1.0", optional = true }
uniswap-lens = { version = "0.3", optional = true }
uniswap-sdk-core = "2.3.0"
uniswap-lens = { version = "0.4", optional = true }
uniswap-sdk-core = "2.4.0"

[features]
default = []
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,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 = "1.1.0", features = ["extensions", "std"] }
uniswap-v3-sdk = { version = "1.2.0", features = ["extensions", "std"] }
```

### Usage
Expand Down
8 changes: 4 additions & 4 deletions benches/swap_math.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(dead_code)]

use alloy_primitives::{keccak256, I256, U160, U256};
use alloy_primitives::{aliases::U24, keccak256, I256, U160, U256};
use alloy_sol_types::SolValue;
use criterion::{criterion_group, criterion_main, Criterion};
use uniswap_v3_math::swap_math;
Expand All @@ -15,15 +15,15 @@ fn pseudo_random_128(seed: u64) -> u128 {
u128::from_be_bytes(s.to_be_bytes::<32>()[..16].try_into().unwrap())
}

fn generate_inputs() -> Vec<(U160, U160, u128, I256, u32)> {
fn generate_inputs() -> Vec<(U160, U160, u128, I256, U24)> {
(0u64..100)
.map(|i| {
(
U160::saturating_from(pseudo_random(i)),
U160::saturating_from(pseudo_random(i.pow(2))),
pseudo_random_128(i.pow(3)),
I256::from_raw(pseudo_random(i.pow(4))),
i as u32,
U24::from(i),
)
})
.collect()
Expand Down Expand Up @@ -81,7 +81,7 @@ fn compute_swap_step_benchmark_ref(c: &mut Criterion) {
*sqrt_ratio_target_x96,
*liquidity,
*amount_remaining,
*fee_pips,
fee_pips.into_limbs()[0] as u32,
);
}
})
Expand Down
16 changes: 14 additions & 2 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(non_camel_case_types)]

use alloy_primitives::{
address,
aliases::{I24, U24},
Expand All @@ -6,15 +8,16 @@ use alloy_primitives::{

pub const FACTORY_ADDRESS: Address = address!("1F98431c8aD98523631AE4a59f267346ea31F984");

pub const ADDRESS_ZERO: Address = Address::ZERO;

pub const POOL_INIT_CODE_HASH: B256 =
b256!("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54");

/// The default factory enabled fee amounts, denominated in hundredths of bips.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FeeAmount {
LOWEST = 100,
LOW_200 = 200,
LOW_300 = 300,
LOW_400 = 400,
LOW = 500,
MEDIUM = 3000,
HIGH = 10000,
Expand All @@ -27,6 +30,9 @@ impl FeeAmount {
pub const fn tick_spacing(&self) -> I24 {
match self {
Self::LOWEST => I24::ONE,
Self::LOW_200 => I24::from_limbs([4]),
Self::LOW_300 => I24::from_limbs([6]),
Self::LOW_400 => I24::from_limbs([8]),
Self::LOW => I24::from_limbs([10]),
Self::MEDIUM => I24::from_limbs([60]),
Self::HIGH => I24::from_limbs([200]),
Expand All @@ -39,6 +45,9 @@ impl From<u32> for FeeAmount {
fn from(fee: u32) -> Self {
match fee {
100 => Self::LOWEST,
200 => Self::LOW_200,
300 => Self::LOW_300,
400 => Self::LOW_400,
500 => Self::LOW,
3000 => Self::MEDIUM,
10000 => Self::HIGH,
Expand All @@ -52,6 +61,9 @@ impl From<i32> for FeeAmount {
fn from(tick_spacing: i32) -> Self {
match tick_spacing {
1 => Self::LOWEST,
4 => Self::LOW_200,
6 => Self::LOW_300,
8 => Self::LOW_400,
10 => Self::LOW,
60 => Self::MEDIUM,
200 => Self::HIGH,
Expand Down
141 changes: 13 additions & 128 deletions src/entities/pool.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::prelude::{Error, *};
use alloy_primitives::{ChainId, B256, I256, U160, U256};
use alloy_primitives::{ChainId, B256, I256, U160};
use core::fmt;
use once_cell::sync::Lazy;
use uniswap_sdk_core::prelude::*;
Expand Down Expand Up @@ -53,25 +53,6 @@ where
}
}

struct SwapState<I = i32> {
amount_specified_remaining: I256,
amount_calculated: I256,
sqrt_price_x96: U160,
tick: I,
liquidity: u128,
}

#[derive(Clone, Copy, Default)]
struct StepComputations<I = i32> {
sqrt_price_start_x96: U160,
tick_next: I,
initialized: bool,
sqrt_price_next_x96: U160,
amount_in: U256,
amount_out: U256,
fee_amount: U256,
}

impl Pool {
/// Construct a pool
///
Expand Down Expand Up @@ -145,6 +126,7 @@ impl Pool {
token_b.address(),
fee,
init_code_hash_manual_override,
Some(token_a.chain_id()),
)
}
}
Expand Down Expand Up @@ -275,114 +257,17 @@ impl<TP: TickDataProvider> Pool<TP> {
amount_specified: I256,
sqrt_price_limit_x96: Option<U160>,
) -> Result<SwapState<TP::Index>, Error> {
let sqrt_price_limit_x96 = sqrt_price_limit_x96.unwrap_or_else(|| {
if zero_for_one {
MIN_SQRT_RATIO + ONE
} else {
MAX_SQRT_RATIO - ONE
}
});

if zero_for_one {
assert!(sqrt_price_limit_x96 > MIN_SQRT_RATIO, "RATIO_MIN");
assert!(sqrt_price_limit_x96 < self.sqrt_ratio_x96, "RATIO_CURRENT");
} else {
assert!(sqrt_price_limit_x96 < MAX_SQRT_RATIO, "RATIO_MAX");
assert!(sqrt_price_limit_x96 > self.sqrt_ratio_x96, "RATIO_CURRENT");
}

let exact_input = amount_specified >= I256::ZERO;

// keep track of swap state
let mut state = SwapState {
amount_specified_remaining: amount_specified,
amount_calculated: I256::ZERO,
sqrt_price_x96: self.sqrt_ratio_x96,
tick: self.tick_current,
liquidity: self.liquidity,
};

// start swap while loop
while !state.amount_specified_remaining.is_zero()
&& state.sqrt_price_x96 != sqrt_price_limit_x96
{
let mut step = StepComputations {
sqrt_price_start_x96: state.sqrt_price_x96,
..Default::default()
};

// because each iteration of the while loop rounds, we can't optimize this code
// (relative to the smart contract) by simply traversing to the next available tick, we
// instead need to exactly replicate
(step.tick_next, step.initialized) = self
.tick_data_provider
.next_initialized_tick_within_one_word(
state.tick,
zero_for_one,
self.tick_spacing(),
)?;

step.tick_next = TP::Index::from_i24(step.tick_next.to_i24().clamp(MIN_TICK, MAX_TICK));
step.sqrt_price_next_x96 = get_sqrt_ratio_at_tick(step.tick_next.to_i24())?;

(
state.sqrt_price_x96,
step.amount_in,
step.amount_out,
step.fee_amount,
) = compute_swap_step(
state.sqrt_price_x96,
if zero_for_one {
step.sqrt_price_next_x96.max(sqrt_price_limit_x96)
} else {
step.sqrt_price_next_x96.min(sqrt_price_limit_x96)
},
state.liquidity,
state.amount_specified_remaining,
self.fee as u32,
)?;

if exact_input {
state.amount_specified_remaining = I256::from_raw(
state.amount_specified_remaining.into_raw() - step.amount_in - step.fee_amount,
);
state.amount_calculated =
I256::from_raw(state.amount_calculated.into_raw() - step.amount_out);
} else {
state.amount_specified_remaining =
I256::from_raw(state.amount_specified_remaining.into_raw() + step.amount_out);
state.amount_calculated = I256::from_raw(
state.amount_calculated.into_raw() + step.amount_in + step.fee_amount,
);
}

if state.sqrt_price_x96 == step.sqrt_price_next_x96 {
// if the tick is initialized, run the tick transition
if step.initialized {
let mut liquidity_net = self
.tick_data_provider
.get_tick(step.tick_next)?
.liquidity_net;
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
if zero_for_one {
liquidity_net = -liquidity_net;
}
state.liquidity = add_delta(state.liquidity, liquidity_net)?;
}
state.tick = if zero_for_one {
step.tick_next - TP::Index::ONE
} else {
step.tick_next
};
} else if state.sqrt_price_x96 != step.sqrt_price_start_x96 {
// recompute unless we're on a lower tick boundary (i.e. already transitioned
// ticks), and haven't moved
state.tick = TP::Index::from_i24(state.sqrt_price_x96.get_tick_at_sqrt_ratio()?);
}
}

Ok(state)
v3_swap(
self.fee.into(),
self.sqrt_ratio_x96,
self.tick_current,
self.liquidity,
self.tick_spacing(),
&self.tick_data_provider,
zero_for_one,
amount_specified,
sqrt_price_limit_x96,
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/extensions/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ where
P: Provider<T>,
{
IUniswapV3PoolInstance::new(
compute_pool_address(factory, token_a, token_b, fee, None),
compute_pool_address(factory, token_a, token_b, fee, None, None),
provider,
)
}
Expand Down
32 changes: 26 additions & 6 deletions src/utils/compute_pool_address.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::constants::{FeeAmount, POOL_INIT_CODE_HASH};
use alloy_primitives::{keccak256, Address, B256};
use alloy_primitives::{b256, keccak256, Address, B256};
use alloy_sol_types::SolValue;
use uniswap_sdk_core::prelude::{
compute_zksync_create2_address::compute_zksync_create2_address, ChainId,
};

/// Computes a pool address
///
Expand Down Expand Up @@ -32,6 +35,7 @@ use alloy_sol_types::SolValue;
/// DAI_ADDRESS,
/// FeeAmount::LOW,
/// None,
/// None,
/// );
/// assert_eq!(result, address!("90B1b09A9715CaDbFD9331b3A7652B24BfBEfD32"));
/// assert_eq!(
Expand All @@ -42,6 +46,7 @@ use alloy_sol_types::SolValue;
/// USDC_ADDRESS,
/// FeeAmount::LOW,
/// None,
/// None
/// )
/// );
/// ```
Expand All @@ -53,16 +58,31 @@ pub fn compute_pool_address(
token_b: Address,
fee: FeeAmount,
init_code_hash_manual_override: Option<B256>,
chain_id: Option<alloy_primitives::ChainId>,
) -> Address {
assert_ne!(token_a, token_b, "ADDRESSES");
let (token_0, token_1) = if token_a < token_b {
(token_a, token_b)
} else {
(token_b, token_a)
};
let pool_key = (token_0, token_1, fee as i32);
factory.create2(
keccak256(pool_key.abi_encode()),
init_code_hash_manual_override.unwrap_or(POOL_INIT_CODE_HASH),
)
let salt = keccak256((token_0, token_1, fee as i32).abi_encode());
const ZKSYNC_CHAIN_ID: u64 = ChainId::ZKSYNC as u64;

// ZKSync uses a different create2 address computation
// Most likely all ZKEVM chains will use the different computation from standard create2
match chain_id {
Some(ZKSYNC_CHAIN_ID) => compute_zksync_create2_address(
factory,
init_code_hash_manual_override.unwrap_or(b256!(
"010013f177ea1fcbc4520f9a3ca7cd2d1d77959e05aa66484027cb38e712aeed"
)),
salt,
None,
),
_ => factory.create2(
salt,
init_code_hash_manual_override.unwrap_or(POOL_INIT_CODE_HASH),
),
}
}
2 changes: 1 addition & 1 deletion src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub use max_liquidity_for_amounts::*;
pub use nearest_usable_tick::nearest_usable_tick;
pub use price_tick_conversions::*;
pub use sqrt_price_math::*;
pub use swap_math::compute_swap_step;
pub use swap_math::*;
pub use tick_list::TickList;
pub use tick_math::*;
pub use types::*;
Expand Down
Loading

0 comments on commit be1c802

Please sign in to comment.