95 lines
3.0 KiB
Rust
95 lines
3.0 KiB
Rust
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"
|
|
);
|
|
}
|