ERC20Permit.test.js 3.8 KB

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