Bläddra i källkod

Migrate ERC20 extensions tests to ethers v6 (#4773)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: ernestognw <ernestognw@gmail.com>
Renan Souza 1 år sedan
förälder
incheckning
88512b23d2

+ 1 - 5
test/governance/Governor.test.js

@@ -4,11 +4,7 @@ const ethSigUtil = require('eth-sig-util');
 const Wallet = require('ethereumjs-wallet').default;
 
 const Enums = require('../helpers/enums');
-const {
-  getDomain,
-  domainType,
-  types: { Ballot },
-} = require('../helpers/eip712');
+const { getDomain, domainType, Ballot } = require('../helpers/eip712');
 const { GovernorHelper, proposalStatesToBitMap } = require('../helpers/governance');
 const { clockFromReceipt } = require('../helpers/time');
 const { expectRevertCustomError } = require('../helpers/customError');

+ 1 - 5
test/governance/extensions/GovernorWithParams.test.js

@@ -4,11 +4,7 @@ const ethSigUtil = require('eth-sig-util');
 const Wallet = require('ethereumjs-wallet').default;
 
 const Enums = require('../../helpers/enums');
-const {
-  getDomain,
-  domainType,
-  types: { ExtendedBallot },
-} = require('../../helpers/eip712');
+const { getDomain, domainType, ExtendedBallot } = require('../../helpers/eip712');
 const { GovernorHelper } = require('../../helpers/governance');
 const { expectRevertCustomError } = require('../../helpers/customError');
 

+ 1 - 5
test/governance/utils/Votes.behavior.js

@@ -7,11 +7,7 @@ const ethSigUtil = require('eth-sig-util');
 const Wallet = require('ethereumjs-wallet').default;
 
 const { shouldBehaveLikeERC6372 } = require('./ERC6372.behavior');
-const {
-  getDomain,
-  domainType,
-  types: { Delegation },
-} = require('../../helpers/eip712');
+const { getDomain, domainType, Delegation } = require('../../helpers/eip712');
 const { clockFromReceipt } = require('../../helpers/time');
 const { expectRevertCustomError } = require('../../helpers/customError');
 

+ 52 - 44
test/helpers/eip712-types.js

@@ -1,44 +1,52 @@
-module.exports = {
-  EIP712Domain: [
-    { name: 'name', type: 'string' },
-    { name: 'version', type: 'string' },
-    { name: 'chainId', type: 'uint256' },
-    { name: 'verifyingContract', type: 'address' },
-    { name: 'salt', type: 'bytes32' },
-  ],
-  Permit: [
-    { name: 'owner', type: 'address' },
-    { name: 'spender', type: 'address' },
-    { name: 'value', type: 'uint256' },
-    { name: 'nonce', type: 'uint256' },
-    { name: 'deadline', type: 'uint256' },
-  ],
-  Ballot: [
-    { name: 'proposalId', type: 'uint256' },
-    { name: 'support', type: 'uint8' },
-    { name: 'voter', type: 'address' },
-    { name: 'nonce', type: 'uint256' },
-  ],
-  ExtendedBallot: [
-    { name: 'proposalId', type: 'uint256' },
-    { name: 'support', type: 'uint8' },
-    { name: 'voter', type: 'address' },
-    { name: 'nonce', type: 'uint256' },
-    { name: 'reason', type: 'string' },
-    { name: 'params', type: 'bytes' },
-  ],
-  Delegation: [
-    { name: 'delegatee', type: 'address' },
-    { name: 'nonce', type: 'uint256' },
-    { name: 'expiry', type: 'uint256' },
-  ],
-  ForwardRequest: [
-    { name: 'from', type: 'address' },
-    { name: 'to', type: 'address' },
-    { name: 'value', type: 'uint256' },
-    { name: 'gas', type: 'uint256' },
-    { name: 'nonce', type: 'uint256' },
-    { name: 'deadline', type: 'uint48' },
-    { name: 'data', type: 'bytes' },
-  ],
-};
+const { mapValues } = require('./iterate');
+
+const formatType = schema => Object.entries(schema).map(([name, type]) => ({ name, type }));
+
+module.exports = mapValues(
+  {
+    EIP712Domain: {
+      name: 'string',
+      version: 'string',
+      chainId: 'uint256',
+      verifyingContract: 'address',
+      salt: 'bytes32',
+    },
+    Permit: {
+      owner: 'address',
+      spender: 'address',
+      value: 'uint256',
+      nonce: 'uint256',
+      deadline: 'uint256',
+    },
+    Ballot: {
+      proposalId: 'uint256',
+      support: 'uint8',
+      voter: 'address',
+      nonce: 'uint256',
+    },
+    ExtendedBallot: {
+      proposalId: 'uint256',
+      support: 'uint8',
+      voter: 'address',
+      nonce: 'uint256',
+      reason: 'string',
+      params: 'bytes',
+    },
+    Delegation: {
+      delegatee: 'address',
+      nonce: 'uint256',
+      expiry: 'uint256',
+    },
+    ForwardRequest: {
+      from: 'address',
+      to: 'address',
+      value: 'uint256',
+      gas: 'uint256',
+      nonce: 'uint256',
+      deadline: 'uint48',
+      data: 'bytes',
+    },
+  },
+  formatType,
+);
+module.exports.formatType = formatType;

+ 1 - 1
test/helpers/eip712.js

@@ -38,9 +38,9 @@ function hashTypedData(domain, structHash) {
 }
 
 module.exports = {
-  types,
   getDomain,
   domainType,
   domainSeparator: ethers.TypedDataEncoder.hashDomain,
   hashTypedData,
+  ...types,
 };

+ 4 - 5
test/helpers/time.js

@@ -1,6 +1,5 @@
 const { time, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers');
-
-const mapObject = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
+const { mapValues } = require('./iterate');
 
 module.exports = {
   clock: {
@@ -22,8 +21,8 @@ module.exports = {
 
 // TODO: deprecate the old version in favor of this one
 module.exports.bigint = {
-  clock: mapObject(module.exports.clock, fn => () => fn().then(BigInt)),
-  clockFromReceipt: mapObject(module.exports.clockFromReceipt, fn => receipt => fn(receipt).then(BigInt)),
+  clock: mapValues(module.exports.clock, fn => () => fn().then(BigInt)),
+  clockFromReceipt: mapValues(module.exports.clockFromReceipt, fn => receipt => fn(receipt).then(BigInt)),
   forward: module.exports.forward,
-  duration: mapObject(module.exports.duration, fn => n => BigInt(fn(n))),
+  duration: mapValues(module.exports.duration, fn => n => BigInt(fn(n))),
 };

+ 2 - 12
test/metatx/ERC2771Context.test.js

@@ -3,7 +3,7 @@ const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
 const { impersonate } = require('../helpers/account');
-const { getDomain } = require('../helpers/eip712');
+const { getDomain, ForwardRequest } = require('../helpers/eip712');
 const { MAX_UINT48 } = require('../helpers/constants');
 
 const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior');
@@ -15,17 +15,7 @@ async function fixture() {
   const forwarderAsSigner = await impersonate(forwarder.target);
   const context = await ethers.deployContract('ERC2771ContextMock', [forwarder]);
   const domain = await getDomain(forwarder);
-  const types = {
-    ForwardRequest: [
-      { name: 'from', type: 'address' },
-      { name: 'to', type: 'address' },
-      { name: 'value', type: 'uint256' },
-      { name: 'gas', type: 'uint256' },
-      { name: 'nonce', type: 'uint256' },
-      { name: 'deadline', type: 'uint48' },
-      { name: 'data', type: 'bytes' },
-    ],
-  };
+  const types = { ForwardRequest };
 
   return { sender, other, forwarder, forwarderAsSigner, context, domain, types };
 }

+ 2 - 12
test/metatx/ERC2771Forwarder.test.js

@@ -2,7 +2,7 @@ const { ethers } = require('hardhat');
 const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const { getDomain } = require('../helpers/eip712');
+const { getDomain, ForwardRequest } = require('../helpers/eip712');
 const { bigint: time } = require('../helpers/time');
 const { sum } = require('../helpers/math');
 
@@ -12,17 +12,7 @@ async function fixture() {
   const forwarder = await ethers.deployContract('ERC2771Forwarder', ['ERC2771Forwarder']);
   const receiver = await ethers.deployContract('CallReceiverMockTrustingForwarder', [forwarder]);
   const domain = await getDomain(forwarder);
-  const types = {
-    ForwardRequest: [
-      { name: 'from', type: 'address' },
-      { name: 'to', type: 'address' },
-      { name: 'value', type: 'uint256' },
-      { name: 'gas', type: 'uint256' },
-      { name: 'nonce', type: 'uint256' },
-      { name: 'deadline', type: 'uint48' },
-      { name: 'data', type: 'bytes' },
-    ],
-  };
+  const types = { ForwardRequest };
 
   const forgeRequest = async (override = {}, signer = sender) => {
     const req = {

+ 0 - 116
test/token/ERC20/extensions/ERC20Burnable.behavior.js

@@ -1,116 +0,0 @@
-const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers');
-const { ZERO_ADDRESS } = constants;
-
-const { expect } = require('chai');
-const { expectRevertCustomError } = require('../../../helpers/customError');
-
-function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) {
-  describe('burn', function () {
-    describe('when the given value is not greater than balance of the sender', function () {
-      context('for a zero value', function () {
-        shouldBurn(new BN(0));
-      });
-
-      context('for a non-zero value', function () {
-        shouldBurn(new BN(100));
-      });
-
-      function shouldBurn(value) {
-        beforeEach(async function () {
-          this.receipt = await this.token.burn(value, { from: owner });
-        });
-
-        it('burns the requested value', async function () {
-          expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(value));
-        });
-
-        it('emits a transfer event', async function () {
-          expectEvent(this.receipt, 'Transfer', {
-            from: owner,
-            to: ZERO_ADDRESS,
-            value: value,
-          });
-        });
-      }
-    });
-
-    describe('when the given value is greater than the balance of the sender', function () {
-      const value = initialBalance.addn(1);
-
-      it('reverts', async function () {
-        await expectRevertCustomError(this.token.burn(value, { from: owner }), 'ERC20InsufficientBalance', [
-          owner,
-          initialBalance,
-          value,
-        ]);
-      });
-    });
-  });
-
-  describe('burnFrom', function () {
-    describe('on success', function () {
-      context('for a zero value', function () {
-        shouldBurnFrom(new BN(0));
-      });
-
-      context('for a non-zero value', function () {
-        shouldBurnFrom(new BN(100));
-      });
-
-      function shouldBurnFrom(value) {
-        const originalAllowance = value.muln(3);
-
-        beforeEach(async function () {
-          await this.token.approve(burner, originalAllowance, { from: owner });
-          this.receipt = await this.token.burnFrom(owner, value, { from: burner });
-        });
-
-        it('burns the requested value', async function () {
-          expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(value));
-        });
-
-        it('decrements allowance', async function () {
-          expect(await this.token.allowance(owner, burner)).to.be.bignumber.equal(originalAllowance.sub(value));
-        });
-
-        it('emits a transfer event', async function () {
-          expectEvent(this.receipt, 'Transfer', {
-            from: owner,
-            to: ZERO_ADDRESS,
-            value: value,
-          });
-        });
-      }
-    });
-
-    describe('when the given value is greater than the balance of the sender', function () {
-      const value = initialBalance.addn(1);
-
-      it('reverts', async function () {
-        await this.token.approve(burner, value, { from: owner });
-        await expectRevertCustomError(this.token.burnFrom(owner, value, { from: burner }), 'ERC20InsufficientBalance', [
-          owner,
-          initialBalance,
-          value,
-        ]);
-      });
-    });
-
-    describe('when the given value is greater than the allowance', function () {
-      const allowance = new BN(100);
-
-      it('reverts', async function () {
-        await this.token.approve(burner, allowance, { from: owner });
-        await expectRevertCustomError(
-          this.token.burnFrom(owner, allowance.addn(1), { from: burner }),
-          'ERC20InsufficientAllowance',
-          [burner, allowance, allowance.addn(1)],
-        );
-      });
-    });
-  });
-}
-
-module.exports = {
-  shouldBehaveLikeERC20Burnable,
-};

+ 99 - 11
test/token/ERC20/extensions/ERC20Burnable.test.js

@@ -1,20 +1,108 @@
-const { BN } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const { shouldBehaveLikeERC20Burnable } = require('./ERC20Burnable.behavior');
-const ERC20Burnable = artifacts.require('$ERC20Burnable');
+const name = 'My Token';
+const symbol = 'MTKN';
+const initialBalance = 1000n;
 
-contract('ERC20Burnable', function (accounts) {
-  const [owner, ...otherAccounts] = accounts;
+async function fixture() {
+  const [owner, burner] = await ethers.getSigners();
 
-  const initialBalance = new BN(1000);
+  const token = await ethers.deployContract('$ERC20Burnable', [name, symbol], owner);
+  await token.$_mint(owner, initialBalance);
 
-  const name = 'My Token';
-  const symbol = 'MTKN';
+  return { owner, burner, token, initialBalance };
+}
 
+describe('ERC20Burnable', function () {
   beforeEach(async function () {
-    this.token = await ERC20Burnable.new(name, symbol, { from: owner });
-    await this.token.$_mint(owner, initialBalance);
+    Object.assign(this, await loadFixture(fixture));
   });
 
-  shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts);
+  describe('burn', function () {
+    it('reverts if not enough balance', async function () {
+      const value = this.initialBalance + 1n;
+
+      await expect(this.token.connect(this.owner).burn(value))
+        .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
+        .withArgs(this.owner.address, this.initialBalance, value);
+    });
+
+    describe('on success', function () {
+      for (const { title, value } of [
+        { title: 'for a zero value', value: 0n },
+        { title: 'for a non-zero value', value: 100n },
+      ]) {
+        describe(title, function () {
+          beforeEach(async function () {
+            this.tx = await this.token.connect(this.owner).burn(value);
+          });
+
+          it('burns the requested value', async function () {
+            await expect(this.tx).to.changeTokenBalance(this.token, this.owner, -value);
+          });
+
+          it('emits a transfer event', async function () {
+            await expect(this.tx)
+              .to.emit(this.token, 'Transfer')
+              .withArgs(this.owner.address, ethers.ZeroAddress, value);
+          });
+        });
+      }
+    });
+  });
+
+  describe('burnFrom', function () {
+    describe('reverts', function () {
+      it('if not enough balance', async function () {
+        const value = this.initialBalance + 1n;
+
+        await this.token.connect(this.owner).approve(this.burner, value);
+
+        await expect(this.token.connect(this.burner).burnFrom(this.owner, value))
+          .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
+          .withArgs(this.owner.address, this.initialBalance, value);
+      });
+
+      it('if not enough allowance', async function () {
+        const allowance = 100n;
+
+        await this.token.connect(this.owner).approve(this.burner, allowance);
+
+        await expect(this.token.connect(this.burner).burnFrom(this.owner, allowance + 1n))
+          .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance')
+          .withArgs(this.burner.address, allowance, allowance + 1n);
+      });
+    });
+
+    describe('on success', function () {
+      for (const { title, value } of [
+        { title: 'for a zero value', value: 0n },
+        { title: 'for a non-zero value', value: 100n },
+      ]) {
+        describe(title, function () {
+          const originalAllowance = value * 3n;
+
+          beforeEach(async function () {
+            await this.token.connect(this.owner).approve(this.burner, originalAllowance);
+            this.tx = await this.token.connect(this.burner).burnFrom(this.owner, value);
+          });
+
+          it('burns the requested value', async function () {
+            await expect(this.tx).to.changeTokenBalance(this.token, this.owner, -value);
+          });
+
+          it('decrements allowance', async function () {
+            expect(await this.token.allowance(this.owner, this.burner)).to.equal(originalAllowance - value);
+          });
+
+          it('emits a transfer event', async function () {
+            await expect(this.tx)
+              .to.emit(this.token, 'Transfer')
+              .withArgs(this.owner.address, ethers.ZeroAddress, value);
+          });
+        });
+      }
+    });
+  });
 });

+ 0 - 31
test/token/ERC20/extensions/ERC20Capped.behavior.js

@@ -1,31 +0,0 @@
-const { expect } = require('chai');
-const { expectRevertCustomError } = require('../../../helpers/customError');
-
-function shouldBehaveLikeERC20Capped(accounts, cap) {
-  describe('capped token', function () {
-    const user = accounts[0];
-
-    it('starts with the correct cap', async function () {
-      expect(await this.token.cap()).to.be.bignumber.equal(cap);
-    });
-
-    it('mints when value is less than cap', async function () {
-      await this.token.$_mint(user, cap.subn(1));
-      expect(await this.token.totalSupply()).to.be.bignumber.equal(cap.subn(1));
-    });
-
-    it('fails to mint if the value exceeds the cap', async function () {
-      await this.token.$_mint(user, cap.subn(1));
-      await expectRevertCustomError(this.token.$_mint(user, 2), 'ERC20ExceededCap', [cap.addn(1), cap]);
-    });
-
-    it('fails to mint after cap is reached', async function () {
-      await this.token.$_mint(user, cap);
-      await expectRevertCustomError(this.token.$_mint(user, 1), 'ERC20ExceededCap', [cap.addn(1), cap]);
-    });
-  });
-}
-
-module.exports = {
-  shouldBehaveLikeERC20Capped,
-};

+ 44 - 13
test/token/ERC20/extensions/ERC20Capped.test.js

@@ -1,24 +1,55 @@
-const { ether } = require('@openzeppelin/test-helpers');
-const { shouldBehaveLikeERC20Capped } = require('./ERC20Capped.behavior');
-const { expectRevertCustomError } = require('../../../helpers/customError');
+const { ethers } = require('hardhat');
+const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const ERC20Capped = artifacts.require('$ERC20Capped');
+const name = 'My Token';
+const symbol = 'MTKN';
+const cap = 1000n;
 
-contract('ERC20Capped', function (accounts) {
-  const cap = ether('1000');
+async function fixture() {
+  const [user] = await ethers.getSigners();
 
-  const name = 'My Token';
-  const symbol = 'MTKN';
+  const token = await ethers.deployContract('$ERC20Capped', [name, symbol, cap]);
+
+  return { user, token, cap };
+}
+
+describe('ERC20Capped', function () {
+  beforeEach(async function () {
+    Object.assign(this, await loadFixture(fixture));
+  });
 
   it('requires a non-zero cap', async function () {
-    await expectRevertCustomError(ERC20Capped.new(name, symbol, 0), 'ERC20InvalidCap', [0]);
+    const ERC20Capped = await ethers.getContractFactory('$ERC20Capped');
+
+    await expect(ERC20Capped.deploy(name, symbol, 0))
+      .to.be.revertedWithCustomError(ERC20Capped, 'ERC20InvalidCap')
+      .withArgs(0);
   });
 
-  context('once deployed', async function () {
-    beforeEach(async function () {
-      this.token = await ERC20Capped.new(name, symbol, cap);
+  describe('capped token', function () {
+    it('starts with the correct cap', async function () {
+      expect(await this.token.cap()).to.equal(this.cap);
     });
 
-    shouldBehaveLikeERC20Capped(accounts, cap);
+    it('mints when value is less than cap', async function () {
+      const value = this.cap - 1n;
+      await this.token.$_mint(this.user, value);
+      expect(await this.token.totalSupply()).to.equal(value);
+    });
+
+    it('fails to mint if the value exceeds the cap', async function () {
+      await this.token.$_mint(this.user, this.cap - 1n);
+      await expect(this.token.$_mint(this.user, 2))
+        .to.be.revertedWithCustomError(this.token, 'ERC20ExceededCap')
+        .withArgs(this.cap + 1n, this.cap);
+    });
+
+    it('fails to mint after cap is reached', async function () {
+      await this.token.$_mint(this.user, this.cap);
+      await expect(this.token.$_mint(this.user, 1))
+        .to.be.revertedWithCustomError(this.token, 'ERC20ExceededCap')
+        .withArgs(this.cap + 1n, this.cap);
+    });
   });
 });

+ 101 - 147
test/token/ERC20/extensions/ERC20FlashMint.test.js

@@ -1,209 +1,163 @@
-/* eslint-disable */
-
-const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
-const { expectRevertCustomError } = require('../../../helpers/customError');
-const { MAX_UINT256, ZERO_ADDRESS } = constants;
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const ERC20FlashMintMock = artifacts.require('$ERC20FlashMintMock');
-const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock');
+const name = 'My Token';
+const symbol = 'MTKN';
+const initialSupply = 100n;
+const loanValue = 10_000_000_000_000n;
 
-contract('ERC20FlashMint', function (accounts) {
-  const [initialHolder, other, anotherAccount] = accounts;
+async function fixture() {
+  const [initialHolder, other, anotherAccount] = await ethers.getSigners();
 
-  const name = 'My Token';
-  const symbol = 'MTKN';
+  const token = await ethers.deployContract('$ERC20FlashMintMock', [name, symbol]);
+  await token.$_mint(initialHolder, initialSupply);
 
-  const initialSupply = new BN(100);
-  const loanValue = new BN(10000000000000);
+  return { initialHolder, other, anotherAccount, token };
+}
 
+describe('ERC20FlashMint', function () {
   beforeEach(async function () {
-    this.token = await ERC20FlashMintMock.new(name, symbol);
-    await this.token.$_mint(initialHolder, initialSupply);
+    Object.assign(this, await loadFixture(fixture));
   });
 
   describe('maxFlashLoan', function () {
     it('token match', async function () {
-      expect(await this.token.maxFlashLoan(this.token.address)).to.be.bignumber.equal(MAX_UINT256.sub(initialSupply));
+      expect(await this.token.maxFlashLoan(this.token)).to.equal(ethers.MaxUint256 - initialSupply);
     });
 
     it('token mismatch', async function () {
-      expect(await this.token.maxFlashLoan(ZERO_ADDRESS)).to.be.bignumber.equal('0');
+      expect(await this.token.maxFlashLoan(ethers.ZeroAddress)).to.equal(0n);
     });
   });
 
   describe('flashFee', function () {
     it('token match', async function () {
-      expect(await this.token.flashFee(this.token.address, loanValue)).to.be.bignumber.equal('0');
+      expect(await this.token.flashFee(this.token, loanValue)).to.equal(0n);
     });
 
     it('token mismatch', async function () {
-      await expectRevertCustomError(this.token.flashFee(ZERO_ADDRESS, loanValue), 'ERC3156UnsupportedToken', [
-        ZERO_ADDRESS,
-      ]);
+      await expect(this.token.flashFee(ethers.ZeroAddress, loanValue))
+        .to.be.revertedWithCustomError(this.token, 'ERC3156UnsupportedToken')
+        .withArgs(ethers.ZeroAddress);
     });
   });
 
   describe('flashFeeReceiver', function () {
     it('default receiver', async function () {
-      expect(await this.token.$_flashFeeReceiver()).to.be.eq(ZERO_ADDRESS);
+      expect(await this.token.$_flashFeeReceiver()).to.equal(ethers.ZeroAddress);
     });
   });
 
   describe('flashLoan', function () {
     it('success', async function () {
-      const receiver = await ERC3156FlashBorrowerMock.new(true, true);
-      const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x');
-
-      await expectEvent.inTransaction(tx, this.token, 'Transfer', {
-        from: ZERO_ADDRESS,
-        to: receiver.address,
-        value: loanValue,
-      });
-      await expectEvent.inTransaction(tx, this.token, 'Transfer', {
-        from: receiver.address,
-        to: ZERO_ADDRESS,
-        value: loanValue,
-      });
-      await expectEvent.inTransaction(tx, receiver, 'BalanceOf', {
-        token: this.token.address,
-        account: receiver.address,
-        value: loanValue,
-      });
-      await expectEvent.inTransaction(tx, receiver, 'TotalSupply', {
-        token: this.token.address,
-        value: initialSupply.add(loanValue),
-      });
-
-      expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
-      expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal('0');
-      expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0');
+      const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
+
+      const tx = await this.token.flashLoan(receiver, this.token, loanValue, '0x');
+      await expect(tx)
+        .to.emit(this.token, 'Transfer')
+        .withArgs(ethers.ZeroAddress, receiver.target, loanValue)
+        .to.emit(this.token, 'Transfer')
+        .withArgs(receiver.target, ethers.ZeroAddress, loanValue)
+        .to.emit(receiver, 'BalanceOf')
+        .withArgs(this.token.target, receiver.target, loanValue)
+        .to.emit(receiver, 'TotalSupply')
+        .withArgs(this.token.target, initialSupply + loanValue);
+      await expect(tx).to.changeTokenBalance(this.token, receiver, 0);
+
+      expect(await this.token.totalSupply()).to.equal(initialSupply);
+      expect(await this.token.allowance(receiver, this.token)).to.equal(0n);
     });
 
     it('missing return value', async function () {
-      const receiver = await ERC3156FlashBorrowerMock.new(false, true);
-      await expectRevertCustomError(
-        this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'),
-        'ERC3156InvalidReceiver',
-        [receiver.address],
-      );
+      const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [false, true]);
+      await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x'))
+        .to.be.revertedWithCustomError(this.token, 'ERC3156InvalidReceiver')
+        .withArgs(receiver.target);
     });
 
     it('missing approval', async function () {
-      const receiver = await ERC3156FlashBorrowerMock.new(true, false);
-      await expectRevertCustomError(
-        this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'),
-        'ERC20InsufficientAllowance',
-        [this.token.address, 0, loanValue],
-      );
+      const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, false]);
+      await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x'))
+        .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance')
+        .withArgs(this.token.target, 0, loanValue);
     });
 
     it('unavailable funds', async function () {
-      const receiver = await ERC3156FlashBorrowerMock.new(true, true);
-      const data = this.token.contract.methods.transfer(other, 10).encodeABI();
-      await expectRevertCustomError(
-        this.token.flashLoan(receiver.address, this.token.address, loanValue, data),
-        'ERC20InsufficientBalance',
-        [receiver.address, loanValue - 10, loanValue],
-      );
+      const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
+      const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]);
+      await expect(this.token.flashLoan(receiver, this.token, loanValue, data))
+        .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
+        .withArgs(receiver.target, loanValue - 10n, loanValue);
     });
 
     it('more than maxFlashLoan', async function () {
-      const receiver = await ERC3156FlashBorrowerMock.new(true, true);
-      const data = this.token.contract.methods.transfer(other, 10).encodeABI();
-      // _mint overflow reverts using a panic code. No reason string.
-      await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data));
+      const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
+      const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]);
+      await expect(this.token.flashLoan(receiver, this.token, ethers.MaxUint256, data))
+        .to.be.revertedWithCustomError(this.token, 'ERC3156ExceededMaxLoan')
+        .withArgs(ethers.MaxUint256 - initialSupply);
     });
 
     describe('custom flash fee & custom fee receiver', function () {
-      const receiverInitialBalance = new BN(200000);
-      const flashFee = new BN(5000);
+      const receiverInitialBalance = 200_000n;
+      const flashFee = 5_000n;
 
       beforeEach('init receiver balance & set flash fee', async function () {
-        this.receiver = await ERC3156FlashBorrowerMock.new(true, true);
-        const receipt = await this.token.$_mint(this.receiver.address, receiverInitialBalance);
-        await expectEvent(receipt, 'Transfer', {
-          from: ZERO_ADDRESS,
-          to: this.receiver.address,
-          value: receiverInitialBalance,
-        });
-        expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(receiverInitialBalance);
+        this.receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
+
+        const tx = await this.token.$_mint(this.receiver, receiverInitialBalance);
+        await expect(tx)
+          .to.emit(this.token, 'Transfer')
+          .withArgs(ethers.ZeroAddress, this.receiver.target, receiverInitialBalance);
+        await expect(tx).to.changeTokenBalance(this.token, this.receiver, receiverInitialBalance);
 
         await this.token.setFlashFee(flashFee);
-        expect(await this.token.flashFee(this.token.address, loanValue)).to.be.bignumber.equal(flashFee);
+        expect(await this.token.flashFee(this.token, loanValue)).to.equal(flashFee);
       });
 
       it('default flash fee receiver', async function () {
-        const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanValue, '0x');
-        await expectEvent.inTransaction(tx, this.token, 'Transfer', {
-          from: ZERO_ADDRESS,
-          to: this.receiver.address,
-          value: loanValue,
-        });
-        await expectEvent.inTransaction(tx, this.token, 'Transfer', {
-          from: this.receiver.address,
-          to: ZERO_ADDRESS,
-          value: loanValue.add(flashFee),
-        });
-        await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', {
-          token: this.token.address,
-          account: this.receiver.address,
-          value: receiverInitialBalance.add(loanValue),
-        });
-        await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', {
-          token: this.token.address,
-          value: initialSupply.add(receiverInitialBalance).add(loanValue),
-        });
-
-        expect(await this.token.totalSupply()).to.be.bignumber.equal(
-          initialSupply.add(receiverInitialBalance).sub(flashFee),
-        );
-        expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(
-          receiverInitialBalance.sub(flashFee),
-        );
-        expect(await this.token.balanceOf(await this.token.$_flashFeeReceiver())).to.be.bignumber.equal('0');
-        expect(await this.token.allowance(this.receiver.address, this.token.address)).to.be.bignumber.equal('0');
+        const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x');
+        await expect(tx)
+          .to.emit(this.token, 'Transfer')
+          .withArgs(ethers.ZeroAddress, this.receiver.target, loanValue)
+          .to.emit(this.token, 'Transfer')
+          .withArgs(this.receiver.target, ethers.ZeroAddress, loanValue + flashFee)
+          .to.emit(this.receiver, 'BalanceOf')
+          .withArgs(this.token.target, this.receiver.target, receiverInitialBalance + loanValue)
+          .to.emit(this.receiver, 'TotalSupply')
+          .withArgs(this.token.target, initialSupply + receiverInitialBalance + loanValue);
+        await expect(tx).to.changeTokenBalances(this.token, [this.receiver, ethers.ZeroAddress], [-flashFee, 0]);
+
+        expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance - flashFee);
+        expect(await this.token.allowance(this.receiver, this.token)).to.equal(0n);
       });
 
       it('custom flash fee receiver', async function () {
-        const flashFeeReceiverAddress = anotherAccount;
+        const flashFeeReceiverAddress = this.anotherAccount;
         await this.token.setFlashFeeReceiver(flashFeeReceiverAddress);
-        expect(await this.token.$_flashFeeReceiver()).to.be.eq(flashFeeReceiverAddress);
-
-        expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0');
-
-        const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanValue, '0x');
-        await expectEvent.inTransaction(tx, this.token, 'Transfer', {
-          from: ZERO_ADDRESS,
-          to: this.receiver.address,
-          value: loanValue,
-        });
-        await expectEvent.inTransaction(tx, this.token, 'Transfer', {
-          from: this.receiver.address,
-          to: ZERO_ADDRESS,
-          value: loanValue,
-        });
-        await expectEvent.inTransaction(tx, this.token, 'Transfer', {
-          from: this.receiver.address,
-          to: flashFeeReceiverAddress,
-          value: flashFee,
-        });
-        await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', {
-          token: this.token.address,
-          account: this.receiver.address,
-          value: receiverInitialBalance.add(loanValue),
-        });
-        await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', {
-          token: this.token.address,
-          value: initialSupply.add(receiverInitialBalance).add(loanValue),
-        });
-
-        expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance));
-        expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(
-          receiverInitialBalance.sub(flashFee),
+        expect(await this.token.$_flashFeeReceiver()).to.equal(flashFeeReceiverAddress.address);
+
+        const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x');
+        await expect(tx)
+          .to.emit(this.token, 'Transfer')
+          .withArgs(ethers.ZeroAddress, this.receiver.target, loanValue)
+          .to.emit(this.token, 'Transfer')
+          .withArgs(this.receiver.target, ethers.ZeroAddress, loanValue)
+          .to.emit(this.token, 'Transfer')
+          .withArgs(this.receiver.target, flashFeeReceiverAddress.address, flashFee)
+          .to.emit(this.receiver, 'BalanceOf')
+          .withArgs(this.token.target, this.receiver.target, receiverInitialBalance + loanValue)
+          .to.emit(this.receiver, 'TotalSupply')
+          .withArgs(this.token.target, initialSupply + receiverInitialBalance + loanValue);
+        await expect(tx).to.changeTokenBalances(
+          this.token,
+          [this.receiver, flashFeeReceiverAddress],
+          [-flashFee, flashFee],
         );
-        expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal(flashFee);
-        expect(await this.token.allowance(this.receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0');
+
+        expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance);
+        expect(await this.token.allowance(this.receiver, flashFeeReceiverAddress)).to.equal(0n);
       });
     });
   });

+ 48 - 55
test/token/ERC20/extensions/ERC20Pausable.test.js

@@ -1,135 +1,128 @@
-const { BN } = require('@openzeppelin/test-helpers');
-
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
-const { expectRevertCustomError } = require('../../../helpers/customError');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const ERC20Pausable = artifacts.require('$ERC20Pausable');
+const name = 'My Token';
+const symbol = 'MTKN';
+const initialSupply = 100n;
 
-contract('ERC20Pausable', function (accounts) {
-  const [holder, recipient, anotherAccount] = accounts;
+async function fixture() {
+  const [holder, recipient, approved] = await ethers.getSigners();
 
-  const initialSupply = new BN(100);
+  const token = await ethers.deployContract('$ERC20Pausable', [name, symbol]);
+  await token.$_mint(holder, initialSupply);
 
-  const name = 'My Token';
-  const symbol = 'MTKN';
+  return { holder, recipient, approved, token };
+}
 
+describe('ERC20Pausable', function () {
   beforeEach(async function () {
-    this.token = await ERC20Pausable.new(name, symbol);
-    await this.token.$_mint(holder, initialSupply);
+    Object.assign(this, await loadFixture(fixture));
   });
 
   describe('pausable token', function () {
     describe('transfer', function () {
       it('allows to transfer when unpaused', async function () {
-        await this.token.transfer(recipient, initialSupply, { from: holder });
-
-        expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0');
-        expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply);
+        await expect(this.token.connect(this.holder).transfer(this.recipient, initialSupply)).to.changeTokenBalances(
+          this.token,
+          [this.holder, this.recipient],
+          [-initialSupply, initialSupply],
+        );
       });
 
       it('allows to transfer when paused and then unpaused', async function () {
         await this.token.$_pause();
         await this.token.$_unpause();
 
-        await this.token.transfer(recipient, initialSupply, { from: holder });
-
-        expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0');
-        expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply);
+        await expect(this.token.connect(this.holder).transfer(this.recipient, initialSupply)).to.changeTokenBalances(
+          this.token,
+          [this.holder, this.recipient],
+          [-initialSupply, initialSupply],
+        );
       });
 
       it('reverts when trying to transfer when paused', async function () {
         await this.token.$_pause();
 
-        await expectRevertCustomError(
-          this.token.transfer(recipient, initialSupply, { from: holder }),
-          'EnforcedPause',
-          [],
-        );
+        await expect(
+          this.token.connect(this.holder).transfer(this.recipient, initialSupply),
+        ).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
       });
     });
 
     describe('transfer from', function () {
-      const allowance = new BN(40);
+      const allowance = 40n;
 
       beforeEach(async function () {
-        await this.token.approve(anotherAccount, allowance, { from: holder });
+        await this.token.connect(this.holder).approve(this.approved, allowance);
       });
 
       it('allows to transfer from when unpaused', async function () {
-        await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount });
-
-        expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance);
-        expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance));
+        await expect(
+          this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance),
+        ).to.changeTokenBalances(this.token, [this.holder, this.recipient], [-allowance, allowance]);
       });
 
       it('allows to transfer when paused and then unpaused', async function () {
         await this.token.$_pause();
         await this.token.$_unpause();
 
-        await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount });
-
-        expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance);
-        expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance));
+        await expect(
+          this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance),
+        ).to.changeTokenBalances(this.token, [this.holder, this.recipient], [-allowance, allowance]);
       });
 
       it('reverts when trying to transfer from when paused', async function () {
         await this.token.$_pause();
 
-        await expectRevertCustomError(
-          this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }),
-          'EnforcedPause',
-          [],
-        );
+        await expect(
+          this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance),
+        ).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
       });
     });
 
     describe('mint', function () {
-      const value = new BN('42');
+      const value = 42n;
 
       it('allows to mint when unpaused', async function () {
-        await this.token.$_mint(recipient, value);
-
-        expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value);
+        await expect(this.token.$_mint(this.recipient, value)).to.changeTokenBalance(this.token, this.recipient, value);
       });
 
       it('allows to mint when paused and then unpaused', async function () {
         await this.token.$_pause();
         await this.token.$_unpause();
 
-        await this.token.$_mint(recipient, value);
-
-        expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value);
+        await expect(this.token.$_mint(this.recipient, value)).to.changeTokenBalance(this.token, this.recipient, value);
       });
 
       it('reverts when trying to mint when paused', async function () {
         await this.token.$_pause();
 
-        await expectRevertCustomError(this.token.$_mint(recipient, value), 'EnforcedPause', []);
+        await expect(this.token.$_mint(this.recipient, value)).to.be.revertedWithCustomError(
+          this.token,
+          'EnforcedPause',
+        );
       });
     });
 
     describe('burn', function () {
-      const value = new BN('42');
+      const value = 42n;
 
       it('allows to burn when unpaused', async function () {
-        await this.token.$_burn(holder, value);
-
-        expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(value));
+        await expect(this.token.$_burn(this.holder, value)).to.changeTokenBalance(this.token, this.holder, -value);
       });
 
       it('allows to burn when paused and then unpaused', async function () {
         await this.token.$_pause();
         await this.token.$_unpause();
 
-        await this.token.$_burn(holder, value);
-
-        expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(value));
+        await expect(this.token.$_burn(this.holder, value)).to.changeTokenBalance(this.token, this.holder, -value);
       });
 
       it('reverts when trying to burn when paused', async function () {
         await this.token.$_pause();
 
-        await expectRevertCustomError(this.token.$_burn(holder, value), 'EnforcedPause', []);
+        await expect(this.token.$_burn(this.holder, value)).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
       });
     });
   });

+ 73 - 85
test/token/ERC20/extensions/ERC20Permit.test.js

@@ -1,41 +1,38 @@
-/* eslint-disable */
-
-const { BN, constants, time } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
-const { MAX_UINT256 } = constants;
-
-const { fromRpcSig } = require('ethereumjs-util');
-const ethSigUtil = require('eth-sig-util');
-const Wallet = require('ethereumjs-wallet').default;
-
-const ERC20Permit = artifacts.require('$ERC20Permit');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
+const { getDomain, domainSeparator, Permit } = require('../../../helpers/eip712');
 const {
-  types: { Permit },
-  getDomain,
-  domainType,
-  domainSeparator,
-} = require('../../../helpers/eip712');
-const { getChainId } = require('../../../helpers/chainid');
-const { expectRevertCustomError } = require('../../../helpers/customError');
+  bigint: { clock, duration },
+} = require('../../../helpers/time');
 
-contract('ERC20Permit', function (accounts) {
-  const [initialHolder, spender] = accounts;
+const name = 'My Token';
+const symbol = 'MTKN';
+const initialSupply = 100n;
 
-  const name = 'My Token';
-  const symbol = 'MTKN';
+async function fixture() {
+  const [initialHolder, spender, owner, other] = await ethers.getSigners();
 
-  const initialSupply = new BN(100);
+  const token = await ethers.deployContract('$ERC20Permit', [name, symbol, name]);
+  await token.$_mint(initialHolder, initialSupply);
 
-  beforeEach(async function () {
-    this.chainId = await getChainId();
+  return {
+    initialHolder,
+    spender,
+    owner,
+    other,
+    token,
+  };
+}
 
-    this.token = await ERC20Permit.new(name, symbol, name);
-    await this.token.$_mint(initialHolder, initialSupply);
+describe('ERC20Permit', function () {
+  beforeEach(async function () {
+    Object.assign(this, await loadFixture(fixture));
   });
 
   it('initial nonce is 0', async function () {
-    expect(await this.token.nonces(initialHolder)).to.be.bignumber.equal('0');
+    expect(await this.token.nonces(this.initialHolder)).to.equal(0n);
   });
 
   it('domain separator', async function () {
@@ -43,81 +40,72 @@ contract('ERC20Permit', function (accounts) {
   });
 
   describe('permit', function () {
-    const wallet = Wallet.generate();
-
-    const owner = wallet.getAddressString();
-    const value = new BN(42);
-    const nonce = 0;
-    const maxDeadline = MAX_UINT256;
-
-    const buildData = (contract, deadline = maxDeadline) =>
-      getDomain(contract).then(domain => ({
-        primaryType: 'Permit',
-        types: { EIP712Domain: domainType(domain), Permit },
-        domain,
-        message: { owner, spender, value, nonce, deadline },
-      }));
+    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 buildData(this.token)
-        .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }))
-        .then(fromRpcSig);
+      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(owner, spender, value, maxDeadline, v, r, s);
+      await this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s);
 
-      expect(await this.token.nonces(owner)).to.be.bignumber.equal('1');
-      expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
+      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 sig = await buildData(this.token).then(data =>
-        ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }),
-      );
-      const { r, s, v } = fromRpcSig(sig);
-
-      await this.token.permit(owner, spender, value, maxDeadline, v, r, s);
-
-      const domain = await getDomain(this.token);
-      const typedMessage = {
-        primaryType: 'Permit',
-        types: { EIP712Domain: domainType(domain), Permit },
-        domain,
-        message: { owner, spender, value, nonce: nonce + 1, deadline: maxDeadline },
-      };
-
-      await expectRevertCustomError(
-        this.token.permit(owner, spender, value, maxDeadline, v, r, s),
-        'ERC2612InvalidSigner',
-        [ethSigUtil.recoverTypedSignature({ data: typedMessage, sig }), owner],
+      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.address);
     });
 
     it('rejects other signature', async function () {
-      const otherWallet = Wallet.generate();
+      const { v, r, s } = await this.buildData(this.token)
+        .then(({ domain, types, message }) => this.other.signTypedData(domain, types, message))
+        .then(ethers.Signature.from);
 
-      const { v, r, s } = await buildData(this.token)
-        .then(data => ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data }))
-        .then(fromRpcSig);
-
-      await expectRevertCustomError(
-        this.token.permit(owner, spender, value, maxDeadline, v, r, s),
-        'ERC2612InvalidSigner',
-        [await otherWallet.getAddressString(), owner],
-      );
+      await expect(this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s))
+        .to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner')
+        .withArgs(this.other.address, this.owner.address);
     });
 
     it('rejects expired permit', async function () {
-      const deadline = (await time.latest()) - time.duration.weeks(1);
+      const deadline = (await clock.timestamp()) - duration.weeks(1);
 
-      const { v, r, s } = await buildData(this.token, deadline)
-        .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }))
-        .then(fromRpcSig);
+      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 expectRevertCustomError(
-        this.token.permit(owner, spender, value, deadline, v, r, s),
-        'ERC2612ExpiredSignature',
-        [deadline],
-      );
+      await expect(this.token.permit(this.owner, this.spender, value, deadline, v, r, s))
+        .to.be.revertedWithCustomError(this.token, 'ERC2612ExpiredSignature')
+        .withArgs(deadline);
     });
   });
 });

+ 1 - 5
test/token/ERC20/extensions/ERC20Votes.test.js

@@ -10,11 +10,7 @@ const ethSigUtil = require('eth-sig-util');
 const Wallet = require('ethereumjs-wallet').default;
 
 const { batchInBlock } = require('../../../helpers/txpool');
-const {
-  getDomain,
-  domainType,
-  types: { Delegation },
-} = require('../../../helpers/eip712');
+const { getDomain, domainType, Delegation } = require('../../../helpers/eip712');
 const { clock, clockFromReceipt } = require('../../../helpers/time');
 const { expectRevertCustomError } = require('../../../helpers/customError');
 

+ 7 - 12
test/token/ERC20/extensions/ERC20Wrapper.test.js

@@ -6,12 +6,13 @@ const { shouldBehaveLikeERC20 } = require('../ERC20.behavior');
 
 const name = 'My Token';
 const symbol = 'MTKN';
+const decimals = 9n;
 const initialSupply = 100n;
 
 async function fixture() {
   const [initialHolder, recipient, anotherAccount] = await ethers.getSigners();
 
-  const underlying = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 9]);
+  const underlying = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, decimals]);
   await underlying.$_mint(initialHolder, initialSupply);
 
   const token = await ethers.deployContract('$ERC20Wrapper', [`Wrapped ${name}`, `W${symbol}`, underlying]);
@@ -20,9 +21,6 @@ async function fixture() {
 }
 
 describe('ERC20Wrapper', function () {
-  const name = 'My Token';
-  const symbol = 'MTKN';
-
   beforeEach(async function () {
     Object.assign(this, await loadFixture(fixture));
   });
@@ -40,7 +38,7 @@ describe('ERC20Wrapper', function () {
   });
 
   it('has the same decimals as the underlying token', async function () {
-    expect(await this.token.decimals()).to.be.equal(9n);
+    expect(await this.token.decimals()).to.be.equal(decimals);
   });
 
   it('decimals default back to 18 if token has no metadata', async function () {
@@ -56,13 +54,13 @@ describe('ERC20Wrapper', function () {
   describe('deposit', function () {
     it('executes with approval', async function () {
       await this.underlying.connect(this.initialHolder).approve(this.token, initialSupply);
+
       const tx = await this.token.connect(this.initialHolder).depositFor(this.initialHolder, initialSupply);
       await expect(tx)
         .to.emit(this.underlying, 'Transfer')
         .withArgs(this.initialHolder.address, this.token.target, initialSupply)
         .to.emit(this.token, 'Transfer')
         .withArgs(ethers.ZeroAddress, this.initialHolder.address, initialSupply);
-
       await expect(tx).to.changeTokenBalances(
         this.underlying,
         [this.initialHolder, this.token],
@@ -79,6 +77,7 @@ describe('ERC20Wrapper', function () {
 
     it('reverts when inssuficient balance', async function () {
       await this.underlying.connect(this.initialHolder).approve(this.token, ethers.MaxUint256);
+
       await expect(this.token.connect(this.initialHolder).depositFor(this.initialHolder, ethers.MaxUint256))
         .to.be.revertedWithCustomError(this.underlying, 'ERC20InsufficientBalance')
         .withArgs(this.initialHolder.address, initialSupply, ethers.MaxUint256);
@@ -86,13 +85,13 @@ describe('ERC20Wrapper', function () {
 
     it('deposits to other account', async function () {
       await this.underlying.connect(this.initialHolder).approve(this.token, initialSupply);
+
       const tx = await this.token.connect(this.initialHolder).depositFor(this.recipient, initialSupply);
       await expect(tx)
         .to.emit(this.underlying, 'Transfer')
         .withArgs(this.initialHolder.address, this.token.target, initialSupply)
         .to.emit(this.token, 'Transfer')
         .withArgs(ethers.ZeroAddress, this.recipient.address, initialSupply);
-
       await expect(tx).to.changeTokenBalances(
         this.underlying,
         [this.initialHolder, this.token],
@@ -103,6 +102,7 @@ describe('ERC20Wrapper', function () {
 
     it('reverts minting to the wrapper contract', async function () {
       await this.underlying.connect(this.initialHolder).approve(this.token, ethers.MaxUint256);
+
       await expect(this.token.connect(this.initialHolder).depositFor(this.token, ethers.MaxUint256))
         .to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver')
         .withArgs(this.token.target);
@@ -130,7 +130,6 @@ describe('ERC20Wrapper', function () {
         .withArgs(this.token.target, this.initialHolder.address, value)
         .to.emit(this.token, 'Transfer')
         .withArgs(this.initialHolder.address, ethers.ZeroAddress, value);
-
       await expect(tx).to.changeTokenBalances(this.underlying, [this.token, this.initialHolder], [-value, value]);
       await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, -value);
     });
@@ -142,7 +141,6 @@ describe('ERC20Wrapper', function () {
         .withArgs(this.token.target, this.initialHolder.address, initialSupply)
         .to.emit(this.token, 'Transfer')
         .withArgs(this.initialHolder.address, ethers.ZeroAddress, initialSupply);
-
       await expect(tx).to.changeTokenBalances(
         this.underlying,
         [this.token, this.initialHolder],
@@ -158,7 +156,6 @@ describe('ERC20Wrapper', function () {
         .withArgs(this.token.target, this.recipient.address, initialSupply)
         .to.emit(this.token, 'Transfer')
         .withArgs(this.initialHolder.address, ethers.ZeroAddress, initialSupply);
-
       await expect(tx).to.changeTokenBalances(
         this.underlying,
         [this.token, this.initialHolder, this.recipient],
@@ -181,7 +178,6 @@ describe('ERC20Wrapper', function () {
 
       const tx = await this.token.$_recover(this.recipient);
       await expect(tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, this.recipient.address, 0n);
-
       await expect(tx).to.changeTokenBalance(this.token, this.recipient, 0);
     });
 
@@ -192,7 +188,6 @@ describe('ERC20Wrapper', function () {
       await expect(tx)
         .to.emit(this.token, 'Transfer')
         .withArgs(ethers.ZeroAddress, this.recipient.address, initialSupply);
-
       await expect(tx).to.changeTokenBalance(this.token, this.recipient, initialSupply);
     });
   });

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 463 - 599
test/token/ERC20/extensions/ERC4626.test.js


+ 5 - 4
test/utils/cryptography/EIP712.test.js

@@ -3,6 +3,7 @@ const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
 const { getDomain, domainSeparator, hashTypedData } = require('../../helpers/eip712');
+const { formatType } = require('../../helpers/eip712-types');
 const { getChainId } = require('../../helpers/chainid');
 
 const LENGTHS = {
@@ -77,10 +78,10 @@ describe('EIP712', function () {
 
       it('digest', async function () {
         const types = {
-          Mail: [
-            { name: 'to', type: 'address' },
-            { name: 'contents', type: 'string' },
-          ],
+          Mail: formatType({
+            to: 'address',
+            contents: 'string',
+          }),
         };
 
         const message = {

Vissa filer visades inte eftersom för många filer har ändrats