/
ERC20Permit.test.js
109 lines (87 loc) · 3.79 KB
/
ERC20Permit.test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { getDomain, domainSeparator, Permit } = require('../../../helpers/eip712');
const time = require('../../../helpers/time');
const name = 'My Token';
const symbol = 'MTKN';
const initialSupply = 100n;
async function fixture() {
const [holder, spender, owner, other] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC20Permit', [name, symbol, name]);
await token.$_mint(holder, initialSupply);
return {
holder,
spender,
owner,
other,
token,
};
}
describe('ERC20Permit', function () {
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
it('initial nonce is 0', async function () {
expect(await this.token.nonces(this.holder)).to.equal(0n);
});
it('domain separator', async function () {
expect(await this.token.DOMAIN_SEPARATOR()).to.equal(await getDomain(this.token).then(domainSeparator));
});
describe('permit', function () {
const value = 42n;
const nonce = 0n;
const maxDeadline = ethers.MaxUint256;
beforeEach(function () {
this.buildData = (contract, deadline = maxDeadline) =>
getDomain(contract).then(domain => ({
domain,
types: { Permit },
message: {
owner: this.owner.address,
spender: this.spender.address,
value,
nonce,
deadline,
},
}));
});
it('accepts owner signature', async function () {
const { v, r, s } = await this.buildData(this.token)
.then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message))
.then(ethers.Signature.from);
await this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s);
expect(await this.token.nonces(this.owner)).to.equal(1n);
expect(await this.token.allowance(this.owner, this.spender)).to.equal(value);
});
it('rejects reused signature', async function () {
const { v, r, s, serialized } = await this.buildData(this.token)
.then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message))
.then(ethers.Signature.from);
await this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s);
const recovered = await this.buildData(this.token).then(({ domain, types, message }) =>
ethers.verifyTypedData(domain, types, { ...message, nonce: nonce + 1n, deadline: maxDeadline }, serialized),
);
await expect(this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s))
.to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner')
.withArgs(recovered, this.owner);
});
it('rejects other signature', async function () {
const { v, r, s } = await this.buildData(this.token)
.then(({ domain, types, message }) => this.other.signTypedData(domain, types, message))
.then(ethers.Signature.from);
await expect(this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s))
.to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner')
.withArgs(this.other, this.owner);
});
it('rejects expired permit', async function () {
const deadline = (await time.clock.timestamp()) - time.duration.weeks(1);
const { v, r, s } = await this.buildData(this.token, deadline)
.then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message))
.then(ethers.Signature.from);
await expect(this.token.permit(this.owner, this.spender, value, deadline, v, r, s))
.to.be.revertedWithCustomError(this.token, 'ERC2612ExpiredSignature')
.withArgs(deadline);
});
});
});