Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sync with TypeScript SDK #86

Merged
merged 4 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
shuhuiluo marked this conversation as resolved.
Show resolved Hide resolved
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>,
shuhuiluo marked this conversation as resolved.
Show resolved Hide resolved
) -> 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(
shuhuiluo marked this conversation as resolved.
Show resolved Hide resolved
factory,
init_code_hash_manual_override.unwrap_or(b256!(
"010013f177ea1fcbc4520f9a3ca7cd2d1d77959e05aa66484027cb38e712aeed"
)),
shuhuiluo marked this conversation as resolved.
Show resolved Hide resolved
salt,
None,
),
shuhuiluo marked this conversation as resolved.
Show resolved Hide resolved
_ => 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