diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 864e6e2f..b93a7715 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -121,6 +121,53 @@ pub fn get_contract_address(sender: impl Into
, nonce: impl Into) /// [EIP1014](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md) /// /// keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12..] +/// +/// # Example +/// +/// Calculate the address of a UniswapV3 pool. +/// +/// ``` +/// use ethers_core::{ +/// abi, +/// abi::Token, +/// types::{Address, Bytes, U256}, +/// utils::{get_create2_address, keccak256}, +/// }; +/// +/// // We substitute some arbitrary short init code for brevity. The real +/// // pool init code can be found under "Contract Creation Code" on etherscan: +/// // https://etherscan.io/address/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/advanced#code +/// let UNISWAP_V3_POOL_INIT_CODE = Bytes::from(hex::decode("610160604052").unwrap()); +/// let factory: Address = "0x1F98431c8aD98523631AE4a59f267346ea31F984" +/// .parse() +/// .unwrap(); +/// let token0: Address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +/// .parse() +/// .unwrap(); +/// let token1: Address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" +/// .parse() +/// .unwrap(); +/// let fee = 500; +/// +/// let input = abi::encode(&vec![ +/// Token::Address(token0), +/// Token::Address(token1), +/// Token::Uint(U256::from(fee)), +/// ]); +/// +/// // keccak256(abi.encode(token0, token1, fee)) +/// let salt = keccak256(&input); +/// let pool_address = get_create2_address(factory, salt.to_vec(), UNISWAP_V3_POOL_INIT_CODE); +/// +/// // Actual USDC/ETH pool address (created with proper init code): +/// // 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 +/// assert_eq!( +/// pool_address, +/// "0x43953f76983c3ee678bb7a23b4e9eb813d6508b4" +/// .parse() +/// .unwrap() +/// ); +/// ``` pub fn get_create2_address( from: impl Into
, salt: impl Into, diff --git a/examples/permit_hash.rs b/examples/permit_hash.rs new file mode 100644 index 00000000..db6c4cc0 --- /dev/null +++ b/examples/permit_hash.rs @@ -0,0 +1,94 @@ +use ethers::{ + abi, + abi::Token, + prelude::U256, + types::Address, + utils, + utils::{keccak256, parse_ether}, +}; + +const UNISWAP_V2_USDC_ETH_PAIR: &'static str = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"; + +// Generate the EIP712 permit hash to sign for a Uniswap V2 pair. +// https://eips.ethereum.org/EIPS/eip-712 +// https://eips.ethereum.org/EIPS/eip-2612 +fn main() { + // Test data + let owner: Address = "0x617072Cb2a1897192A9d301AC53fC541d35c4d9D" + .parse() + .unwrap(); + let spender: Address = "0x2819c144D5946404C0516B6f817a960dB37D4929" + .parse() + .unwrap(); + let value = parse_ether(10).unwrap(); + let nonce = U256::from(1); + let deadline = U256::from(3133728498 as u32); + let verifying_contract: Address = UNISWAP_V2_USDC_ETH_PAIR.parse().unwrap(); + let name = "Uniswap V2"; + let version = "1"; + let chainid = 1; + + // Typehash for the permit() function + let permit_typehash = utils::keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)", + ); + // Typehash for the struct used to generate the domain separator + let domain_typehash = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", + ); + + // Corresponds to solidity's abi.encode() + let domain_separator_input = abi::encode(&vec![ + Token::Uint(U256::from(domain_typehash)), + Token::Uint(U256::from(keccak256(&name))), + Token::Uint(U256::from(keccak256(&version))), + Token::Uint(U256::from(chainid)), + Token::Address(verifying_contract), + ]); + + // Corresponds to the following solidity: + // DOMAIN_SEPARATOR = keccak256( + // abi.encode( + // keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), + // keccak256(bytes(name)), + // keccak256(bytes('1')), + // chainId, + // address(this) + // ) + // ); + let domain_separator = keccak256(&domain_separator_input); + + // Corresponds to solidity's abi.encode() + let struct_input = abi::encode(&vec![ + Token::Uint(U256::from(permit_typehash)), + Token::Address(owner), + Token::Address(spender), + Token::Uint(value), + Token::Uint(nonce), + Token::Uint(deadline), + ]); + let struct_hash = keccak256(&struct_input); + + // Corresponds to solidity's abi.encodePacked() + let digest_input = [ + &[0x19, 0x01], + domain_separator.as_ref(), + struct_hash.as_ref(), + ] + .concat(); + + // Matches the following solidity: + // bytes32 digest = keccak256( + // abi.encodePacked( + // '\x19\x01', + // DOMAIN_SEPARATOR, + // keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) + // ) + // ); + let permit_hash = keccak256(&digest_input); + + assert_eq!( + hex::encode(permit_hash), + "7b90248477de48c0b971e0af8951a55974733455191480e1e117c86cc2a6cd03" + ); +}