ERC20Permit.test.js 3.9 KB

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