ERC20Permit.test.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /* eslint-disable */
  2. const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
  3. const { expect } = require('chai');
  4. const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants;
  5. const { fromRpcSig } = require('ethereumjs-util');
  6. const ethSigUtil = require('eth-sig-util');
  7. const Wallet = require('ethereumjs-wallet').default;
  8. const ERC20PermitMock = artifacts.require('ERC20PermitMock');
  9. const { EIP712Domain, domainSeparator } = require('../helpers/eip712');
  10. const Permit = [
  11. { name: 'owner', type: 'address' },
  12. { name: 'spender', type: 'address' },
  13. { name: 'value', type: 'uint256' },
  14. { name: 'nonce', type: 'uint256' },
  15. { name: 'deadline', type: 'uint256' },
  16. ];
  17. contract('ERC20Permit', function (accounts) {
  18. const [ initialHolder, spender, recipient, other ] = accounts;
  19. const name = 'My Token';
  20. const symbol = 'MTKN';
  21. const version = '1';
  22. const initialSupply = new BN(100);
  23. beforeEach(async function () {
  24. this.token = await ERC20PermitMock.new(name, symbol, initialHolder, initialSupply);
  25. // We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
  26. // from within the EVM as from the JSON RPC interface.
  27. // See https://github.com/trufflesuite/ganache-core/issues/515
  28. this.chainId = await this.token.getChainId();
  29. });
  30. it('initial nonce is 0', async function () {
  31. expect(await this.token.nonces(initialHolder)).to.be.bignumber.equal('0');
  32. });
  33. it('domain separator', async function () {
  34. expect(
  35. await this.token.DOMAIN_SEPARATOR(),
  36. ).to.equal(
  37. await domainSeparator(name, version, this.chainId, this.token.address),
  38. );
  39. });
  40. describe('permit', function () {
  41. const wallet = Wallet.generate();
  42. const owner = wallet.getAddressString();
  43. const value = new BN(42);
  44. const nonce = 0;
  45. const maxDeadline = MAX_UINT256;
  46. const buildData = (chainId, verifyingContract, deadline = maxDeadline) => ({
  47. primaryType: 'Permit',
  48. types: { EIP712Domain, Permit },
  49. domain: { name, version, chainId, verifyingContract },
  50. message: { owner, spender, value, nonce, deadline },
  51. });
  52. it('accepts owner signature', async function () {
  53. const data = buildData(this.chainId, this.token.address);
  54. const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
  55. const { v, r, s } = fromRpcSig(signature);
  56. const receipt = await this.token.permit(owner, spender, value, maxDeadline, v, r, s);
  57. expect(await this.token.nonces(owner)).to.be.bignumber.equal('1');
  58. expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
  59. });
  60. it('rejects reused signature', async function () {
  61. const data = buildData(this.chainId, this.token.address);
  62. const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
  63. const { v, r, s } = fromRpcSig(signature);
  64. await this.token.permit(owner, spender, value, maxDeadline, v, r, s);
  65. await expectRevert(
  66. this.token.permit(owner, spender, value, maxDeadline, v, r, s),
  67. 'ERC20Permit: invalid signature',
  68. );
  69. });
  70. it('rejects other signature', async function () {
  71. const otherWallet = Wallet.generate();
  72. const data = buildData(this.chainId, this.token.address);
  73. const signature = ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data });
  74. const { v, r, s } = fromRpcSig(signature);
  75. await expectRevert(
  76. this.token.permit(owner, spender, value, maxDeadline, v, r, s),
  77. 'ERC20Permit: invalid signature',
  78. );
  79. });
  80. it('rejects expired permit', async function () {
  81. const deadline = (await time.latest()) - time.duration.weeks(1);
  82. const data = buildData(this.chainId, this.token.address, deadline);
  83. const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
  84. const { v, r, s } = fromRpcSig(signature);
  85. await expectRevert(
  86. this.token.permit(owner, spender, value, deadline, v, r, s),
  87. 'ERC20Permit: expired deadline',
  88. );
  89. });
  90. });
  91. });