Add usage examples for hashing utilities (#436)
* core: add doctest for utils::get_create2_address() * examples: add permit hash encoding example
This commit is contained in:
parent
3754ceca48
commit
46faf7614b
|
@ -121,6 +121,53 @@ pub fn get_contract_address(sender: impl Into<Address>, nonce: impl Into<U256>)
|
|||
/// [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<Address>,
|
||||
salt: impl Into<Bytes>,
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue