Skip to content

Commit

Permalink
fix: correct permit encoding and enhance examples
Browse files Browse the repository at this point in the history
Adjusts permit encoding by fixing `v` value calculation. Adds new example demonstrating `burn_liquidity` with permit signing, improving usability and documentation of the SDK.
  • Loading branch information
shuhuiluo committed Dec 30, 2024
1 parent 366403a commit db32b82
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "uniswap-v3-sdk"
version = "3.1.2"
version = "3.1.3"
edition = "2021"
authors = ["Shuhui Luo <twitter.com/aureliano_law>"]
description = "Uniswap V3 SDK for Rust"
Expand Down
155 changes: 125 additions & 30 deletions examples/nonfungible_position_manager.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use alloy::{
eips::BlockId,
node_bindings::WEI_IN_ETHER,
providers::{ext::AnvilApi, Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::{
k256::ecdsa::SigningKey,
local::{LocalSigner, PrivateKeySigner},
SignerSync,
},
transports::{http::reqwest::Url, Transport},
};
use alloy_primitives::{address, Address, U256};
Expand Down Expand Up @@ -31,7 +37,13 @@ async fn main() {
.fork(rpc_url)
.fork_block_number(block_id.as_u64().unwrap())
});
let account = provider.get_accounts().await.unwrap()[0];
provider.anvil_auto_impersonate_account(true).await.unwrap();
let account: LocalSigner<SigningKey> = PrivateKeySigner::random();
provider
.anvil_set_balance(account.address(), WEI_IN_ETHER)
.await
.unwrap();
let sender = provider.get_accounts().await.unwrap()[0];

let pool = Pool::from_pool_key(
1,
Expand All @@ -51,45 +63,64 @@ async fn main() {
nearest_usable_tick(pool.tick_current + pool.tick_spacing(), pool.tick_spacing()),
);

// Set the state of the account to allow the position to be minted
let MintAmounts { amount0, amount1 } = position.mint_amounts().unwrap();
let mut overrides = get_erc20_state_overrides(
position.pool.token0.address(),
account,
npm,
amount0,
&provider,
)
.await
.unwrap();
overrides.extend(
get_erc20_state_overrides(
position.pool.token1.address(),
account,
{
// Set the state of the account to allow the position to be minted
let MintAmounts { amount0, amount1 } = position.mint_amounts().unwrap();
let mut overrides = get_erc20_state_overrides(
position.pool.token0.address(),
account.address(),
npm,
amount1,
amount0,
&provider,
)
.await
.unwrap(),
);
for (token, account_override) in overrides {
for (slot, value) in account_override.state_diff.unwrap() {
provider
.anvil_set_storage_at(token, U256::from_be_bytes(slot.0), value)
.await
.unwrap();
.unwrap();
overrides.extend(
get_erc20_state_overrides(
position.pool.token1.address(),
account.address(),
npm,
amount1,
&provider,
)
.await
.unwrap(),
);
for (token, account_override) in overrides {
for (slot, value) in account_override.state_diff.unwrap() {
provider
.anvil_set_storage_at(token, U256::from_be_bytes(slot.0), value)
.await
.unwrap();
}
}
}

let minted_position = mint_liquidity(&mut position, account, &provider).await;
let token_id = mint_liquidity(&mut position, account.address(), &provider).await;

let minted_position = Position::from_token_id(1, npm, token_id, provider.clone(), None)
.await
.unwrap();

assert_eq!(minted_position.liquidity, position.liquidity);
assert_eq!(minted_position.tick_lower, position.tick_lower);
assert_eq!(minted_position.tick_upper, position.tick_upper);

burn_liquidity(token_id, &position, &account, sender, &provider).await;

assert_eq!(
IERC721Enumerable::new(npm, provider)
.balanceOf(account.address())
.call()
.await
.unwrap()
._0,
U256::ZERO
);
}

async fn mint_liquidity<T, P>(position: &mut Position, from: Address, provider: &P) -> Position
/// Mint a position
async fn mint_liquidity<T, P>(position: &mut Position, from: Address, provider: &P) -> U256
where
T: Transport + Clone,
P: Provider<T>,
Expand Down Expand Up @@ -120,13 +151,77 @@ where
.await
.unwrap();

let token_id = IERC721Enumerable::new(npm, provider)
IERC721Enumerable::new(npm, provider)
.tokenOfOwnerByIndex(from, U256::ZERO)
.call()
.await
.unwrap()
._0;
Position::from_token_id(1, npm, token_id, provider, None)
._0
}

/// Burn a position with a permit
async fn burn_liquidity<T, P>(
token_id: U256,
position: &Position,
owner: &LocalSigner<SigningKey>,
sender: Address,
provider: &P,
) where
T: Transport + Clone,
P: Provider<T>,
{
let npm = *NONFUNGIBLE_POSITION_MANAGER_ADDRESSES.get(&1).unwrap();

// Sign the permit
let hash = get_permit_data(
NFTPermitValues {
spender: sender,
tokenId: token_id,
nonce: U256::ZERO,
deadline: U256::MAX,
},
npm,
1,
)
.eip712_signing_hash();
let signature = owner.sign_hash_sync(&hash).unwrap();

let options = RemoveLiquidityOptions {
token_id,
liquidity_percentage: Percent::new(1, 1),
slippage_tolerance: Percent::default(),
deadline: U256::MAX,
burn_token: true,
permit: Some(NFTPermitOptions {
signature,
deadline: U256::MAX,
spender: sender,
}),
collect_options: CollectOptions {
token_id,
expected_currency_owed0: CurrencyAmount::from_raw_amount(
position.pool.token0.clone(),
0,
)
.unwrap(),
expected_currency_owed1: CurrencyAmount::from_raw_amount(
position.pool.token1.clone(),
0,
)
.unwrap(),
recipient: owner.address(),
},
};
let params = remove_call_parameters(&position, options).unwrap();
let tx = TransactionRequest::default()
.from(sender)
.to(npm)
.input(params.calldata.into());
provider
.send_transaction(tx)
.await
.unwrap()
.watch()
.await
.unwrap();
}
16 changes: 12 additions & 4 deletions src/nonfungible_position_manager.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::prelude::{Error, *};
use alloy_primitives::{Bytes, PrimitiveSignature, U256};
use alloy_sol_types::{eip712_domain, Eip712Domain, SolCall};
use alloy_primitives::{Bytes, PrimitiveSignature, B256, U256};
use alloy_sol_types::{eip712_domain, Eip712Domain, SolCall, SolStruct};
use uniswap_sdk_core::prelude::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -74,6 +74,14 @@ pub struct NFTPermitData {
pub values: NFTPermitValues,
}

impl NFTPermitData {
#[inline]
#[must_use]
pub fn eip712_signing_hash(&self) -> B256 {
self.values.eip712_signing_hash(&self.domain)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NFTPermitOptions {
pub signature: PrimitiveSignature,
Expand Down Expand Up @@ -337,7 +345,7 @@ where
spender: permit.spender,
tokenId: token_id,
deadline: permit.deadline,
v: permit.signature.v() as u8,
v: permit.signature.v() as u8 + 27,
r: permit.signature.r().into(),
s: permit.signature.s().into(),
}
Expand Down Expand Up @@ -456,7 +464,7 @@ pub fn safe_transfer_from_parameters(options: SafeTransferOptions) -> MethodPara
/// let data: NFTPermitData = get_permit_data(permit, position_manager, 1);
///
/// // Derive the EIP-712 signing hash.
/// let hash: B256 = data.values.eip712_signing_hash(&data.domain);
/// let hash: B256 = data.eip712_signing_hash();
///
/// let signer = PrivateKeySigner::random();
/// let signature: PrimitiveSignature = signer.sign_hash_sync(&hash).unwrap();
Expand Down

0 comments on commit db32b82

Please sign in to comment.