From 674727c70efd73c032b92b37fb1b522331aead11 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sat, 12 Oct 2024 18:18:57 -0400 Subject: [PATCH] feat: refactor permit types and add ERC20 permit support (#91) * feat: refactor permit types and add ERC20 permit support Replaced custom `Permit` structs with standardized ones like `IERC721Permit::Permit` and `IERC20Permit::Permit`. This change enhances compatibility with token contracts and simplifies permit data handling. Also introduced a helper function for generating EIP-2612 domain and values for ERC20 permits. * Add `SolStruct` bound to `ERC20PermitData` struct Enforce `SolStruct` constraint on `ERC20PermitData` generic parameter. This ensures type safety by requiring the generic parameter to adhere to the `SolStruct` trait. --- src/abi.rs | 40 +++++++++++-- src/nonfungible_position_manager.rs | 20 ++----- src/self_permit.rs | 93 ++++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 20 deletions(-) diff --git a/src/abi.rs b/src/abi.rs index ab4c17e..c65ac48 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -79,6 +79,20 @@ sol! { function burn(uint256 tokenId) external payable; + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; + } + + interface IERC721Permit { + #[derive(Debug, PartialEq, Eq)] + struct Permit { + address spender; + uint256 tokenId; + uint256 nonce; + uint256 deadline; + } + function permit( address spender, uint256 tokenId, @@ -87,10 +101,6 @@ sol! { bytes32 r, bytes32 s ) external payable; - - function safeTransferFrom(address from, address to, uint256 tokenId) external; - - function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; } interface ISelfPermit { @@ -98,6 +108,28 @@ sol! { function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external payable; } + interface IERC20Permit { + #[derive(Debug, PartialEq, Eq)] + struct Permit { + address owner; + address spender; + uint256 value; + uint256 nonce; + uint256 deadline; + } + } + + interface IDaiPermit { + #[derive(Debug, PartialEq, Eq)] + struct Permit { + address holder; + address spender; + uint256 nonce; + uint256 expiry; + bool allowed; + } + } + interface IPeripheryPaymentsWithFee { function unwrapWETH9(uint256 amountMinimum, address recipient) external payable; diff --git a/src/nonfungible_position_manager.rs b/src/nonfungible_position_manager.rs index f7b5309..f0279e5 100644 --- a/src/nonfungible_position_manager.rs +++ b/src/nonfungible_position_manager.rs @@ -1,6 +1,6 @@ use crate::prelude::{Error, *}; use alloy_primitives::{Bytes, Signature, U256}; -use alloy_sol_types::{eip712_domain, sol, Eip712Domain, SolCall}; +use alloy_sol_types::{eip712_domain, Eip712Domain, SolCall}; use uniswap_sdk_core::prelude::*; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -66,20 +66,10 @@ pub struct CollectOptions { pub recipient: Address, } -sol! { - #[derive(Debug, PartialEq, Eq)] - struct Permit { - address spender; - uint256 tokenId; - uint256 nonce; - uint256 deadline; - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct NFTPermitData { pub domain: Eip712Domain, - pub values: Permit, + pub values: IERC721Permit::Permit, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -341,7 +331,7 @@ where if let Some(permit) = options.permit { calldatas.push( - INonfungiblePositionManager::permitCall { + IERC721Permit::permitCall { spender: permit.spender, tokenId: token_id, deadline: permit.deadline, @@ -451,7 +441,7 @@ pub fn safe_transfer_from_parameters(options: SafeTransferOptions) -> MethodPara /// use alloy_sol_types::SolStruct; /// use uniswap_v3_sdk::prelude::*; /// -/// let permit = Permit { +/// let permit = IERC721Permit::Permit { /// spender: address!("0000000000000000000000000000000000000002"), /// tokenId: uint!(1_U256), /// nonce: uint!(1_U256), @@ -477,7 +467,7 @@ pub fn safe_transfer_from_parameters(options: SafeTransferOptions) -> MethodPara #[inline] #[must_use] pub const fn get_permit_data( - permit: Permit, + permit: IERC721Permit::Permit, position_manager: Address, chain_id: u64, ) -> NFTPermitData { diff --git a/src/self_permit.rs b/src/self_permit.rs index 0753f6e..b79abb9 100644 --- a/src/self_permit.rs +++ b/src/self_permit.rs @@ -1,8 +1,99 @@ use super::abi::ISelfPermit; use alloy_primitives::{Bytes, Signature, U256}; -use alloy_sol_types::SolCall; +use alloy_sol_types::{eip712_domain, Eip712Domain, SolCall, SolStruct}; use uniswap_sdk_core::prelude::*; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ERC20PermitData { + pub domain: Eip712Domain, + pub values: P, +} + +/// Get the EIP-2612 domain and values to sign for an ERC20 permit. +/// +/// ## Arguments +/// +/// * `permit`: The ERC20 permit +/// * `name`: The name of the contract +/// * `version`: The version of the contract +/// * `token`: The address of the token +/// * `chain_id`: The chain ID +/// +/// ## Returns +/// +/// The EIP-2612 domain and values to sign +/// +/// ## Examples +/// +/// ``` +/// use alloy_primitives::{address, b256, uint, Signature, B256}; +/// use alloy_signer::SignerSync; +/// use alloy_signer_local::PrivateKeySigner; +/// use alloy_sol_types::SolStruct; +/// use uniswap_v3_sdk::prelude::*; +/// +/// let signer = PrivateKeySigner::random(); +/// let permit = IERC20Permit::Permit { +/// owner: signer.address(), +/// spender: address!("0000000000000000000000000000000000000002"), +/// value: uint!(1_U256), +/// nonce: uint!(1_U256), +/// deadline: uint!(123_U256), +/// }; +/// assert_eq!( +/// permit.eip712_type_hash(), +/// b256!("6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9") +/// ); +/// assert_eq!( +/// IDaiPermit::Permit { +/// holder: signer.address(), +/// spender: address!("0000000000000000000000000000000000000002"), +/// nonce: uint!(1_U256), +/// expiry: uint!(123_U256), +/// allowed: true, +/// } +/// .eip712_type_hash(), +/// b256!("ea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb") +/// ); +/// +/// let data = get_erc20_permit_data( +/// permit, +/// "ONE", +/// "1", +/// address!("0000000000000000000000000000000000000001"), +/// 1, +/// ); +/// +/// // Derive the EIP-712 signing hash. +/// let hash: B256 = data.values.eip712_signing_hash(&data.domain); +/// +/// let signature: Signature = signer.sign_hash_sync(&hash).unwrap(); +/// assert_eq!( +/// signature.recover_address_from_prehash(&hash).unwrap(), +/// signer.address() +/// ); +/// ``` +#[inline] +#[must_use] +pub fn get_erc20_permit_data( + permit: P, + name: &'static str, + version: &'static str, + token: Address, + chain_id: u64, +) -> ERC20PermitData

{ + let domain = eip712_domain! { + name: name, + version: version, + chain_id: chain_id, + verifying_contract: token, + }; + ERC20PermitData { + domain, + values: permit, + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct StandardPermitArguments { pub signature: Signature,