-
I'm interested in using Ethers to test implementations of EIP712. This includes a number of other ERCs, perhaps most notably (for now) token permits, which allow for gasless approvals by using signed data instead. 712 is a bit of a complicated ERC - there's a whole data object that has to be put together and signed off-chain, and then generally passed in to another function on-chain. I wanted to start this discussion to work out how to use Ethers in order to help work with EIP712 in scripts and tests. I don't want to make any promises I can't keep, but I'd be interested in writing an article explaining how to do this if we can get a successful implementation working here just to help out anyone else who comes along trying to figure this out. I'll offer what I've figured out so far: Domain Separators"Domain Separators" are a concept 712 uses to make sure signed messages are used on the right chain for the right function. They hash the name, version, contract address, and chain id of the function being used. The ERC specifies a function how to make the domain separator, and specifies a function returning it. The following is a working Hardhat test using an ERC20 contract as an example: it("domain separator returns properly", async () => {
const chainId = _the `chainId` of the chain the contract is deployed on_
const _name = _the `name()` of the ERC20 contract_;
const version = _the version as specified in ERC712 (string, generally of a number)_;
const verifyingContract = _address of the contract the function is in_;
expect(await your_contract.DOMAIN_SEPARATOR())
.to.equal(await ethers.utils._TypedDataEncoder.hashDomain({
name: _name,
version,
chainId,
verifyingContract
}));
}); Accepting a SignatureI haven't figured out how to pass a signature into a function like First, you'll have to be able to put together an EIP712-formatted JSON to be signed (it's specified in the EIP, linked above). here's my attempt for ERC20 Permits: const buildData = async (chainId: any, verifyingContract: string) => {
const EIP712Domain = constants.eip712domain;
const Permit = constants.permitDomain;
const _name = _the `name()` of the ERC20 contract_;
const version = _the version as specified in ERC712 (string, generally of a number)_;
const owner = _address of the owner of the tokens_;
const spender = _address being approved to spend the tokens_;
const value = _uint256 of the amount of tokens being approved_;
const nonce = await Contract.nonces(owner);
const deadline = ethers.constants.MaxUint256; // this is for testing, can use a tighter deadline
return {
primaryType: 'Permit',
types: { EIP712Domain, Permit },
domain: { name: _name , version, chainId, verifyingContract },
message: { owner, spender, value, nonce, deadline }
}
} Then the JSON made by this needs to be signed: const data = await buildData(chainId, Token.address);
const digest = await artist._signTypedData(data.domain, data.types, data.message); Next (lol wondering if I've already gone wrong ages ago) should be splitting the signed message into the three components of a signed message (v, r, and s): const { v,r,s } = ethers.utils.splitSignature(digest); Then we should be ready to pass it into const receipt = await your_contract.connect(buyer).permit(
owner,
spender,
value,
ethers.constants.MaxUint256,
v,
r,
s
); Unfortunately, my implementation of a test using this is not passing, so like I mentioned, I'm probably either doing something wrong or missing a step. If this is working, the owner's 712 nonce should be incremented, and the Some notesThere are a bunch of ERC20 Permit implementations out there, I've been trying to look through them to figure this out, but haven't found one that uses plain old vanilla Ethers, or if they do, they have it obfuscated underneath a layer of helper functions. I'll throw in a couple here for reference:
(Both of these use |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
Uniswap v3 actually has a full Ethers implementation of their test fro EIP-2612 here: https://github.com/Uniswap/v3-periphery/blob/main/test/shared/permit.ts You can confirm there that calling |
Beta Was this translation helpful? Give feedback.
-
this info is great! So was it possible to implement it using plain old vanilla ethers? 🥲 |
Beta Was this translation helpful? Give feedback.
-
there's a library for that |
Beta Was this translation helpful? Give feedback.
Uniswap v3 actually has a full Ethers implementation of their test fro EIP-2612 here: https://github.com/Uniswap/v3-periphery/blob/main/test/shared/permit.ts
You can confirm there that calling
splitSignature
on_signTypedData
is in fact correct. As for why I couldn't get my code to work, jury's out. I suspect my original implementation had an error somewhere in the details. (I was working on something a bit different than EIP-2612 permits for ERC20 - I was trying to work out a permit scheme for ERC721, which is now working though.)