소스 검색

Migrate governance tests to ethers.js (#4728)

Co-authored-by: ernestognw <ernestognw@gmail.com>
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Renan Souza 1 년 전
부모
커밋
c3cd70811b

+ 2 - 18
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "openzeppelin-solidity",
-  "version": "5.0.0",
+  "version": "5.0.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "openzeppelin-solidity",
-      "version": "5.0.0",
+      "version": "5.0.1",
       "license": "MIT",
       "devDependencies": {
         "@changesets/changelog-github": "^0.5.0",
@@ -25,7 +25,6 @@
         "@openzeppelin/test-helpers": "^0.5.13",
         "@openzeppelin/upgrade-safe-transpiler": "^0.3.32",
         "@openzeppelin/upgrades-core": "^1.20.6",
-        "array.prototype.at": "^1.1.1",
         "chai": "^4.2.0",
         "eslint": "^8.30.0",
         "eslint-config-prettier": "^9.0.0",
@@ -4908,21 +4907,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/array.prototype.at": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/array.prototype.at/-/array.prototype.at-1.1.2.tgz",
-      "integrity": "sha512-TPj626jUZMc2Qbld8uXKZrXM/lSStx2KfbIyF70Ui9RgdgibpTWC6WGCuff6qQ7xYzqXtir60WAHrfmknkF3Vw==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "es-shim-unscopables": "^1.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/array.prototype.findlast": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.3.tgz",

+ 0 - 1
package.json

@@ -65,7 +65,6 @@
     "@openzeppelin/test-helpers": "^0.5.13",
     "@openzeppelin/upgrade-safe-transpiler": "^0.3.32",
     "@openzeppelin/upgrades-core": "^1.20.6",
-    "array.prototype.at": "^1.1.1",
     "chai": "^4.2.0",
     "eslint": "^8.30.0",
     "eslint-config-prettier": "^9.0.0",

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 362 - 396
test/governance/Governor.test.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 570 - 525
test/governance/TimelockController.test.js


+ 98 - 83
test/governance/extensions/GovernorERC721.test.js

@@ -1,59 +1,77 @@
-const { expectEvent } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const Enums = require('../../helpers/enums');
 const { GovernorHelper } = require('../../helpers/governance');
-
-const Governor = artifacts.require('$GovernorVoteMocks');
-const CallReceiver = artifacts.require('CallReceiverMock');
+const { bigint: Enums } = require('../../helpers/enums');
 
 const TOKENS = [
-  { Token: artifacts.require('$ERC721Votes'), mode: 'blocknumber' },
-  { Token: artifacts.require('$ERC721VotesTimestampMock'), mode: 'timestamp' },
+  { Token: '$ERC721Votes', mode: 'blocknumber' },
+  { Token: '$ERC721VotesTimestampMock', mode: 'timestamp' },
 ];
 
-contract('GovernorERC721', function (accounts) {
-  const [owner, voter1, voter2, voter3, voter4] = accounts;
-
-  const name = 'OZ-Governor';
-  const version = '1';
-  const tokenName = 'MockNFToken';
-  const tokenSymbol = 'MTKN';
-  const NFT0 = web3.utils.toBN(0);
-  const NFT1 = web3.utils.toBN(1);
-  const NFT2 = web3.utils.toBN(2);
-  const NFT3 = web3.utils.toBN(3);
-  const NFT4 = web3.utils.toBN(4);
-  const votingDelay = web3.utils.toBN(4);
-  const votingPeriod = web3.utils.toBN(16);
-  const value = web3.utils.toWei('1');
-
-  for (const { mode, Token } of TOKENS) {
-    describe(`using ${Token._json.contractName}`, function () {
+const name = 'OZ-Governor';
+const version = '1';
+const tokenName = 'MockNFToken';
+const tokenSymbol = 'MTKN';
+const NFT0 = 0n;
+const NFT1 = 1n;
+const NFT2 = 2n;
+const NFT3 = 3n;
+const NFT4 = 4n;
+const votingDelay = 4n;
+const votingPeriod = 16n;
+const value = ethers.parseEther('1');
+
+describe('GovernorERC721', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      const [owner, voter1, voter2, voter3, voter4] = await ethers.getSigners();
+      const receiver = await ethers.deployContract('CallReceiverMock');
+
+      const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
+      const mock = await ethers.deployContract('$GovernorMock', [
+        name, // name
+        votingDelay, // initialVotingDelay
+        votingPeriod, // initialVotingPeriod
+        0n, // initialProposalThreshold
+        token, // tokenAddress
+        10n, // quorumNumeratorValue
+      ]);
+
+      await owner.sendTransaction({ to: mock, value });
+      await Promise.all([NFT0, NFT1, NFT2, NFT3, NFT4].map(tokenId => token.$_mint(owner, tokenId)));
+
+      const helper = new GovernorHelper(mock, mode);
+      await helper.connect(owner).delegate({ token, to: voter1, tokenId: NFT0 });
+      await helper.connect(owner).delegate({ token, to: voter2, tokenId: NFT1 });
+      await helper.connect(owner).delegate({ token, to: voter2, tokenId: NFT2 });
+      await helper.connect(owner).delegate({ token, to: voter3, tokenId: NFT3 });
+      await helper.connect(owner).delegate({ token, to: voter4, tokenId: NFT4 });
+
+      return {
+        owner,
+        voter1,
+        voter2,
+        voter3,
+        voter4,
+        receiver,
+        token,
+        mock,
+        helper,
+      };
+    };
+
+    describe(`using ${Token}`, function () {
       beforeEach(async function () {
-        this.owner = owner;
-        this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
-        this.mock = await Governor.new(name, this.token.address);
-        this.receiver = await CallReceiver.new();
-
-        this.helper = new GovernorHelper(this.mock, mode);
-
-        await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value });
-
-        await Promise.all([NFT0, NFT1, NFT2, NFT3, NFT4].map(tokenId => this.token.$_mint(owner, tokenId)));
-        await this.helper.delegate({ token: this.token, to: voter1, tokenId: NFT0 }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, tokenId: NFT1 }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, tokenId: NFT2 }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter3, tokenId: NFT3 }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter4, tokenId: NFT4 }, { from: owner });
-
-        // default proposal
+        Object.assign(this, await loadFixture(fixture));
+        // initiate fresh proposal
         this.proposal = this.helper.setProposal(
           [
             {
-              target: this.receiver.address,
+              target: this.receiver.target,
+              data: this.receiver.interface.encodeFunctionData('mockFunction'),
               value,
-              data: this.receiver.contract.methods.mockFunction().encodeABI(),
             },
           ],
           '<proposal description>',
@@ -61,55 +79,52 @@ contract('GovernorERC721', function (accounts) {
       });
 
       it('deployment check', async function () {
-        expect(await this.mock.name()).to.be.equal(name);
-        expect(await this.mock.token()).to.be.equal(this.token.address);
-        expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
-        expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
-        expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
+        expect(await this.mock.name()).to.equal(name);
+        expect(await this.mock.token()).to.equal(this.token.target);
+        expect(await this.mock.votingDelay()).to.equal(votingDelay);
+        expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
+        expect(await this.mock.quorum(0n)).to.equal(0n);
+
+        expect(await this.token.getVotes(this.voter1)).to.equal(1n); // NFT0
+        expect(await this.token.getVotes(this.voter2)).to.equal(2n); // NFT1 & NFT2
+        expect(await this.token.getVotes(this.voter3)).to.equal(1n); // NFT3
+        expect(await this.token.getVotes(this.voter4)).to.equal(1n); // NFT4
       });
 
       it('voting with ERC721 token', async function () {
         await this.helper.propose();
         await this.helper.waitForSnapshot();
 
-        expectEvent(await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), 'VoteCast', {
-          voter: voter1,
-          support: Enums.VoteType.For,
-          weight: '1',
-        });
-
-        expectEvent(await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }), 'VoteCast', {
-          voter: voter2,
-          support: Enums.VoteType.For,
-          weight: '2',
-        });
-
-        expectEvent(await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }), 'VoteCast', {
-          voter: voter3,
-          support: Enums.VoteType.Against,
-          weight: '1',
-        });
-
-        expectEvent(await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }), 'VoteCast', {
-          voter: voter4,
-          support: Enums.VoteType.Abstain,
-          weight: '1',
-        });
+        await expect(this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For }))
+          .to.emit(this.mock, 'VoteCast')
+          .withArgs(this.voter1.address, this.proposal.id, Enums.VoteType.For, 1n, '');
+
+        await expect(this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For }))
+          .to.emit(this.mock, 'VoteCast')
+          .withArgs(this.voter2.address, this.proposal.id, Enums.VoteType.For, 2n, '');
+
+        await expect(this.helper.connect(this.voter3).vote({ support: Enums.VoteType.Against }))
+          .to.emit(this.mock, 'VoteCast')
+          .withArgs(this.voter3.address, this.proposal.id, Enums.VoteType.Against, 1n, '');
+
+        await expect(this.helper.connect(this.voter4).vote({ support: Enums.VoteType.Abstain }))
+          .to.emit(this.mock, 'VoteCast')
+          .withArgs(this.voter4.address, this.proposal.id, Enums.VoteType.Abstain, 1n, '');
 
         await this.helper.waitForDeadline();
         await this.helper.execute();
 
-        expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
-        expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true);
-        expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true);
-        expect(await this.mock.hasVoted(this.proposal.id, voter3)).to.be.equal(true);
-        expect(await this.mock.hasVoted(this.proposal.id, voter4)).to.be.equal(true);
-
-        await this.mock.proposalVotes(this.proposal.id).then(results => {
-          expect(results.forVotes).to.be.bignumber.equal('3');
-          expect(results.againstVotes).to.be.bignumber.equal('1');
-          expect(results.abstainVotes).to.be.bignumber.equal('1');
-        });
+        expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter3)).to.be.true;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter4)).to.be.true;
+
+        expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([
+          1n, // againstVotes
+          3n, // forVotes
+          1n, // abstainVotes
+        ]);
       });
     });
   }

+ 118 - 128
test/governance/extensions/GovernorPreventLateQuorum.test.js

@@ -1,66 +1,66 @@
-const { expectEvent } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const Enums = require('../../helpers/enums');
 const { GovernorHelper } = require('../../helpers/governance');
-const { clockFromReceipt } = require('../../helpers/time');
-const { expectRevertCustomError } = require('../../helpers/customError');
-
-const Governor = artifacts.require('$GovernorPreventLateQuorumMock');
-const CallReceiver = artifacts.require('CallReceiverMock');
+const { bigint: Enums } = require('../../helpers/enums');
+const { bigint: time } = require('../../helpers/time');
 
 const TOKENS = [
-  { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
-  { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' },
+  { Token: '$ERC20Votes', mode: 'blocknumber' },
+  { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
 ];
 
-contract('GovernorPreventLateQuorum', function (accounts) {
-  const [owner, proposer, voter1, voter2, voter3, voter4] = accounts;
-
-  const name = 'OZ-Governor';
-  const version = '1';
-  const tokenName = 'MockToken';
-  const tokenSymbol = 'MTKN';
-  const tokenSupply = web3.utils.toWei('100');
-  const votingDelay = web3.utils.toBN(4);
-  const votingPeriod = web3.utils.toBN(16);
-  const lateQuorumVoteExtension = web3.utils.toBN(8);
-  const quorum = web3.utils.toWei('1');
-  const value = web3.utils.toWei('1');
-
-  for (const { mode, Token } of TOKENS) {
-    describe(`using ${Token._json.contractName}`, function () {
+const name = 'OZ-Governor';
+const version = '1';
+const tokenName = 'MockToken';
+const tokenSymbol = 'MTKN';
+const tokenSupply = ethers.parseEther('100');
+const votingDelay = 4n;
+const votingPeriod = 16n;
+const lateQuorumVoteExtension = 8n;
+const quorum = ethers.parseEther('1');
+const value = ethers.parseEther('1');
+
+describe('GovernorPreventLateQuorum', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      const [owner, proposer, voter1, voter2, voter3, voter4] = await ethers.getSigners();
+      const receiver = await ethers.deployContract('CallReceiverMock');
+
+      const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
+      const mock = await ethers.deployContract('$GovernorPreventLateQuorumMock', [
+        name, // name
+        votingDelay, // initialVotingDelay
+        votingPeriod, // initialVotingPeriod
+        0n, // initialProposalThreshold
+        token, // tokenAddress
+        lateQuorumVoteExtension,
+        quorum,
+      ]);
+
+      await owner.sendTransaction({ to: mock, value });
+      await token.$_mint(owner, tokenSupply);
+
+      const helper = new GovernorHelper(mock, mode);
+      await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
+      await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
+      await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
+      await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
+
+      return { owner, proposer, voter1, voter2, voter3, voter4, receiver, token, mock, helper };
+    };
+
+    describe(`using ${Token}`, function () {
       beforeEach(async function () {
-        this.owner = owner;
-        this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
-        this.mock = await Governor.new(
-          name,
-          votingDelay,
-          votingPeriod,
-          0,
-          this.token.address,
-          lateQuorumVoteExtension,
-          quorum,
-        );
-        this.receiver = await CallReceiver.new();
-
-        this.helper = new GovernorHelper(this.mock, mode);
-
-        await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value });
-
-        await this.token.$_mint(owner, tokenSupply);
-        await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
-
-        // default proposal
+        Object.assign(this, await loadFixture(fixture));
+        // initiate fresh proposal
         this.proposal = this.helper.setProposal(
           [
             {
-              target: this.receiver.address,
+              target: this.receiver.target,
+              data: this.receiver.interface.encodeFunctionData('mockFunction'),
               value,
-              data: this.receiver.contract.methods.mockFunction().encodeABI(),
             },
           ],
           '<proposal description>',
@@ -68,110 +68,101 @@ contract('GovernorPreventLateQuorum', function (accounts) {
       });
 
       it('deployment check', async function () {
-        expect(await this.mock.name()).to.be.equal(name);
-        expect(await this.mock.token()).to.be.equal(this.token.address);
-        expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
-        expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
-        expect(await this.mock.quorum(0)).to.be.bignumber.equal(quorum);
-        expect(await this.mock.lateQuorumVoteExtension()).to.be.bignumber.equal(lateQuorumVoteExtension);
+        expect(await this.mock.name()).to.equal(name);
+        expect(await this.mock.token()).to.equal(this.token.target);
+        expect(await this.mock.votingDelay()).to.equal(votingDelay);
+        expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
+        expect(await this.mock.quorum(0)).to.equal(quorum);
+        expect(await this.mock.lateQuorumVoteExtension()).to.equal(lateQuorumVoteExtension);
       });
 
       it('nominal workflow unaffected', async function () {
-        const txPropose = await this.helper.propose({ from: proposer });
+        const txPropose = await this.helper.connect(this.proposer).propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
-        await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 });
-        await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter3).vote({ support: Enums.VoteType.Against });
+        await this.helper.connect(this.voter4).vote({ support: Enums.VoteType.Abstain });
         await this.helper.waitForDeadline();
         await this.helper.execute();
 
-        expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
-        expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true);
-        expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true);
-        expect(await this.mock.hasVoted(this.proposal.id, voter3)).to.be.equal(true);
-        expect(await this.mock.hasVoted(this.proposal.id, voter4)).to.be.equal(true);
-
-        await this.mock.proposalVotes(this.proposal.id).then(results => {
-          expect(results.forVotes).to.be.bignumber.equal(web3.utils.toWei('17'));
-          expect(results.againstVotes).to.be.bignumber.equal(web3.utils.toWei('5'));
-          expect(results.abstainVotes).to.be.bignumber.equal(web3.utils.toWei('2'));
-        });
-
-        const voteStart = web3.utils.toBN(await clockFromReceipt[mode](txPropose.receipt)).add(votingDelay);
-        const voteEnd = web3.utils
-          .toBN(await clockFromReceipt[mode](txPropose.receipt))
-          .add(votingDelay)
-          .add(votingPeriod);
-        expect(await this.mock.proposalSnapshot(this.proposal.id)).to.be.bignumber.equal(voteStart);
-        expect(await this.mock.proposalDeadline(this.proposal.id)).to.be.bignumber.equal(voteEnd);
-
-        expectEvent(txPropose, 'ProposalCreated', {
-          proposalId: this.proposal.id,
-          proposer,
-          targets: this.proposal.targets,
-          // values: this.proposal.values.map(value => web3.utils.toBN(value)),
-          signatures: this.proposal.signatures,
-          calldatas: this.proposal.data,
-          voteStart,
-          voteEnd,
-          description: this.proposal.description,
-        });
+        expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter3)).to.be.true;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter4)).to.be.true;
+
+        expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([
+          ethers.parseEther('5'), // againstVotes
+          ethers.parseEther('17'), // forVotes
+          ethers.parseEther('2'), // abstainVotes
+        ]);
+
+        const voteStart = (await time.clockFromReceipt[mode](txPropose)) + votingDelay;
+        const voteEnd = (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod;
+        expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(voteStart);
+        expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(voteEnd);
+
+        await expect(txPropose)
+          .to.emit(this.mock, 'ProposalCreated')
+          .withArgs(
+            this.proposal.id,
+            this.proposer.address,
+            this.proposal.targets,
+            this.proposal.values,
+            this.proposal.signatures,
+            this.proposal.data,
+            voteStart,
+            voteEnd,
+            this.proposal.description,
+          );
       });
 
       it('Delay is extended to prevent last minute take-over', async function () {
-        const txPropose = await this.helper.propose({ from: proposer });
+        const txPropose = await this.helper.connect(this.proposer).propose();
 
         // compute original schedule
-        const startBlock = web3.utils.toBN(await clockFromReceipt[mode](txPropose.receipt)).add(votingDelay);
-        const endBlock = web3.utils
-          .toBN(await clockFromReceipt[mode](txPropose.receipt))
-          .add(votingDelay)
-          .add(votingPeriod);
-        expect(await this.mock.proposalSnapshot(this.proposal.id)).to.be.bignumber.equal(startBlock);
-        expect(await this.mock.proposalDeadline(this.proposal.id)).to.be.bignumber.equal(endBlock);
-
+        const snapshotTimepoint = (await time.clockFromReceipt[mode](txPropose)) + votingDelay;
+        const deadlineTimepoint = (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod;
+        expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(snapshotTimepoint);
+        expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(deadlineTimepoint);
         // wait for the last minute to vote
-        await this.helper.waitForDeadline(-1);
-        const txVote = await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
+        await this.helper.waitForDeadline(-1n);
+        const txVote = await this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For });
 
         // cannot execute yet
-        expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
+        expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Active);
 
         // compute new extended schedule
-        const extendedDeadline = web3.utils
-          .toBN(await clockFromReceipt[mode](txVote.receipt))
-          .add(lateQuorumVoteExtension);
-        expect(await this.mock.proposalSnapshot(this.proposal.id)).to.be.bignumber.equal(startBlock);
-        expect(await this.mock.proposalDeadline(this.proposal.id)).to.be.bignumber.equal(extendedDeadline);
+        const extendedDeadline = (await time.clockFromReceipt[mode](txVote)) + lateQuorumVoteExtension;
+        expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(snapshotTimepoint);
+        expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(extendedDeadline);
 
         // still possible to vote
-        await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter1 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.Against });
 
         await this.helper.waitForDeadline();
-        expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
-        await this.helper.waitForDeadline(+1);
-        expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Defeated);
+        expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Active);
+        await this.helper.waitForDeadline(1n);
+        expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Defeated);
 
         // check extension event
-        expectEvent(txVote, 'ProposalExtended', { proposalId: this.proposal.id, extendedDeadline });
+        await expect(txVote).to.emit(this.mock, 'ProposalExtended').withArgs(this.proposal.id, extendedDeadline);
       });
 
       describe('onlyGovernance updates', function () {
         it('setLateQuorumVoteExtension is protected', async function () {
-          await expectRevertCustomError(
-            this.mock.setLateQuorumVoteExtension(0, { from: owner }),
-            'GovernorOnlyExecutor',
-            [owner],
-          );
+          await expect(this.mock.connect(this.owner).setLateQuorumVoteExtension(0n))
+            .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
+            .withArgs(this.owner.address);
         });
 
         it('can setLateQuorumVoteExtension through governance', async function () {
           this.helper.setProposal(
             [
               {
-                target: this.mock.address,
-                data: this.mock.contract.methods.setLateQuorumVoteExtension('0').encodeABI(),
+                target: this.mock.target,
+                data: this.mock.interface.encodeFunctionData('setLateQuorumVoteExtension', [0n]),
               },
             ],
             '<proposal description>',
@@ -179,15 +170,14 @@ contract('GovernorPreventLateQuorum', function (accounts) {
 
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
 
-          expectEvent(await this.helper.execute(), 'LateQuorumVoteExtensionSet', {
-            oldVoteExtension: lateQuorumVoteExtension,
-            newVoteExtension: '0',
-          });
+          await expect(this.helper.execute())
+            .to.emit(this.mock, 'LateQuorumVoteExtensionSet')
+            .withArgs(lateQuorumVoteExtension, 0n);
 
-          expect(await this.mock.lateQuorumVoteExtension()).to.be.bignumber.equal('0');
+          expect(await this.mock.lateQuorumVoteExtension()).to.equal(0n);
         });
       });
     });

+ 107 - 102
test/governance/extensions/GovernorStorage.test.js

@@ -1,149 +1,154 @@
-const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');
+const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
 
-const { expectRevertCustomError } = require('../../helpers/customError');
-const Enums = require('../../helpers/enums');
 const { GovernorHelper, timelockSalt } = require('../../helpers/governance');
-
-const Timelock = artifacts.require('TimelockController');
-const Governor = artifacts.require('$GovernorStorageMock');
-const CallReceiver = artifacts.require('CallReceiverMock');
+const { bigint: Enums } = require('../../helpers/enums');
 
 const TOKENS = [
-  { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
-  { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' },
+  { Token: '$ERC20Votes', mode: 'blocknumber' },
+  { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
 ];
 
-contract('GovernorStorage', function (accounts) {
-  const [owner, voter1, voter2, voter3, voter4] = accounts;
-
-  const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
-  const PROPOSER_ROLE = web3.utils.soliditySha3('PROPOSER_ROLE');
-  const EXECUTOR_ROLE = web3.utils.soliditySha3('EXECUTOR_ROLE');
-  const CANCELLER_ROLE = web3.utils.soliditySha3('CANCELLER_ROLE');
-
-  const name = 'OZ-Governor';
-  const version = '1';
-  const tokenName = 'MockToken';
-  const tokenSymbol = 'MTKN';
-  const tokenSupply = web3.utils.toWei('100');
-  const votingDelay = web3.utils.toBN(4);
-  const votingPeriod = web3.utils.toBN(16);
-  const value = web3.utils.toWei('1');
-
-  for (const { mode, Token } of TOKENS) {
-    describe(`using ${Token._json.contractName}`, function () {
+const DEFAULT_ADMIN_ROLE = ethers.ZeroHash;
+const PROPOSER_ROLE = ethers.id('PROPOSER_ROLE');
+const EXECUTOR_ROLE = ethers.id('EXECUTOR_ROLE');
+const CANCELLER_ROLE = ethers.id('CANCELLER_ROLE');
+
+const name = 'OZ-Governor';
+const version = '1';
+const tokenName = 'MockToken';
+const tokenSymbol = 'MTKN';
+const tokenSupply = ethers.parseEther('100');
+const votingDelay = 4n;
+const votingPeriod = 16n;
+const value = ethers.parseEther('1');
+const delay = 3600n;
+
+describe('GovernorStorage', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      const [deployer, owner, proposer, voter1, voter2, voter3, voter4] = await ethers.getSigners();
+      const receiver = await ethers.deployContract('CallReceiverMock');
+
+      const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
+      const timelock = await ethers.deployContract('TimelockController', [delay, [], [], deployer]);
+      const mock = await ethers.deployContract('$GovernorStorageMock', [
+        name,
+        votingDelay,
+        votingPeriod,
+        0n,
+        timelock,
+        token,
+        0n,
+      ]);
+
+      await owner.sendTransaction({ to: timelock, value });
+      await token.$_mint(owner, tokenSupply);
+      await timelock.grantRole(PROPOSER_ROLE, mock);
+      await timelock.grantRole(PROPOSER_ROLE, owner);
+      await timelock.grantRole(CANCELLER_ROLE, mock);
+      await timelock.grantRole(CANCELLER_ROLE, owner);
+      await timelock.grantRole(EXECUTOR_ROLE, ethers.ZeroAddress);
+      await timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer);
+
+      const helper = new GovernorHelper(mock, mode);
+      await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
+      await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
+      await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
+      await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
+
+      return { deployer, owner, proposer, voter1, voter2, voter3, voter4, receiver, token, timelock, mock, helper };
+    };
+
+    describe(`using ${Token}`, function () {
       beforeEach(async function () {
-        const [deployer] = await web3.eth.getAccounts();
-
-        this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
-        this.timelock = await Timelock.new(3600, [], [], deployer);
-        this.mock = await Governor.new(
-          name,
-          votingDelay,
-          votingPeriod,
-          0,
-          this.timelock.address,
-          this.token.address,
-          0,
-        );
-        this.receiver = await CallReceiver.new();
-
-        this.helper = new GovernorHelper(this.mock, mode);
-
-        await web3.eth.sendTransaction({ from: owner, to: this.timelock.address, value });
-
-        // normal setup: governor is proposer, everyone is executor, timelock is its own admin
-        await this.timelock.grantRole(PROPOSER_ROLE, this.mock.address);
-        await this.timelock.grantRole(PROPOSER_ROLE, owner);
-        await this.timelock.grantRole(CANCELLER_ROLE, this.mock.address);
-        await this.timelock.grantRole(CANCELLER_ROLE, owner);
-        await this.timelock.grantRole(EXECUTOR_ROLE, constants.ZERO_ADDRESS);
-        await this.timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer);
-
-        await this.token.$_mint(owner, tokenSupply);
-        await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
-
-        // default proposal
+        Object.assign(this, await loadFixture(fixture));
+        // initiate fresh proposal
         this.proposal = this.helper.setProposal(
           [
             {
-              target: this.receiver.address,
+              target: this.receiver.target,
+              data: this.receiver.interface.encodeFunctionData('mockFunction'),
               value,
-              data: this.receiver.contract.methods.mockFunction().encodeABI(),
             },
           ],
           '<proposal description>',
         );
         this.proposal.timelockid = await this.timelock.hashOperationBatch(
           ...this.proposal.shortProposal.slice(0, 3),
-          '0x0',
-          timelockSalt(this.mock.address, this.proposal.shortProposal[3]),
+          ethers.ZeroHash,
+          timelockSalt(this.mock.target, this.proposal.shortProposal[3]),
         );
       });
 
       describe('proposal indexing', function () {
         it('before propose', async function () {
-          expect(await this.mock.proposalCount()).to.be.bignumber.equal('0');
+          expect(await this.mock.proposalCount()).to.equal(0n);
 
-          // panic code 0x32 (out-of-bound)
-          await expectRevert.unspecified(this.mock.proposalDetailsAt(0));
+          await expect(this.mock.proposalDetailsAt(0n)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
 
-          await expectRevertCustomError(this.mock.proposalDetails(this.proposal.id), 'GovernorNonexistentProposal', [
-            this.proposal.id,
-          ]);
+          await expect(this.mock.proposalDetails(this.proposal.id))
+            .to.be.revertedWithCustomError(this.mock, 'GovernorNonexistentProposal')
+            .withArgs(this.proposal.id);
         });
 
         it('after propose', async function () {
           await this.helper.propose();
 
-          expect(await this.mock.proposalCount()).to.be.bignumber.equal('1');
+          expect(await this.mock.proposalCount()).to.equal(1n);
 
-          const proposalDetailsAt0 = await this.mock.proposalDetailsAt(0);
-          expect(proposalDetailsAt0[0]).to.be.bignumber.equal(this.proposal.id);
-          expect(proposalDetailsAt0[1]).to.be.deep.equal(this.proposal.targets);
-          expect(proposalDetailsAt0[2].map(x => x.toString())).to.be.deep.equal(this.proposal.values);
-          expect(proposalDetailsAt0[3]).to.be.deep.equal(this.proposal.fulldata);
-          expect(proposalDetailsAt0[4]).to.be.equal(this.proposal.descriptionHash);
+          expect(await this.mock.proposalDetailsAt(0n)).to.deep.equal([
+            this.proposal.id,
+            this.proposal.targets,
+            this.proposal.values,
+            this.proposal.data,
+            this.proposal.descriptionHash,
+          ]);
 
-          const proposalDetailsForId = await this.mock.proposalDetails(this.proposal.id);
-          expect(proposalDetailsForId[0]).to.be.deep.equal(this.proposal.targets);
-          expect(proposalDetailsForId[1].map(x => x.toString())).to.be.deep.equal(this.proposal.values);
-          expect(proposalDetailsForId[2]).to.be.deep.equal(this.proposal.fulldata);
-          expect(proposalDetailsForId[3]).to.be.equal(this.proposal.descriptionHash);
+          expect(await this.mock.proposalDetails(this.proposal.id)).to.deep.equal([
+            this.proposal.targets,
+            this.proposal.values,
+            this.proposal.data,
+            this.proposal.descriptionHash,
+          ]);
         });
       });
 
       it('queue and execute by id', async function () {
         await this.helper.propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
-        await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 });
-        await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter3).vote({ support: Enums.VoteType.Against });
+        await this.helper.connect(this.voter4).vote({ support: Enums.VoteType.Abstain });
         await this.helper.waitForDeadline();
-        const txQueue = await this.mock.queue(this.proposal.id);
-        await this.helper.waitForEta();
-        const txExecute = await this.mock.execute(this.proposal.id);
 
-        expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id });
-        await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallScheduled', { id: this.proposal.timelockid });
-        await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallSalt', {
-          id: this.proposal.timelockid,
-        });
+        await expect(this.mock.queue(this.proposal.id))
+          .to.emit(this.mock, 'ProposalQueued')
+          .withArgs(this.proposal.id, anyValue)
+          .to.emit(this.timelock, 'CallScheduled')
+          .withArgs(this.proposal.timelockid, ...Array(6).fill(anyValue))
+          .to.emit(this.timelock, 'CallSalt')
+          .withArgs(this.proposal.timelockid, anyValue);
+
+        await this.helper.waitForEta();
 
-        expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id });
-        await expectEvent.inTransaction(txExecute.tx, this.timelock, 'CallExecuted', { id: this.proposal.timelockid });
-        await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled');
+        await expect(this.mock.execute(this.proposal.id))
+          .to.emit(this.mock, 'ProposalExecuted')
+          .withArgs(this.proposal.id)
+          .to.emit(this.timelock, 'CallExecuted')
+          .withArgs(this.proposal.timelockid, ...Array(4).fill(anyValue))
+          .to.emit(this.receiver, 'MockFunctionCalled');
       });
 
       it('cancel by id', async function () {
-        await this.helper.propose();
-        const txCancel = await this.mock.cancel(this.proposal.id);
-        expectEvent(txCancel, 'ProposalCanceled', { proposalId: this.proposal.id });
+        await this.helper.connect(this.proposer).propose();
+        await expect(this.mock.connect(this.proposer).cancel(this.proposal.id))
+          .to.emit(this.mock, 'ProposalCanceled')
+          .withArgs(this.proposal.id);
       });
     });
   }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 338 - 359
test/governance/extensions/GovernorTimelockAccess.test.js


+ 227 - 220
test/governance/extensions/GovernorTimelockCompound.test.js

@@ -1,77 +1,71 @@
-const { ethers } = require('ethers');
-const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');
 
-const Enums = require('../../helpers/enums');
-const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance');
-const { expectRevertCustomError } = require('../../helpers/customError');
-const { clockFromReceipt } = require('../../helpers/time');
-
-const Timelock = artifacts.require('CompTimelock');
-const Governor = artifacts.require('$GovernorTimelockCompoundMock');
-const CallReceiver = artifacts.require('CallReceiverMock');
-const ERC721 = artifacts.require('$ERC721');
-const ERC1155 = artifacts.require('$ERC1155');
+const { GovernorHelper } = require('../../helpers/governance');
+const { bigint: Enums } = require('../../helpers/enums');
+const { bigint: time } = require('../../helpers/time');
 
 const TOKENS = [
-  { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
-  { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' },
+  { Token: '$ERC20Votes', mode: 'blocknumber' },
+  { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
 ];
 
-contract('GovernorTimelockCompound', function (accounts) {
-  const [owner, voter1, voter2, voter3, voter4, other] = accounts;
-
-  const name = 'OZ-Governor';
-  const version = '1';
-  const tokenName = 'MockToken';
-  const tokenSymbol = 'MTKN';
-  const tokenSupply = web3.utils.toWei('100');
-  const votingDelay = web3.utils.toBN(4);
-  const votingPeriod = web3.utils.toBN(16);
-  const value = web3.utils.toWei('1');
-
-  const defaultDelay = 2 * 86400;
-
-  for (const { mode, Token } of TOKENS) {
-    describe(`using ${Token._json.contractName}`, function () {
+const name = 'OZ-Governor';
+const version = '1';
+const tokenName = 'MockToken';
+const tokenSymbol = 'MTKN';
+const tokenSupply = ethers.parseEther('100');
+const votingDelay = 4n;
+const votingPeriod = 16n;
+const value = ethers.parseEther('1');
+const defaultDelay = time.duration.days(2n);
+
+describe('GovernorTimelockCompound', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      const [deployer, owner, voter1, voter2, voter3, voter4, other] = await ethers.getSigners();
+      const receiver = await ethers.deployContract('CallReceiverMock');
+
+      const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
+      const predictGovernor = await deployer
+        .getNonce()
+        .then(nonce => ethers.getCreateAddress({ from: deployer.address, nonce: nonce + 1 }));
+      const timelock = await ethers.deployContract('CompTimelock', [predictGovernor, defaultDelay]);
+      const mock = await ethers.deployContract('$GovernorTimelockCompoundMock', [
+        name,
+        votingDelay,
+        votingPeriod,
+        0n,
+        timelock,
+        token,
+        0n,
+      ]);
+
+      await owner.sendTransaction({ to: timelock, value });
+      await token.$_mint(owner, tokenSupply);
+
+      const helper = new GovernorHelper(mock, mode);
+      await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
+      await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
+      await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
+      await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
+
+      return { deployer, owner, voter1, voter2, voter3, voter4, other, receiver, token, mock, timelock, helper };
+    };
+
+    describe(`using ${Token}`, function () {
       beforeEach(async function () {
-        const [deployer] = await web3.eth.getAccounts();
-
-        this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
-
-        // Need to predict governance address to set it as timelock admin with a delayed transfer
-        const nonce = await web3.eth.getTransactionCount(deployer);
-        const predictGovernor = ethers.getCreateAddress({ from: deployer, nonce: nonce + 1 });
-
-        this.timelock = await Timelock.new(predictGovernor, defaultDelay);
-        this.mock = await Governor.new(
-          name,
-          votingDelay,
-          votingPeriod,
-          0,
-          this.timelock.address,
-          this.token.address,
-          0,
-        );
-        this.receiver = await CallReceiver.new();
-
-        this.helper = new GovernorHelper(this.mock, mode);
-
-        await web3.eth.sendTransaction({ from: owner, to: this.timelock.address, value });
-
-        await this.token.$_mint(owner, tokenSupply);
-        await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
+        Object.assign(this, await loadFixture(fixture));
 
         // default proposal
         this.proposal = this.helper.setProposal(
           [
             {
-              target: this.receiver.address,
+              target: this.receiver.target,
               value,
-              data: this.receiver.contract.methods.mockFunction().encodeABI(),
+              data: this.receiver.interface.encodeFunctionData('mockFunction'),
             },
           ],
           '<proposal description>',
@@ -79,46 +73,55 @@ contract('GovernorTimelockCompound', function (accounts) {
       });
 
       it("doesn't accept ether transfers", async function () {
-        await expectRevert.unspecified(web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: 1 }));
+        await expect(this.owner.sendTransaction({ to: this.mock, value: 1n })).to.be.revertedWithCustomError(
+          this.mock,
+          'GovernorDisabledDeposit',
+        );
       });
 
       it('post deployment check', async function () {
-        expect(await this.mock.name()).to.be.equal(name);
-        expect(await this.mock.token()).to.be.equal(this.token.address);
-        expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
-        expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
-        expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
-
-        expect(await this.mock.timelock()).to.be.equal(this.timelock.address);
-        expect(await this.timelock.admin()).to.be.equal(this.mock.address);
+        expect(await this.mock.name()).to.equal(name);
+        expect(await this.mock.token()).to.equal(this.token.target);
+        expect(await this.mock.votingDelay()).to.equal(votingDelay);
+        expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
+        expect(await this.mock.quorum(0n)).to.equal(0n);
+
+        expect(await this.mock.timelock()).to.equal(this.timelock.target);
+        expect(await this.timelock.admin()).to.equal(this.mock.target);
       });
 
       it('nominal', async function () {
-        expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal('0');
-        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true);
+        expect(await this.mock.proposalEta(this.proposal.id)).to.equal(0n);
+        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true;
 
         await this.helper.propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
-        await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 });
-        await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter3).vote({ support: Enums.VoteType.Against });
+        await this.helper.connect(this.voter4).vote({ support: Enums.VoteType.Abstain });
         await this.helper.waitForDeadline();
         const txQueue = await this.helper.queue();
 
-        const eta = web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(defaultDelay);
-        expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal(eta);
-        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true);
+        const eta = (await time.clockFromReceipt.timestamp(txQueue)) + defaultDelay;
+        expect(await this.mock.proposalEta(this.proposal.id)).to.equal(eta);
+        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true;
 
         await this.helper.waitForEta();
         const txExecute = await this.helper.execute();
 
-        expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id });
-        await expectEvent.inTransaction(txQueue.tx, this.timelock, 'QueueTransaction', { eta });
-
-        expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id });
-        await expectEvent.inTransaction(txExecute.tx, this.timelock, 'ExecuteTransaction', { eta });
-        await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled');
+        await expect(txQueue)
+          .to.emit(this.mock, 'ProposalQueued')
+          .withArgs(this.proposal.id, eta)
+          .to.emit(this.timelock, 'QueueTransaction')
+          .withArgs(...Array(5).fill(anyValue), eta);
+
+        await expect(txExecute)
+          .to.emit(this.mock, 'ProposalExecuted')
+          .withArgs(this.proposal.id)
+          .to.emit(this.timelock, 'ExecuteTransaction')
+          .withArgs(...Array(5).fill(anyValue), eta)
+          .to.emit(this.receiver, 'MockFunctionCalled');
       });
 
       describe('should revert', function () {
@@ -126,29 +129,35 @@ contract('GovernorTimelockCompound', function (accounts) {
           it('if already queued', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
-            await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [
-              this.proposal.id,
-              Enums.ProposalState.Queued,
-              proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
-            ]);
+            await expect(this.helper.queue())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+              .withArgs(
+                this.proposal.id,
+                Enums.ProposalState.Queued,
+                GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
+              );
           });
 
           it('if proposal contains duplicate calls', async function () {
             const action = {
-              target: this.token.address,
-              data: this.token.contract.methods.approve(this.receiver.address, constants.MAX_UINT256).encodeABI(),
+              target: this.token.target,
+              data: this.token.interface.encodeFunctionData('approve', [this.receiver.target, ethers.MaxUint256]),
             };
             const { id } = this.helper.setProposal([action, action], '<proposal description>');
 
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
-            await expectRevertCustomError(this.helper.queue(), 'GovernorAlreadyQueuedProposal', [id]);
-            await expectRevertCustomError(this.helper.execute(), 'GovernorNotQueuedProposal', [id]);
+            await expect(this.helper.queue())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyQueuedProposal')
+              .withArgs(id);
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorNotQueuedProposal')
+              .withArgs(id);
           });
         });
 
@@ -156,25 +165,26 @@ contract('GovernorTimelockCompound', function (accounts) {
           it('if not queued', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
-            await this.helper.waitForDeadline(+1);
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
+            await this.helper.waitForDeadline(1n);
 
-            expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
+            expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Succeeded);
 
-            await expectRevertCustomError(this.helper.execute(), 'GovernorNotQueuedProposal', [this.proposal.id]);
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorNotQueuedProposal')
+              .withArgs(this.proposal.id);
           });
 
           it('if too early', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
 
-            expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
+            expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Queued);
 
-            await expectRevert(
-              this.helper.execute(),
+            await expect(this.helper.execute()).to.be.rejectedWith(
               "Timelock::executeTransaction: Transaction hasn't surpassed time lock",
             );
           });
@@ -182,96 +192,86 @@ contract('GovernorTimelockCompound', function (accounts) {
           it('if too late', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
-            await this.helper.waitForEta(+30 * 86400);
+            await this.helper.waitForEta(time.duration.days(30));
 
-            expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Expired);
+            expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Expired);
 
-            await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
-              this.proposal.id,
-              Enums.ProposalState.Expired,
-              proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
-            ]);
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+              .withArgs(
+                this.proposal.id,
+                Enums.ProposalState.Expired,
+                GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
+              );
           });
 
           it('if already executed', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
             await this.helper.execute();
-            await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
-              this.proposal.id,
-              Enums.ProposalState.Executed,
-              proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
-            ]);
+
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+              .withArgs(
+                this.proposal.id,
+                Enums.ProposalState.Executed,
+                GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
+              );
           });
         });
 
         describe('on safe receive', function () {
           describe('ERC721', function () {
-            const name = 'Non Fungible Token';
-            const symbol = 'NFT';
-            const tokenId = web3.utils.toBN(1);
+            const tokenId = 1n;
 
             beforeEach(async function () {
-              this.token = await ERC721.new(name, symbol);
-              await this.token.$_mint(owner, tokenId);
+              this.token = await ethers.deployContract('$ERC721', ['Non Fungible Token', 'NFT']);
+              await this.token.$_mint(this.owner, tokenId);
             });
 
             it("can't receive an ERC721 safeTransfer", async function () {
-              await expectRevertCustomError(
-                this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner }),
-                'GovernorDisabledDeposit',
-                [],
-              );
+              await expect(
+                this.token.connect(this.owner).safeTransferFrom(this.owner, this.mock, tokenId),
+              ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit');
             });
           });
 
           describe('ERC1155', function () {
-            const uri = 'https://token-cdn-domain/{id}.json';
             const tokenIds = {
-              1: web3.utils.toBN(1000),
-              2: web3.utils.toBN(2000),
-              3: web3.utils.toBN(3000),
+              1: 1000n,
+              2: 2000n,
+              3: 3000n,
             };
 
             beforeEach(async function () {
-              this.token = await ERC1155.new(uri);
-              await this.token.$_mintBatch(owner, Object.keys(tokenIds), Object.values(tokenIds), '0x');
+              this.token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']);
+              await this.token.$_mintBatch(this.owner, Object.keys(tokenIds), Object.values(tokenIds), '0x');
             });
 
             it("can't receive ERC1155 safeTransfer", async function () {
-              await expectRevertCustomError(
-                this.token.safeTransferFrom(
-                  owner,
-                  this.mock.address,
+              await expect(
+                this.token.connect(this.owner).safeTransferFrom(
+                  this.owner,
+                  this.mock,
                   ...Object.entries(tokenIds)[0], // id + amount
                   '0x',
-                  { from: owner },
                 ),
-                'GovernorDisabledDeposit',
-                [],
-              );
+              ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit');
             });
 
             it("can't receive ERC1155 safeBatchTransfer", async function () {
-              await expectRevertCustomError(
-                this.token.safeBatchTransferFrom(
-                  owner,
-                  this.mock.address,
-                  Object.keys(tokenIds),
-                  Object.values(tokenIds),
-                  '0x',
-                  { from: owner },
-                ),
-                'GovernorDisabledDeposit',
-                [],
-              );
+              await expect(
+                this.token
+                  .connect(this.owner)
+                  .safeBatchTransferFrom(this.owner, this.mock, Object.keys(tokenIds), Object.values(tokenIds), '0x'),
+              ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit');
             });
           });
         });
@@ -281,111 +281,114 @@ contract('GovernorTimelockCompound', function (accounts) {
         it('cancel before queue prevents scheduling', async function () {
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
 
-          expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id });
+          await expect(this.helper.cancel('internal'))
+            .to.emit(this.mock, 'ProposalCanceled')
+            .withArgs(this.proposal.id);
 
-          expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
-          await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [
-            this.proposal.id,
-            Enums.ProposalState.Canceled,
-            proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
-          ]);
+          expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Canceled);
+
+          await expect(this.helper.queue())
+            .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+            .withArgs(
+              this.proposal.id,
+              Enums.ProposalState.Canceled,
+              GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
+            );
         });
 
         it('cancel after queue prevents executing', async function () {
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
           await this.helper.queue();
 
-          expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id });
+          await expect(this.helper.cancel('internal'))
+            .to.emit(this.mock, 'ProposalCanceled')
+            .withArgs(this.proposal.id);
 
-          expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
-          await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
-            this.proposal.id,
-            Enums.ProposalState.Canceled,
-            proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
-          ]);
+          expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Canceled);
+
+          await expect(this.helper.execute())
+            .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+            .withArgs(
+              this.proposal.id,
+              Enums.ProposalState.Canceled,
+              GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
+            );
         });
       });
 
       describe('onlyGovernance', function () {
         describe('relay', function () {
           beforeEach(async function () {
-            await this.token.$_mint(this.mock.address, 1);
+            await this.token.$_mint(this.mock, 1);
           });
 
           it('is protected', async function () {
-            await expectRevertCustomError(
-              this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI(), {
-                from: owner,
-              }),
-              'GovernorOnlyExecutor',
-              [owner],
-            );
+            await expect(
+              this.mock
+                .connect(this.owner)
+                .relay(this.token, 0, this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n])),
+            )
+              .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
+              .withArgs(this.owner.address);
           });
 
           it('can be executed through governance', async function () {
             this.helper.setProposal(
               [
                 {
-                  target: this.mock.address,
-                  data: this.mock.contract.methods
-                    .relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI())
-                    .encodeABI(),
+                  target: this.mock.target,
+                  data: this.mock.interface.encodeFunctionData('relay', [
+                    this.token.target,
+                    0n,
+                    this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n]),
+                  ]),
                 },
               ],
               '<proposal description>',
             );
 
-            expect(await this.token.balanceOf(this.mock.address), 1);
-            expect(await this.token.balanceOf(other), 0);
-
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
-            const txExecute = await this.helper.execute();
 
-            expect(await this.token.balanceOf(this.mock.address), 0);
-            expect(await this.token.balanceOf(other), 1);
+            const txExecute = this.helper.execute();
 
-            await expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', {
-              from: this.mock.address,
-              to: other,
-              value: '1',
-            });
+            await expect(txExecute).to.changeTokenBalances(this.token, [this.mock, this.other], [-1n, 1n]);
+
+            await expect(txExecute).to.emit(this.token, 'Transfer').withArgs(this.mock.target, this.other.address, 1n);
           });
         });
 
         describe('updateTimelock', function () {
           beforeEach(async function () {
-            this.newTimelock = await Timelock.new(this.mock.address, 7 * 86400);
+            this.newTimelock = await ethers.deployContract('CompTimelock', [this.mock, time.duration.days(7n)]);
           });
 
           it('is protected', async function () {
-            await expectRevertCustomError(
-              this.mock.updateTimelock(this.newTimelock.address, { from: owner }),
-              'GovernorOnlyExecutor',
-              [owner],
-            );
+            await expect(this.mock.connect(this.owner).updateTimelock(this.newTimelock))
+              .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
+              .withArgs(this.owner.address);
           });
 
           it('can be executed through governance to', async function () {
             this.helper.setProposal(
               [
                 {
-                  target: this.timelock.address,
-                  data: this.timelock.contract.methods.setPendingAdmin(owner).encodeABI(),
+                  target: this.timelock.target,
+                  data: this.timelock.interface.encodeFunctionData('setPendingAdmin', [this.owner.address]),
                 },
                 {
-                  target: this.mock.address,
-                  data: this.mock.contract.methods.updateTimelock(this.newTimelock.address).encodeABI(),
+                  target: this.mock.target,
+                  data: this.mock.interface.encodeFunctionData('updateTimelock', [this.newTimelock.target]),
                 },
               ],
               '<proposal description>',
@@ -393,28 +396,35 @@ contract('GovernorTimelockCompound', function (accounts) {
 
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
-            const txExecute = await this.helper.execute();
 
-            expectEvent(txExecute, 'TimelockChange', {
-              oldTimelock: this.timelock.address,
-              newTimelock: this.newTimelock.address,
-            });
+            await expect(this.helper.execute())
+              .to.emit(this.mock, 'TimelockChange')
+              .withArgs(this.timelock.target, this.newTimelock.target);
 
-            expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address);
+            expect(await this.mock.timelock()).to.equal(this.newTimelock.target);
           });
         });
 
         it('can transfer timelock to new governor', async function () {
-          const newGovernor = await Governor.new(name, 8, 32, 0, this.timelock.address, this.token.address, 0);
+          const newGovernor = await ethers.deployContract('$GovernorTimelockCompoundMock', [
+            name,
+            8n,
+            32n,
+            0n,
+            this.timelock,
+            this.token,
+            0n,
+          ]);
+
           this.helper.setProposal(
             [
               {
-                target: this.timelock.address,
-                data: this.timelock.contract.methods.setPendingAdmin(newGovernor.address).encodeABI(),
+                target: this.timelock.target,
+                data: this.timelock.interface.encodeFunctionData('setPendingAdmin', [newGovernor.target]),
               },
             ],
             '<proposal description>',
@@ -422,18 +432,15 @@ contract('GovernorTimelockCompound', function (accounts) {
 
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
           await this.helper.queue();
           await this.helper.waitForEta();
-          const txExecute = await this.helper.execute();
 
-          await expectEvent.inTransaction(txExecute.tx, this.timelock, 'NewPendingAdmin', {
-            newPendingAdmin: newGovernor.address,
-          });
+          await expect(this.helper.execute()).to.emit(this.timelock, 'NewPendingAdmin').withArgs(newGovernor.target);
 
           await newGovernor.__acceptAdmin();
-          expect(await this.timelock.admin()).to.be.bignumber.equal(newGovernor.address);
+          expect(await this.timelock.admin()).to.equal(newGovernor.target);
         });
       });
     });

+ 254 - 265
test/governance/extensions/GovernorTimelockControl.test.js

@@ -1,88 +1,79 @@
-const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');
 
-const Enums = require('../../helpers/enums');
-const { GovernorHelper, proposalStatesToBitMap, timelockSalt } = require('../../helpers/governance');
-const { expectRevertCustomError } = require('../../helpers/customError');
-const { clockFromReceipt } = require('../../helpers/time');
-
-const Timelock = artifacts.require('TimelockController');
-const Governor = artifacts.require('$GovernorTimelockControlMock');
-const CallReceiver = artifacts.require('CallReceiverMock');
-const ERC721 = artifacts.require('$ERC721');
-const ERC1155 = artifacts.require('$ERC1155');
+const { GovernorHelper, timelockSalt } = require('../../helpers/governance');
+const { bigint: Enums } = require('../../helpers/enums');
+const { bigint: time } = require('../../helpers/time');
 
 const TOKENS = [
-  { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
-  { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' },
+  { Token: '$ERC20Votes', mode: 'blocknumber' },
+  { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
 ];
 
-contract('GovernorTimelockControl', function (accounts) {
-  const [owner, voter1, voter2, voter3, voter4, other] = accounts;
-
-  const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
-  const PROPOSER_ROLE = web3.utils.soliditySha3('PROPOSER_ROLE');
-  const EXECUTOR_ROLE = web3.utils.soliditySha3('EXECUTOR_ROLE');
-  const CANCELLER_ROLE = web3.utils.soliditySha3('CANCELLER_ROLE');
-
-  const name = 'OZ-Governor';
-  const version = '1';
-  const tokenName = 'MockToken';
-  const tokenSymbol = 'MTKN';
-  const tokenSupply = web3.utils.toWei('100');
-  const votingDelay = web3.utils.toBN(4);
-  const votingPeriod = web3.utils.toBN(16);
-  const value = web3.utils.toWei('1');
-
-  const delay = 3600;
-
-  for (const { mode, Token } of TOKENS) {
-    describe(`using ${Token._json.contractName}`, function () {
+const DEFAULT_ADMIN_ROLE = ethers.ZeroHash;
+const PROPOSER_ROLE = ethers.id('PROPOSER_ROLE');
+const EXECUTOR_ROLE = ethers.id('EXECUTOR_ROLE');
+const CANCELLER_ROLE = ethers.id('CANCELLER_ROLE');
+
+const name = 'OZ-Governor';
+const version = '1';
+const tokenName = 'MockToken';
+const tokenSymbol = 'MTKN';
+const tokenSupply = ethers.parseEther('100');
+const votingDelay = 4n;
+const votingPeriod = 16n;
+const value = ethers.parseEther('1');
+const delay = time.duration.hours(1n);
+
+describe('GovernorTimelockControl', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      const [deployer, owner, voter1, voter2, voter3, voter4, other] = await ethers.getSigners();
+      const receiver = await ethers.deployContract('CallReceiverMock');
+
+      const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
+      const timelock = await ethers.deployContract('TimelockController', [delay, [], [], deployer]);
+      const mock = await ethers.deployContract('$GovernorTimelockControlMock', [
+        name,
+        votingDelay,
+        votingPeriod,
+        0n,
+        timelock,
+        token,
+        0n,
+      ]);
+
+      await owner.sendTransaction({ to: timelock, value });
+      await token.$_mint(owner, tokenSupply);
+      await timelock.grantRole(PROPOSER_ROLE, mock);
+      await timelock.grantRole(PROPOSER_ROLE, owner);
+      await timelock.grantRole(CANCELLER_ROLE, mock);
+      await timelock.grantRole(CANCELLER_ROLE, owner);
+      await timelock.grantRole(EXECUTOR_ROLE, ethers.ZeroAddress);
+      await timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer);
+
+      const helper = new GovernorHelper(mock, mode);
+      await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
+      await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
+      await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
+      await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
+
+      return { deployer, owner, voter1, voter2, voter3, voter4, other, receiver, token, mock, timelock, helper };
+    };
+
+    describe(`using ${Token}`, function () {
       beforeEach(async function () {
-        const [deployer] = await web3.eth.getAccounts();
-
-        this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
-        this.timelock = await Timelock.new(delay, [], [], deployer);
-        this.mock = await Governor.new(
-          name,
-          votingDelay,
-          votingPeriod,
-          0,
-          this.timelock.address,
-          this.token.address,
-          0,
-        );
-        this.receiver = await CallReceiver.new();
-
-        this.helper = new GovernorHelper(this.mock, mode);
-
-        this.PROPOSER_ROLE = await this.timelock.PROPOSER_ROLE();
-        this.EXECUTOR_ROLE = await this.timelock.EXECUTOR_ROLE();
-        this.CANCELLER_ROLE = await this.timelock.CANCELLER_ROLE();
-
-        await web3.eth.sendTransaction({ from: owner, to: this.timelock.address, value });
-
-        // normal setup: governor is proposer, everyone is executor, timelock is its own admin
-        await this.timelock.grantRole(PROPOSER_ROLE, this.mock.address);
-        await this.timelock.grantRole(PROPOSER_ROLE, owner);
-        await this.timelock.grantRole(CANCELLER_ROLE, this.mock.address);
-        await this.timelock.grantRole(CANCELLER_ROLE, owner);
-        await this.timelock.grantRole(EXECUTOR_ROLE, constants.ZERO_ADDRESS);
-        await this.timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer);
-
-        await this.token.$_mint(owner, tokenSupply);
-        await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
+        Object.assign(this, await loadFixture(fixture));
 
         // default proposal
         this.proposal = this.helper.setProposal(
           [
             {
-              target: this.receiver.address,
+              target: this.receiver.target,
               value,
-              data: this.receiver.contract.methods.mockFunction().encodeABI(),
+              data: this.receiver.interface.encodeFunctionData('mockFunction'),
             },
           ],
           '<proposal description>',
@@ -90,54 +81,63 @@ contract('GovernorTimelockControl', function (accounts) {
 
         this.proposal.timelockid = await this.timelock.hashOperationBatch(
           ...this.proposal.shortProposal.slice(0, 3),
-          '0x0',
-          timelockSalt(this.mock.address, this.proposal.shortProposal[3]),
+          ethers.ZeroHash,
+          timelockSalt(this.mock.target, this.proposal.shortProposal[3]),
         );
       });
 
       it("doesn't accept ether transfers", async function () {
-        await expectRevert.unspecified(web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: 1 }));
+        await expect(this.owner.sendTransaction({ to: this.mock, value: 1n })).to.be.revertedWithCustomError(
+          this.mock,
+          'GovernorDisabledDeposit',
+        );
       });
 
       it('post deployment check', async function () {
-        expect(await this.mock.name()).to.be.equal(name);
-        expect(await this.mock.token()).to.be.equal(this.token.address);
-        expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
-        expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
-        expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
+        expect(await this.mock.name()).to.equal(name);
+        expect(await this.mock.token()).to.equal(this.token.target);
+        expect(await this.mock.votingDelay()).to.equal(votingDelay);
+        expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
+        expect(await this.mock.quorum(0n)).to.equal(0n);
 
-        expect(await this.mock.timelock()).to.be.equal(this.timelock.address);
+        expect(await this.mock.timelock()).to.equal(this.timelock.target);
       });
 
       it('nominal', async function () {
-        expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal('0');
-        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true);
+        expect(await this.mock.proposalEta(this.proposal.id)).to.equal(0n);
+        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true;
 
         await this.helper.propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
-        await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 });
-        await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter3).vote({ support: Enums.VoteType.Against });
+        await this.helper.connect(this.voter4).vote({ support: Enums.VoteType.Abstain });
         await this.helper.waitForDeadline();
-        const txQueue = await this.helper.queue();
-        await this.helper.waitForEta();
-
-        const eta = web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(delay);
-        expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal(eta);
-        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true);
 
-        const txExecute = await this.helper.execute();
+        expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true;
+        const txQueue = await this.helper.queue();
 
-        expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id });
-        await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallScheduled', { id: this.proposal.timelockid });
-        await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallSalt', {
-          id: this.proposal.timelockid,
-        });
+        const eta = (await time.clockFromReceipt.timestamp(txQueue)) + delay;
+        expect(await this.mock.proposalEta(this.proposal.id)).to.equal(eta);
+        await this.helper.waitForEta();
 
-        expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id });
-        await expectEvent.inTransaction(txExecute.tx, this.timelock, 'CallExecuted', { id: this.proposal.timelockid });
-        await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled');
+        const txExecute = this.helper.execute();
+
+        await expect(txQueue)
+          .to.emit(this.mock, 'ProposalQueued')
+          .withArgs(this.proposal.id, anyValue)
+          .to.emit(this.timelock, 'CallScheduled')
+          .withArgs(this.proposal.timelockid, ...Array(6).fill(anyValue))
+          .to.emit(this.timelock, 'CallSalt')
+          .withArgs(this.proposal.timelockid, anyValue);
+
+        await expect(txExecute)
+          .to.emit(this.mock, 'ProposalExecuted')
+          .withArgs(this.proposal.id)
+          .to.emit(this.timelock, 'CallExecuted')
+          .withArgs(this.proposal.timelockid, ...Array(4).fill(anyValue))
+          .to.emit(this.receiver, 'MockFunctionCalled');
       });
 
       describe('should revert', function () {
@@ -145,14 +145,16 @@ contract('GovernorTimelockControl', function (accounts) {
           it('if already queued', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
-            await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [
-              this.proposal.id,
-              Enums.ProposalState.Queued,
-              proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
-            ]);
+            await expect(this.helper.queue())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+              .withArgs(
+                this.proposal.id,
+                Enums.ProposalState.Queued,
+                GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
+              );
           });
         });
 
@@ -160,66 +162,69 @@ contract('GovernorTimelockControl', function (accounts) {
           it('if not queued', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
-            await this.helper.waitForDeadline(+1);
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
+            await this.helper.waitForDeadline(1n);
 
-            expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
+            expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Succeeded);
 
-            await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [
-              this.proposal.timelockid,
-              proposalStatesToBitMap(Enums.OperationState.Ready),
-            ]);
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.timelock, 'TimelockUnexpectedOperationState')
+              .withArgs(this.proposal.timelockid, GovernorHelper.proposalStatesToBitMap(Enums.OperationState.Ready));
           });
 
           it('if too early', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
 
-            expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
+            expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Queued);
 
-            await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [
-              this.proposal.timelockid,
-              proposalStatesToBitMap(Enums.OperationState.Ready),
-            ]);
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.timelock, 'TimelockUnexpectedOperationState')
+              .withArgs(this.proposal.timelockid, GovernorHelper.proposalStatesToBitMap(Enums.OperationState.Ready));
           });
 
           it('if already executed', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
             await this.helper.execute();
-            await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
-              this.proposal.id,
-              Enums.ProposalState.Executed,
-              proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
-            ]);
+
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+              .withArgs(
+                this.proposal.id,
+                Enums.ProposalState.Executed,
+                GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
+              );
           });
 
           it('if already executed by another proposer', async function () {
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
 
             await this.timelock.executeBatch(
               ...this.proposal.shortProposal.slice(0, 3),
-              '0x0',
-              timelockSalt(this.mock.address, this.proposal.shortProposal[3]),
+              ethers.ZeroHash,
+              timelockSalt(this.mock.target, this.proposal.shortProposal[3]),
             );
 
-            await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
-              this.proposal.id,
-              Enums.ProposalState.Executed,
-              proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
-            ]);
+            await expect(this.helper.execute())
+              .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+              .withArgs(
+                this.proposal.id,
+                Enums.ProposalState.Executed,
+                GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
+              );
           });
         });
       });
@@ -228,178 +233,179 @@ contract('GovernorTimelockControl', function (accounts) {
         it('cancel before queue prevents scheduling', async function () {
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
 
-          expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id });
+          await expect(this.helper.cancel('internal'))
+            .to.emit(this.mock, 'ProposalCanceled')
+            .withArgs(this.proposal.id);
+
+          expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Canceled);
 
-          expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
-          await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [
-            this.proposal.id,
-            Enums.ProposalState.Canceled,
-            proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
-          ]);
+          await expect(this.helper.queue())
+            .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+            .withArgs(
+              this.proposal.id,
+              Enums.ProposalState.Canceled,
+              GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded]),
+            );
         });
 
         it('cancel after queue prevents executing', async function () {
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
           await this.helper.queue();
 
-          expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id });
+          await expect(this.helper.cancel('internal'))
+            .to.emit(this.mock, 'ProposalCanceled')
+            .withArgs(this.proposal.id);
 
-          expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
-          await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
-            this.proposal.id,
-            Enums.ProposalState.Canceled,
-            proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
-          ]);
+          expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Canceled);
+
+          await expect(this.helper.execute())
+            .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+            .withArgs(
+              this.proposal.id,
+              Enums.ProposalState.Canceled,
+              GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
+            );
         });
 
         it('cancel on timelock is reflected on governor', async function () {
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
           await this.helper.queue();
 
-          expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
+          expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Queued);
 
-          expectEvent(await this.timelock.cancel(this.proposal.timelockid, { from: owner }), 'Cancelled', {
-            id: this.proposal.timelockid,
-          });
+          await expect(this.timelock.connect(this.owner).cancel(this.proposal.timelockid))
+            .to.emit(this.timelock, 'Cancelled')
+            .withArgs(this.proposal.timelockid);
 
-          expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
+          expect(await this.mock.state(this.proposal.id)).to.equal(Enums.ProposalState.Canceled);
         });
       });
 
       describe('onlyGovernance', function () {
         describe('relay', function () {
           beforeEach(async function () {
-            await this.token.$_mint(this.mock.address, 1);
+            await this.token.$_mint(this.mock, 1);
           });
 
           it('is protected', async function () {
-            await expectRevertCustomError(
-              this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI(), {
-                from: owner,
-              }),
-              'GovernorOnlyExecutor',
-              [owner],
-            );
+            await expect(
+              this.mock
+                .connect(this.owner)
+                .relay(this.token, 0n, this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n])),
+            )
+              .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
+              .withArgs(this.owner.address);
           });
 
           it('can be executed through governance', async function () {
             this.helper.setProposal(
               [
                 {
-                  target: this.mock.address,
-                  data: this.mock.contract.methods
-                    .relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI())
-                    .encodeABI(),
+                  target: this.mock.target,
+                  data: this.mock.interface.encodeFunctionData('relay', [
+                    this.token.target,
+                    0n,
+                    this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n]),
+                  ]),
                 },
               ],
               '<proposal description>',
             );
 
-            expect(await this.token.balanceOf(this.mock.address), 1);
-            expect(await this.token.balanceOf(other), 0);
-
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
+
             const txExecute = await this.helper.execute();
 
-            expect(await this.token.balanceOf(this.mock.address), 0);
-            expect(await this.token.balanceOf(other), 1);
+            await expect(txExecute).to.changeTokenBalances(this.token, [this.mock, this.other], [-1n, 1n]);
 
-            await expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', {
-              from: this.mock.address,
-              to: other,
-              value: '1',
-            });
+            await expect(txExecute).to.emit(this.token, 'Transfer').withArgs(this.mock.target, this.other.address, 1n);
           });
 
           it('is payable and can transfer eth to EOA', async function () {
-            const t2g = web3.utils.toBN(128); // timelock to governor
-            const g2o = web3.utils.toBN(100); // governor to eoa (other)
+            const t2g = 128n; // timelock to governor
+            const g2o = 100n; // governor to eoa (other)
 
             this.helper.setProposal(
               [
                 {
-                  target: this.mock.address,
+                  target: this.mock.target,
                   value: t2g,
-                  data: this.mock.contract.methods.relay(other, g2o, '0x').encodeABI(),
+                  data: this.mock.interface.encodeFunctionData('relay', [this.other.address, g2o, '0x']),
                 },
               ],
               '<proposal description>',
             );
 
-            expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(web3.utils.toBN(0));
-            const timelockBalance = await web3.eth.getBalance(this.timelock.address).then(web3.utils.toBN);
-            const otherBalance = await web3.eth.getBalance(other).then(web3.utils.toBN);
-
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
-            await this.helper.execute();
 
-            expect(await web3.eth.getBalance(this.timelock.address)).to.be.bignumber.equal(timelockBalance.sub(t2g));
-            expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(t2g.sub(g2o));
-            expect(await web3.eth.getBalance(other)).to.be.bignumber.equal(otherBalance.add(g2o));
+            await expect(this.helper.execute()).to.changeEtherBalances(
+              [this.timelock, this.mock, this.other],
+              [-t2g, t2g - g2o, g2o],
+            );
           });
 
           it('protected against other proposers', async function () {
-            const target = this.mock.address;
-            const value = web3.utils.toWei('0');
-            const data = this.mock.contract.methods.relay(constants.ZERO_ADDRESS, 0, '0x').encodeABI();
-            const predecessor = constants.ZERO_BYTES32;
-            const salt = constants.ZERO_BYTES32;
-
-            await this.timelock.schedule(target, value, data, predecessor, salt, delay, { from: owner });
-
-            await time.increase(delay);
-
-            await expectRevertCustomError(
-              this.timelock.execute(target, value, data, predecessor, salt, { from: owner }),
-              'QueueEmpty', // Bubbled up from Governor
-              [],
+            const call = [
+              this.mock,
+              0n,
+              this.mock.interface.encodeFunctionData('relay', [ethers.ZeroAddress, 0n, '0x']),
+              ethers.ZeroHash,
+              ethers.ZeroHash,
+            ];
+
+            await this.timelock.connect(this.owner).schedule(...call, delay);
+
+            await time.clock.timestamp().then(clock => time.forward.timestamp(clock + delay));
+
+            // Error bubbled up from Governor
+            await expect(this.timelock.connect(this.owner).execute(...call)).to.be.revertedWithCustomError(
+              this.mock,
+              'QueueEmpty',
             );
           });
         });
 
         describe('updateTimelock', function () {
           beforeEach(async function () {
-            this.newTimelock = await Timelock.new(
+            this.newTimelock = await ethers.deployContract('TimelockController', [
               delay,
-              [this.mock.address],
-              [this.mock.address],
-              constants.ZERO_ADDRESS,
-            );
+              [this.mock],
+              [this.mock],
+              ethers.ZeroAddress,
+            ]);
           });
 
           it('is protected', async function () {
-            await expectRevertCustomError(
-              this.mock.updateTimelock(this.newTimelock.address, { from: owner }),
-              'GovernorOnlyExecutor',
-              [owner],
-            );
+            await expect(this.mock.connect(this.owner).updateTimelock(this.newTimelock))
+              .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
+              .withArgs(this.owner.address);
           });
 
           it('can be executed through governance to', async function () {
             this.helper.setProposal(
               [
                 {
-                  target: this.mock.address,
-                  data: this.mock.contract.methods.updateTimelock(this.newTimelock.address).encodeABI(),
+                  target: this.mock.target,
+                  data: this.mock.interface.encodeFunctionData('updateTimelock', [this.newTimelock.target]),
                 },
               ],
               '<proposal description>',
@@ -407,81 +413,64 @@ contract('GovernorTimelockControl', function (accounts) {
 
             await this.helper.propose();
             await this.helper.waitForSnapshot();
-            await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+            await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
             await this.helper.waitForDeadline();
             await this.helper.queue();
             await this.helper.waitForEta();
-            const txExecute = await this.helper.execute();
 
-            expectEvent(txExecute, 'TimelockChange', {
-              oldTimelock: this.timelock.address,
-              newTimelock: this.newTimelock.address,
-            });
+            await expect(this.helper.execute())
+              .to.emit(this.mock, 'TimelockChange')
+              .withArgs(this.timelock.target, this.newTimelock.target);
 
-            expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address);
+            expect(await this.mock.timelock()).to.equal(this.newTimelock.target);
           });
         });
 
         describe('on safe receive', function () {
           describe('ERC721', function () {
-            const name = 'Non Fungible Token';
-            const symbol = 'NFT';
-            const tokenId = web3.utils.toBN(1);
+            const tokenId = 1n;
 
             beforeEach(async function () {
-              this.token = await ERC721.new(name, symbol);
-              await this.token.$_mint(owner, tokenId);
+              this.token = await ethers.deployContract('$ERC721', ['Non Fungible Token', 'NFT']);
+              await this.token.$_mint(this.owner, tokenId);
             });
 
             it("can't receive an ERC721 safeTransfer", async function () {
-              await expectRevertCustomError(
-                this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner }),
-                'GovernorDisabledDeposit',
-                [],
-              );
+              await expect(
+                this.token.connect(this.owner).safeTransferFrom(this.owner, this.mock, tokenId),
+              ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit');
             });
           });
 
           describe('ERC1155', function () {
-            const uri = 'https://token-cdn-domain/{id}.json';
             const tokenIds = {
-              1: web3.utils.toBN(1000),
-              2: web3.utils.toBN(2000),
-              3: web3.utils.toBN(3000),
+              1: 1000n,
+              2: 2000n,
+              3: 3000n,
             };
 
             beforeEach(async function () {
-              this.token = await ERC1155.new(uri);
-              await this.token.$_mintBatch(owner, Object.keys(tokenIds), Object.values(tokenIds), '0x');
+              this.token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']);
+              await this.token.$_mintBatch(this.owner, Object.keys(tokenIds), Object.values(tokenIds), '0x');
             });
 
             it("can't receive ERC1155 safeTransfer", async function () {
-              await expectRevertCustomError(
-                this.token.safeTransferFrom(
-                  owner,
-                  this.mock.address,
+              await expect(
+                this.token.connect(this.owner).safeTransferFrom(
+                  this.owner,
+                  this.mock,
                   ...Object.entries(tokenIds)[0], // id + amount
                   '0x',
-                  { from: owner },
                 ),
-                'GovernorDisabledDeposit',
-                [],
-              );
+              ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit');
             });
 
             it("can't receive ERC1155 safeBatchTransfer", async function () {
-              await expectRevertCustomError(
-                this.token.safeBatchTransferFrom(
-                  owner,
-                  this.mock.address,
-                  Object.keys(tokenIds),
-                  Object.values(tokenIds),
-                  '0x',
-                  { from: owner },
-                ),
-                'GovernorDisabledDeposit',
-                [],
-              );
+              await expect(
+                this.token
+                  .connect(this.owner)
+                  .safeBatchTransferFrom(this.owner, this.mock, Object.keys(tokenIds), Object.values(tokenIds), '0x'),
+              ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit');
             });
           });
         });
@@ -491,8 +480,8 @@ contract('GovernorTimelockControl', function (accounts) {
         this.helper.setProposal(
           [
             {
-              target: this.mock.address,
-              data: this.mock.contract.methods.nonGovernanceFunction().encodeABI(),
+              target: this.mock.target,
+              data: this.mock.interface.encodeFunctionData('nonGovernanceFunction'),
             },
           ],
           '<proposal description>',
@@ -500,7 +489,7 @@ contract('GovernorTimelockControl', function (accounts) {
 
         await this.helper.propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
         await this.helper.waitForDeadline();
         await this.helper.queue();
         await this.helper.waitForEta();

+ 81 - 83
test/governance/extensions/GovernorVotesQuorumFraction.test.js

@@ -1,58 +1,60 @@
-const { expectEvent, time } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
+const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers');
 
-const Enums = require('../../helpers/enums');
-const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance');
-const { clock } = require('../../helpers/time');
-const { expectRevertCustomError } = require('../../helpers/customError');
-
-const Governor = artifacts.require('$GovernorMock');
-const CallReceiver = artifacts.require('CallReceiverMock');
+const { GovernorHelper } = require('../../helpers/governance');
+const { bigint: Enums } = require('../../helpers/enums');
+const { bigint: time } = require('../../helpers/time');
 
 const TOKENS = [
-  { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
-  { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' },
+  { Token: '$ERC20Votes', mode: 'blocknumber' },
+  { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
 ];
 
-contract('GovernorVotesQuorumFraction', function (accounts) {
-  const [owner, voter1, voter2, voter3, voter4] = accounts;
-
-  const name = 'OZ-Governor';
-  const version = '1';
-  const tokenName = 'MockToken';
-  const tokenSymbol = 'MTKN';
-  const tokenSupply = web3.utils.toBN(web3.utils.toWei('100'));
-  const ratio = web3.utils.toBN(8); // percents
-  const newRatio = web3.utils.toBN(6); // percents
-  const votingDelay = web3.utils.toBN(4);
-  const votingPeriod = web3.utils.toBN(16);
-  const value = web3.utils.toWei('1');
-
-  for (const { mode, Token } of TOKENS) {
-    describe(`using ${Token._json.contractName}`, function () {
-      beforeEach(async function () {
-        this.owner = owner;
-        this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
-        this.mock = await Governor.new(name, votingDelay, votingPeriod, 0, this.token.address, ratio);
-        this.receiver = await CallReceiver.new();
+const name = 'OZ-Governor';
+const version = '1';
+const tokenName = 'MockToken';
+const tokenSymbol = 'MTKN';
+const tokenSupply = ethers.parseEther('100');
+const ratio = 8n; // percents
+const newRatio = 6n; // percents
+const votingDelay = 4n;
+const votingPeriod = 16n;
+const value = ethers.parseEther('1');
+
+describe('GovernorVotesQuorumFraction', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      const [owner, voter1, voter2, voter3, voter4] = await ethers.getSigners();
+
+      const receiver = await ethers.deployContract('CallReceiverMock');
+
+      const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
+      const mock = await ethers.deployContract('$GovernorMock', [name, votingDelay, votingPeriod, 0n, token, ratio]);
 
-        this.helper = new GovernorHelper(this.mock, mode);
+      await owner.sendTransaction({ to: mock, value });
+      await token.$_mint(owner, tokenSupply);
 
-        await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value });
+      const helper = new GovernorHelper(mock, mode);
+      await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
+      await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
+      await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
+      await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
 
-        await this.token.$_mint(owner, tokenSupply);
-        await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
+      return { owner, voter1, voter2, voter3, voter4, receiver, token, mock, helper };
+    };
+
+    describe(`using ${Token}`, function () {
+      beforeEach(async function () {
+        Object.assign(this, await loadFixture(fixture));
 
         // default proposal
         this.proposal = this.helper.setProposal(
           [
             {
-              target: this.receiver.address,
+              target: this.receiver.target,
               value,
-              data: this.receiver.contract.methods.mockFunction().encodeABI(),
+              data: this.receiver.interface.encodeFunctionData('mockFunction'),
             },
           ],
           '<proposal description>',
@@ -60,22 +62,22 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
       });
 
       it('deployment check', async function () {
-        expect(await this.mock.name()).to.be.equal(name);
-        expect(await this.mock.token()).to.be.equal(this.token.address);
-        expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
-        expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
-        expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
-        expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(ratio);
-        expect(await this.mock.quorumDenominator()).to.be.bignumber.equal('100');
-        expect(await clock[mode]().then(timepoint => this.mock.quorum(timepoint - 1))).to.be.bignumber.equal(
-          tokenSupply.mul(ratio).divn(100),
+        expect(await this.mock.name()).to.equal(name);
+        expect(await this.mock.token()).to.equal(this.token.target);
+        expect(await this.mock.votingDelay()).to.equal(votingDelay);
+        expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
+        expect(await this.mock.quorum(0)).to.equal(0n);
+        expect(await this.mock.quorumNumerator()).to.equal(ratio);
+        expect(await this.mock.quorumDenominator()).to.equal(100n);
+        expect(await time.clock[mode]().then(clock => this.mock.quorum(clock - 1n))).to.equal(
+          (tokenSupply * ratio) / 100n,
         );
       });
 
       it('quroum reached', async function () {
         await this.helper.propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
         await this.helper.waitForDeadline();
         await this.helper.execute();
       });
@@ -83,30 +85,30 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
       it('quroum not reached', async function () {
         await this.helper.propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
+        await this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For });
         await this.helper.waitForDeadline();
-        await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
-          this.proposal.id,
-          Enums.ProposalState.Defeated,
-          proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
-        ]);
+        await expect(this.helper.execute())
+          .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
+          .withArgs(
+            this.proposal.id,
+            Enums.ProposalState.Defeated,
+            GovernorHelper.proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
+          );
       });
 
       describe('onlyGovernance updates', function () {
         it('updateQuorumNumerator is protected', async function () {
-          await expectRevertCustomError(
-            this.mock.updateQuorumNumerator(newRatio, { from: owner }),
-            'GovernorOnlyExecutor',
-            [owner],
-          );
+          await expect(this.mock.connect(this.owner).updateQuorumNumerator(newRatio))
+            .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
+            .withArgs(this.owner.address);
         });
 
         it('can updateQuorumNumerator through governance', async function () {
           this.helper.setProposal(
             [
               {
-                target: this.mock.address,
-                data: this.mock.contract.methods.updateQuorumNumerator(newRatio).encodeABI(),
+                target: this.mock.target,
+                data: this.mock.interface.encodeFunctionData('updateQuorumNumerator', [newRatio]),
               },
             ],
             '<proposal description>',
@@ -114,36 +116,33 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
 
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
 
-          expectEvent(await this.helper.execute(), 'QuorumNumeratorUpdated', {
-            oldQuorumNumerator: ratio,
-            newQuorumNumerator: newRatio,
-          });
+          await expect(this.helper.execute()).to.emit(this.mock, 'QuorumNumeratorUpdated').withArgs(ratio, newRatio);
 
-          expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(newRatio);
-          expect(await this.mock.quorumDenominator()).to.be.bignumber.equal('100');
+          expect(await this.mock.quorumNumerator()).to.equal(newRatio);
+          expect(await this.mock.quorumDenominator()).to.equal(100n);
 
           // it takes one block for the new quorum to take effect
-          expect(await clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1))).to.be.bignumber.equal(
-            tokenSupply.mul(ratio).divn(100),
+          expect(await time.clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1n))).to.equal(
+            (tokenSupply * ratio) / 100n,
           );
 
-          await time.advanceBlock();
+          await mine();
 
-          expect(await clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1))).to.be.bignumber.equal(
-            tokenSupply.mul(newRatio).divn(100),
+          expect(await time.clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1n))).to.equal(
+            (tokenSupply * newRatio) / 100n,
           );
         });
 
         it('cannot updateQuorumNumerator over the maximum', async function () {
-          const quorumNumerator = 101;
+          const quorumNumerator = 101n;
           this.helper.setProposal(
             [
               {
-                target: this.mock.address,
-                data: this.mock.contract.methods.updateQuorumNumerator(quorumNumerator).encodeABI(),
+                target: this.mock.target,
+                data: this.mock.interface.encodeFunctionData('updateQuorumNumerator', [quorumNumerator]),
               },
             ],
             '<proposal description>',
@@ -151,15 +150,14 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
 
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
+          await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For });
           await this.helper.waitForDeadline();
 
           const quorumDenominator = await this.mock.quorumDenominator();
 
-          await expectRevertCustomError(this.helper.execute(), 'GovernorInvalidQuorumFraction', [
-            quorumNumerator,
-            quorumDenominator,
-          ]);
+          await expect(this.helper.execute())
+            .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidQuorumFraction')
+            .withArgs(quorumNumerator, quorumDenominator);
         });
       });
     });

+ 151 - 176
test/governance/extensions/GovernorWithParams.test.js

@@ -1,66 +1,62 @@
-const { expectEvent } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
-const ethSigUtil = require('eth-sig-util');
-const Wallet = require('ethereumjs-wallet').default;
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-const Enums = require('../../helpers/enums');
-const { getDomain, domainType, ExtendedBallot } = require('../../helpers/eip712');
 const { GovernorHelper } = require('../../helpers/governance');
-const { expectRevertCustomError } = require('../../helpers/customError');
-
-const Governor = artifacts.require('$GovernorWithParamsMock');
-const CallReceiver = artifacts.require('CallReceiverMock');
-const ERC1271WalletMock = artifacts.require('ERC1271WalletMock');
-
-const rawParams = {
-  uintParam: web3.utils.toBN('42'),
-  strParam: 'These are my params',
-};
-
-const encodedParams = web3.eth.abi.encodeParameters(['uint256', 'string'], Object.values(rawParams));
+const { bigint: Enums } = require('../../helpers/enums');
+const { getDomain, ExtendedBallot } = require('../../helpers/eip712');
 
 const TOKENS = [
-  { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
-  { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' },
+  { Token: '$ERC20Votes', mode: 'blocknumber' },
+  { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
 ];
 
-contract('GovernorWithParams', function (accounts) {
-  const [owner, proposer, voter1, voter2, voter3, voter4] = accounts;
+const name = 'OZ-Governor';
+const version = '1';
+const tokenName = 'MockToken';
+const tokenSymbol = 'MTKN';
+const tokenSupply = ethers.parseEther('100');
+const votingDelay = 4n;
+const votingPeriod = 16n;
+const value = ethers.parseEther('1');
+
+const params = {
+  decoded: [42n, 'These are my params'],
+  encoded: ethers.AbiCoder.defaultAbiCoder().encode(['uint256', 'string'], [42n, 'These are my params']),
+};
 
-  const name = 'OZ-Governor';
-  const version = '1';
-  const tokenName = 'MockToken';
-  const tokenSymbol = 'MTKN';
-  const tokenSupply = web3.utils.toWei('100');
-  const votingDelay = web3.utils.toBN(4);
-  const votingPeriod = web3.utils.toBN(16);
-  const value = web3.utils.toWei('1');
+describe('GovernorWithParams', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      const [owner, proposer, voter1, voter2, voter3, voter4, other] = await ethers.getSigners();
+      const receiver = await ethers.deployContract('CallReceiverMock');
 
-  for (const { mode, Token } of TOKENS) {
-    describe(`using ${Token._json.contractName}`, function () {
-      beforeEach(async function () {
-        this.chainId = await web3.eth.getChainId();
-        this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
-        this.mock = await Governor.new(name, this.token.address);
-        this.receiver = await CallReceiver.new();
+      const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
+      const mock = await ethers.deployContract('$GovernorWithParamsMock', [name, token]);
 
-        this.helper = new GovernorHelper(this.mock, mode);
+      await owner.sendTransaction({ to: mock, value });
+      await token.$_mint(owner, tokenSupply);
 
-        await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value });
+      const helper = new GovernorHelper(mock, mode);
+      await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
+      await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
+      await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
+      await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
 
-        await this.token.$_mint(owner, tokenSupply);
-        await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
-        await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
+      return { owner, proposer, voter1, voter2, voter3, voter4, other, receiver, token, mock, helper };
+    };
+
+    describe(`using ${Token}`, function () {
+      beforeEach(async function () {
+        Object.assign(this, await loadFixture(fixture));
 
         // default proposal
         this.proposal = this.helper.setProposal(
           [
             {
-              target: this.receiver.address,
+              target: this.receiver.target,
               value,
-              data: this.receiver.contract.methods.mockFunction().encodeABI(),
+              data: this.receiver.interface.encodeFunctionData('mockFunction'),
             },
           ],
           '<proposal description>',
@@ -68,201 +64,180 @@ contract('GovernorWithParams', function (accounts) {
       });
 
       it('deployment check', async function () {
-        expect(await this.mock.name()).to.be.equal(name);
-        expect(await this.mock.token()).to.be.equal(this.token.address);
-        expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
-        expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
+        expect(await this.mock.name()).to.equal(name);
+        expect(await this.mock.token()).to.equal(this.token.target);
+        expect(await this.mock.votingDelay()).to.equal(votingDelay);
+        expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
       });
 
       it('nominal is unaffected', async function () {
-        await this.helper.propose({ from: proposer });
+        await this.helper.connect(this.proposer).propose();
         await this.helper.waitForSnapshot();
-        await this.helper.vote({ support: Enums.VoteType.For, reason: 'This is nice' }, { from: voter1 });
-        await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
-        await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 });
-        await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 });
+        await this.helper.connect(this.voter1).vote({ support: Enums.VoteType.For, reason: 'This is nice' });
+        await this.helper.connect(this.voter2).vote({ support: Enums.VoteType.For });
+        await this.helper.connect(this.voter3).vote({ support: Enums.VoteType.Against });
+        await this.helper.connect(this.voter4).vote({ support: Enums.VoteType.Abstain });
         await this.helper.waitForDeadline();
         await this.helper.execute();
 
-        expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
-        expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true);
-        expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true);
-        expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0');
-        expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(value);
+        expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true;
+        expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true;
+        expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
+        expect(await ethers.provider.getBalance(this.receiver)).to.equal(value);
       });
 
       it('Voting with params is properly supported', async function () {
-        await this.helper.propose({ from: proposer });
+        await this.helper.connect(this.proposer).propose();
         await this.helper.waitForSnapshot();
 
-        const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam);
+        const weight = ethers.parseEther('7') - params.decoded[0];
 
-        const tx = await this.helper.vote(
-          {
+        await expect(
+          this.helper.connect(this.voter2).vote({
             support: Enums.VoteType.For,
             reason: 'no particular reason',
-            params: encodedParams,
-          },
-          { from: voter2 },
-        );
-
-        expectEvent(tx, 'CountParams', { ...rawParams });
-        expectEvent(tx, 'VoteCastWithParams', {
-          voter: voter2,
-          proposalId: this.proposal.id,
-          support: Enums.VoteType.For,
-          weight,
-          reason: 'no particular reason',
-          params: encodedParams,
-        });
+            params: params.encoded,
+          }),
+        )
+          .to.emit(this.mock, 'CountParams')
+          .withArgs(...params.decoded)
+          .to.emit(this.mock, 'VoteCastWithParams')
+          .withArgs(
+            this.voter2.address,
+            this.proposal.id,
+            Enums.VoteType.For,
+            weight,
+            'no particular reason',
+            params.encoded,
+          );
 
-        const votes = await this.mock.proposalVotes(this.proposal.id);
-        expect(votes.forVotes).to.be.bignumber.equal(weight);
+        expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, weight, 0n]);
       });
 
       describe('voting by signature', function () {
-        beforeEach(async function () {
-          this.voterBySig = Wallet.generate();
-          this.voterBySig.address = web3.utils.toChecksumAddress(this.voterBySig.getAddressString());
-
-          this.data = (contract, message) =>
-            getDomain(contract).then(domain => ({
-              primaryType: 'ExtendedBallot',
-              types: {
-                EIP712Domain: domainType(domain),
-                ExtendedBallot,
-              },
-              domain,
-              message,
-            }));
-
-          this.sign = privateKey => async (contract, message) =>
-            ethSigUtil.signTypedMessage(privateKey, { data: await this.data(contract, message) });
-        });
-
         it('supports EOA signatures', async function () {
-          await this.token.delegate(this.voterBySig.address, { from: voter2 });
-
-          const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam);
-
-          const nonce = await this.mock.nonces(this.voterBySig.address);
+          await this.token.connect(this.voter2).delegate(this.other);
 
           // Run proposal
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          const tx = await this.helper.vote({
-            support: Enums.VoteType.For,
-            voter: this.voterBySig.address,
-            nonce,
-            reason: 'no particular reason',
-            params: encodedParams,
-            signature: this.sign(this.voterBySig.getPrivateKey()),
-          });
 
-          expectEvent(tx, 'CountParams', { ...rawParams });
-          expectEvent(tx, 'VoteCastWithParams', {
-            voter: this.voterBySig.address,
+          // Prepare vote
+          const weight = ethers.parseEther('7') - params.decoded[0];
+          const nonce = await this.mock.nonces(this.other);
+          const data = {
             proposalId: this.proposal.id,
             support: Enums.VoteType.For,
-            weight,
+            voter: this.other.address,
+            nonce,
             reason: 'no particular reason',
-            params: encodedParams,
-          });
+            params: params.encoded,
+            signature: (contract, message) =>
+              getDomain(contract).then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message)),
+          };
+
+          // Vote
+          await expect(this.helper.vote(data))
+            .to.emit(this.mock, 'CountParams')
+            .withArgs(...params.decoded)
+            .to.emit(this.mock, 'VoteCastWithParams')
+            .withArgs(data.voter, data.proposalId, data.support, weight, data.reason, data.params);
 
-          const votes = await this.mock.proposalVotes(this.proposal.id);
-          expect(votes.forVotes).to.be.bignumber.equal(weight);
-          expect(await this.mock.nonces(this.voterBySig.address)).to.be.bignumber.equal(nonce.addn(1));
+          expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, weight, 0n]);
+          expect(await this.mock.nonces(this.other)).to.equal(nonce + 1n);
         });
 
         it('supports EIP-1271 signature signatures', async function () {
-          const ERC1271WalletOwner = Wallet.generate();
-          ERC1271WalletOwner.address = web3.utils.toChecksumAddress(ERC1271WalletOwner.getAddressString());
-
-          const wallet = await ERC1271WalletMock.new(ERC1271WalletOwner.address);
-
-          await this.token.delegate(wallet.address, { from: voter2 });
-
-          const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam);
-
-          const nonce = await this.mock.nonces(wallet.address);
+          const wallet = await ethers.deployContract('ERC1271WalletMock', [this.other]);
+          await this.token.connect(this.voter2).delegate(wallet);
 
           // Run proposal
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          const tx = await this.helper.vote({
-            support: Enums.VoteType.For,
-            voter: wallet.address,
-            nonce,
-            reason: 'no particular reason',
-            params: encodedParams,
-            signature: this.sign(ERC1271WalletOwner.getPrivateKey()),
-          });
 
-          expectEvent(tx, 'CountParams', { ...rawParams });
-          expectEvent(tx, 'VoteCastWithParams', {
-            voter: wallet.address,
+          // Prepare vote
+          const weight = ethers.parseEther('7') - params.decoded[0];
+          const nonce = await this.mock.nonces(this.other);
+          const data = {
             proposalId: this.proposal.id,
             support: Enums.VoteType.For,
-            weight,
+            voter: wallet.target,
+            nonce,
             reason: 'no particular reason',
-            params: encodedParams,
-          });
+            params: params.encoded,
+            signature: (contract, message) =>
+              getDomain(contract).then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message)),
+          };
+
+          // Vote
+          await expect(this.helper.vote(data))
+            .to.emit(this.mock, 'CountParams')
+            .withArgs(...params.decoded)
+            .to.emit(this.mock, 'VoteCastWithParams')
+            .withArgs(data.voter, data.proposalId, data.support, weight, data.reason, data.params);
 
-          const votes = await this.mock.proposalVotes(this.proposal.id);
-          expect(votes.forVotes).to.be.bignumber.equal(weight);
-          expect(await this.mock.nonces(wallet.address)).to.be.bignumber.equal(nonce.addn(1));
+          expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, weight, 0n]);
+          expect(await this.mock.nonces(wallet)).to.equal(nonce + 1n);
         });
 
         it('reverts if signature does not match signer', async function () {
-          await this.token.delegate(this.voterBySig.address, { from: voter2 });
-
-          const nonce = await this.mock.nonces(this.voterBySig.address);
-
-          const signature = this.sign(this.voterBySig.getPrivateKey());
+          await this.token.connect(this.voter2).delegate(this.other);
 
           // Run proposal
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          const voteParams = {
+
+          // Prepare vote
+          const nonce = await this.mock.nonces(this.other);
+          const data = {
+            proposalId: this.proposal.id,
             support: Enums.VoteType.For,
-            voter: this.voterBySig.address,
+            voter: this.other.address,
             nonce,
-            signature: async (...params) => {
-              const sig = await signature(...params);
-              const tamperedSig = web3.utils.hexToBytes(sig);
-              tamperedSig[42] ^= 0xff;
-              return web3.utils.bytesToHex(tamperedSig);
-            },
             reason: 'no particular reason',
-            params: encodedParams,
+            params: params.encoded,
+            // tampered signature
+            signature: (contract, message) =>
+              getDomain(contract)
+                .then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message))
+                .then(signature => {
+                  const tamperedSig = ethers.toBeArray(signature);
+                  tamperedSig[42] ^= 0xff;
+                  return ethers.hexlify(tamperedSig);
+                }),
           };
 
-          await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSignature', [voteParams.voter]);
+          // Vote
+          await expect(this.helper.vote(data))
+            .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature')
+            .withArgs(data.voter);
         });
 
         it('reverts if vote nonce is incorrect', async function () {
-          await this.token.delegate(this.voterBySig.address, { from: voter2 });
-
-          const nonce = await this.mock.nonces(this.voterBySig.address);
+          await this.token.connect(this.voter2).delegate(this.other);
 
           // Run proposal
           await this.helper.propose();
           await this.helper.waitForSnapshot();
-          const voteParams = {
+
+          // Prepare vote
+          const nonce = await this.mock.nonces(this.other);
+          const data = {
+            proposalId: this.proposal.id,
             support: Enums.VoteType.For,
-            voter: this.voterBySig.address,
-            nonce: nonce.addn(1),
-            signature: this.sign(this.voterBySig.getPrivateKey()),
+            voter: this.other.address,
+            nonce: nonce + 1n,
             reason: 'no particular reason',
-            params: encodedParams,
+            params: params.encoded,
+            signature: (contract, message) =>
+              getDomain(contract).then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message)),
           };
 
-          await expectRevertCustomError(
-            this.helper.vote(voteParams),
-            // The signature check implies the nonce can't be tampered without changing the signer
-            'GovernorInvalidSignature',
-            [voteParams.voter],
-          );
+          // Vote
+          await expect(this.helper.vote(data))
+            .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature')
+            .withArgs(data.voter);
         });
       });
     });

+ 5 - 5
test/governance/utils/ERC6372.behavior.js

@@ -1,19 +1,19 @@
-const { clock } = require('../../helpers/time');
+const { bigint: time } = require('../../helpers/time');
 
 function shouldBehaveLikeERC6372(mode = 'blocknumber') {
-  describe('should implement ERC6372', function () {
+  describe('should implement ERC-6372', function () {
     beforeEach(async function () {
       this.mock = this.mock ?? this.token ?? this.votes;
     });
 
     it('clock is correct', async function () {
-      expect(await this.mock.clock()).to.be.bignumber.equal(await clock[mode]().then(web3.utils.toBN));
+      expect(await this.mock.clock()).to.equal(await time.clock[mode]());
     });
 
     it('CLOCK_MODE is correct', async function () {
       const params = new URLSearchParams(await this.mock.CLOCK_MODE());
-      expect(params.get('mode')).to.be.equal(mode);
-      expect(params.get('from')).to.be.equal(mode == 'blocknumber' ? 'default' : null);
+      expect(params.get('mode')).to.equal(mode);
+      expect(params.get('from')).to.equal(mode == 'blocknumber' ? 'default' : null);
     });
   });
 }

+ 219 - 248
test/governance/utils/Votes.behavior.js

@@ -1,303 +1,277 @@
-const { constants, expectEvent, time } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
+const { expect } = require('chai');
+const { mine } = require('@nomicfoundation/hardhat-network-helpers');
 
-const { MAX_UINT256, ZERO_ADDRESS } = constants;
-
-const { fromRpcSig } = require('ethereumjs-util');
-const ethSigUtil = require('eth-sig-util');
-const Wallet = require('ethereumjs-wallet').default;
+const { bigint: time } = require('../../helpers/time');
+const { getDomain, Delegation } = require('../../helpers/eip712');
 
 const { shouldBehaveLikeERC6372 } = require('./ERC6372.behavior');
-const { getDomain, domainType, Delegation } = require('../../helpers/eip712');
-const { clockFromReceipt } = require('../../helpers/time');
-const { expectRevertCustomError } = require('../../helpers/customError');
-
-const buildAndSignDelegation = (contract, message, pk) =>
-  getDomain(contract)
-    .then(domain => ({
-      primaryType: 'Delegation',
-      types: { EIP712Domain: domainType(domain), Delegation },
-      domain,
-      message,
-    }))
-    .then(data => fromRpcSig(ethSigUtil.signTypedMessage(pk, { data })));
-
-function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungible = true }) {
+
+function shouldBehaveLikeVotes(tokens, { mode = 'blocknumber', fungible = true }) {
+  beforeEach(async function () {
+    [this.delegator, this.delegatee, this.alice, this.bob, this.other] = this.accounts;
+    this.domain = await getDomain(this.votes);
+  });
+
   shouldBehaveLikeERC6372(mode);
 
-  const getWeight = token => web3.utils.toBN(fungible ? token : 1);
+  const getWeight = token => (fungible ? token : 1n);
 
   describe('run votes workflow', function () {
     it('initial nonce is 0', async function () {
-      expect(await this.votes.nonces(accounts[0])).to.be.bignumber.equal('0');
+      expect(await this.votes.nonces(this.alice)).to.equal(0n);
     });
 
     describe('delegation with signature', function () {
       const token = tokens[0];
 
       it('delegation without tokens', async function () {
-        expect(await this.votes.delegates(accounts[1])).to.be.equal(ZERO_ADDRESS);
+        expect(await this.votes.delegates(this.alice)).to.equal(ethers.ZeroAddress);
 
-        const { receipt } = await this.votes.delegate(accounts[1], { from: accounts[1] });
-        expectEvent(receipt, 'DelegateChanged', {
-          delegator: accounts[1],
-          fromDelegate: ZERO_ADDRESS,
-          toDelegate: accounts[1],
-        });
-        expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
+        await expect(this.votes.connect(this.alice).delegate(this.alice))
+          .to.emit(this.votes, 'DelegateChanged')
+          .withArgs(this.alice.address, ethers.ZeroAddress, this.alice.address)
+          .to.not.emit(this.votes, 'DelegateVotesChanged');
 
-        expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[1]);
+        expect(await this.votes.delegates(this.alice)).to.equal(this.alice.address);
       });
 
       it('delegation with tokens', async function () {
-        await this.votes.$_mint(accounts[1], token);
+        await this.votes.$_mint(this.alice, token);
         const weight = getWeight(token);
 
-        expect(await this.votes.delegates(accounts[1])).to.be.equal(ZERO_ADDRESS);
+        expect(await this.votes.delegates(this.alice)).to.equal(ethers.ZeroAddress);
 
-        const { receipt } = await this.votes.delegate(accounts[1], { from: accounts[1] });
-        const timepoint = await clockFromReceipt[mode](receipt);
+        const tx = await this.votes.connect(this.alice).delegate(this.alice);
+        const timepoint = await time.clockFromReceipt[mode](tx);
 
-        expectEvent(receipt, 'DelegateChanged', {
-          delegator: accounts[1],
-          fromDelegate: ZERO_ADDRESS,
-          toDelegate: accounts[1],
-        });
-        expectEvent(receipt, 'DelegateVotesChanged', {
-          delegate: accounts[1],
-          previousVotes: '0',
-          newVotes: weight,
-        });
+        await expect(tx)
+          .to.emit(this.votes, 'DelegateChanged')
+          .withArgs(this.alice.address, ethers.ZeroAddress, this.alice.address)
+          .to.emit(this.votes, 'DelegateVotesChanged')
+          .withArgs(this.alice.address, 0n, weight);
 
-        expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[1]);
-        expect(await this.votes.getVotes(accounts[1])).to.be.bignumber.equal(weight);
-        expect(await this.votes.getPastVotes(accounts[1], timepoint - 1)).to.be.bignumber.equal('0');
-        await time.advanceBlock();
-        expect(await this.votes.getPastVotes(accounts[1], timepoint)).to.be.bignumber.equal(weight);
+        expect(await this.votes.delegates(this.alice)).to.equal(this.alice.address);
+        expect(await this.votes.getVotes(this.alice)).to.equal(weight);
+        expect(await this.votes.getPastVotes(this.alice, timepoint - 1n)).to.equal(0n);
+        await mine();
+        expect(await this.votes.getPastVotes(this.alice, timepoint)).to.equal(weight);
       });
 
       it('delegation update', async function () {
-        await this.votes.delegate(accounts[1], { from: accounts[1] });
-        await this.votes.$_mint(accounts[1], token);
+        await this.votes.connect(this.alice).delegate(this.alice);
+        await this.votes.$_mint(this.alice, token);
         const weight = getWeight(token);
 
-        expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[1]);
-        expect(await this.votes.getVotes(accounts[1])).to.be.bignumber.equal(weight);
-        expect(await this.votes.getVotes(accounts[2])).to.be.bignumber.equal('0');
-
-        const { receipt } = await this.votes.delegate(accounts[2], { from: accounts[1] });
-        const timepoint = await clockFromReceipt[mode](receipt);
-
-        expectEvent(receipt, 'DelegateChanged', {
-          delegator: accounts[1],
-          fromDelegate: accounts[1],
-          toDelegate: accounts[2],
-        });
-        expectEvent(receipt, 'DelegateVotesChanged', {
-          delegate: accounts[1],
-          previousVotes: weight,
-          newVotes: '0',
-        });
-        expectEvent(receipt, 'DelegateVotesChanged', {
-          delegate: accounts[2],
-          previousVotes: '0',
-          newVotes: weight,
-        });
-
-        expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[2]);
-        expect(await this.votes.getVotes(accounts[1])).to.be.bignumber.equal('0');
-        expect(await this.votes.getVotes(accounts[2])).to.be.bignumber.equal(weight);
-
-        expect(await this.votes.getPastVotes(accounts[1], timepoint - 1)).to.be.bignumber.equal(weight);
-        expect(await this.votes.getPastVotes(accounts[2], timepoint - 1)).to.be.bignumber.equal('0');
-        await time.advanceBlock();
-        expect(await this.votes.getPastVotes(accounts[1], timepoint)).to.be.bignumber.equal('0');
-        expect(await this.votes.getPastVotes(accounts[2], timepoint)).to.be.bignumber.equal(weight);
+        expect(await this.votes.delegates(this.alice)).to.equal(this.alice.address);
+        expect(await this.votes.getVotes(this.alice)).to.equal(weight);
+        expect(await this.votes.getVotes(this.bob)).to.equal(0);
+
+        const tx = await this.votes.connect(this.alice).delegate(this.bob);
+        const timepoint = await time.clockFromReceipt[mode](tx);
+
+        await expect(tx)
+          .to.emit(this.votes, 'DelegateChanged')
+          .withArgs(this.alice.address, this.alice.address, this.bob.address)
+          .to.emit(this.votes, 'DelegateVotesChanged')
+          .withArgs(this.alice.address, weight, 0)
+          .to.emit(this.votes, 'DelegateVotesChanged')
+          .withArgs(this.bob.address, 0, weight);
+
+        expect(await this.votes.delegates(this.alice)).to.equal(this.bob.address);
+        expect(await this.votes.getVotes(this.alice)).to.equal(0n);
+        expect(await this.votes.getVotes(this.bob)).to.equal(weight);
+
+        expect(await this.votes.getPastVotes(this.alice, timepoint - 1n)).to.equal(weight);
+        expect(await this.votes.getPastVotes(this.bob, timepoint - 1n)).to.equal(0n);
+        await mine();
+        expect(await this.votes.getPastVotes(this.alice, timepoint)).to.equal(0n);
+        expect(await this.votes.getPastVotes(this.bob, timepoint)).to.equal(weight);
       });
 
       describe('with signature', function () {
-        const delegator = Wallet.generate();
-        const [delegatee, other] = accounts;
-        const nonce = 0;
-        delegator.address = web3.utils.toChecksumAddress(delegator.getAddressString());
+        const nonce = 0n;
 
         it('accept signed delegation', async function () {
-          await this.votes.$_mint(delegator.address, token);
+          await this.votes.$_mint(this.delegator.address, token);
           const weight = getWeight(token);
 
-          const { v, r, s } = await buildAndSignDelegation(
-            this.votes,
-            {
-              delegatee,
-              nonce,
-              expiry: MAX_UINT256,
-            },
-            delegator.getPrivateKey(),
-          );
-
-          expect(await this.votes.delegates(delegator.address)).to.be.equal(ZERO_ADDRESS);
-
-          const { receipt } = await this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s);
-          const timepoint = await clockFromReceipt[mode](receipt);
-
-          expectEvent(receipt, 'DelegateChanged', {
-            delegator: delegator.address,
-            fromDelegate: ZERO_ADDRESS,
-            toDelegate: delegatee,
-          });
-          expectEvent(receipt, 'DelegateVotesChanged', {
-            delegate: delegatee,
-            previousVotes: '0',
-            newVotes: weight,
-          });
-
-          expect(await this.votes.delegates(delegator.address)).to.be.equal(delegatee);
-          expect(await this.votes.getVotes(delegator.address)).to.be.bignumber.equal('0');
-          expect(await this.votes.getVotes(delegatee)).to.be.bignumber.equal(weight);
-          expect(await this.votes.getPastVotes(delegatee, timepoint - 1)).to.be.bignumber.equal('0');
-          await time.advanceBlock();
-          expect(await this.votes.getPastVotes(delegatee, timepoint)).to.be.bignumber.equal(weight);
+          const { r, s, v } = await this.delegator
+            .signTypedData(
+              this.domain,
+              { Delegation },
+              {
+                delegatee: this.delegatee.address,
+                nonce,
+                expiry: ethers.MaxUint256,
+              },
+            )
+            .then(ethers.Signature.from);
+
+          expect(await this.votes.delegates(this.delegator.address)).to.equal(ethers.ZeroAddress);
+
+          const tx = await this.votes.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s);
+          const timepoint = await time.clockFromReceipt[mode](tx);
+
+          await expect(tx)
+            .to.emit(this.votes, 'DelegateChanged')
+            .withArgs(this.delegator.address, ethers.ZeroAddress, this.delegatee.address)
+            .to.emit(this.votes, 'DelegateVotesChanged')
+            .withArgs(this.delegatee.address, 0, weight);
+
+          expect(await this.votes.delegates(this.delegator.address)).to.equal(this.delegatee.address);
+          expect(await this.votes.getVotes(this.delegator.address)).to.equal(0n);
+          expect(await this.votes.getVotes(this.delegatee)).to.equal(weight);
+          expect(await this.votes.getPastVotes(this.delegatee, timepoint - 1n)).to.equal(0n);
+          await mine();
+          expect(await this.votes.getPastVotes(this.delegatee, timepoint)).to.equal(weight);
         });
 
         it('rejects reused signature', async function () {
-          const { v, r, s } = await buildAndSignDelegation(
-            this.votes,
-            {
-              delegatee,
-              nonce,
-              expiry: MAX_UINT256,
-            },
-            delegator.getPrivateKey(),
-          );
-
-          await this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s);
-
-          await expectRevertCustomError(
-            this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s),
-            'InvalidAccountNonce',
-            [delegator.address, nonce + 1],
-          );
+          const { r, s, v } = await this.delegator
+            .signTypedData(
+              this.domain,
+              { Delegation },
+              {
+                delegatee: this.delegatee.address,
+                nonce,
+                expiry: ethers.MaxUint256,
+              },
+            )
+            .then(ethers.Signature.from);
+
+          await this.votes.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s);
+
+          await expect(this.votes.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s))
+            .to.be.revertedWithCustomError(this.votes, 'InvalidAccountNonce')
+            .withArgs(this.delegator.address, nonce + 1n);
         });
 
         it('rejects bad delegatee', async function () {
-          const { v, r, s } = await buildAndSignDelegation(
-            this.votes,
-            {
-              delegatee,
-              nonce,
-              expiry: MAX_UINT256,
-            },
-            delegator.getPrivateKey(),
+          const { r, s, v } = await this.delegator
+            .signTypedData(
+              this.domain,
+              { Delegation },
+              {
+                delegatee: this.delegatee.address,
+                nonce,
+                expiry: ethers.MaxUint256,
+              },
+            )
+            .then(ethers.Signature.from);
+
+          const tx = await this.votes.delegateBySig(this.other, nonce, ethers.MaxUint256, v, r, s);
+          const receipt = await tx.wait();
+
+          const [delegateChanged] = receipt.logs.filter(
+            log => this.votes.interface.parseLog(log)?.name === 'DelegateChanged',
           );
-
-          const receipt = await this.votes.delegateBySig(other, nonce, MAX_UINT256, v, r, s);
-          const { args } = receipt.logs.find(({ event }) => event === 'DelegateChanged');
-          expect(args.delegator).to.not.be.equal(delegator.address);
-          expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS);
-          expect(args.toDelegate).to.be.equal(other);
+          const { args } = this.votes.interface.parseLog(delegateChanged);
+          expect(args.delegator).to.not.be.equal(this.delegator.address);
+          expect(args.fromDelegate).to.equal(ethers.ZeroAddress);
+          expect(args.toDelegate).to.equal(this.other.address);
         });
 
         it('rejects bad nonce', async function () {
-          const { v, r, s } = await buildAndSignDelegation(
-            this.votes,
-            {
-              delegatee,
-              nonce: nonce + 1,
-              expiry: MAX_UINT256,
-            },
-            delegator.getPrivateKey(),
-          );
-
-          await expectRevertCustomError(
-            this.votes.delegateBySig(delegatee, nonce + 1, MAX_UINT256, v, r, s),
-            'InvalidAccountNonce',
-            [delegator.address, 0],
-          );
+          const { r, s, v } = await this.delegator
+            .signTypedData(
+              this.domain,
+              { Delegation },
+              {
+                delegatee: this.delegatee.address,
+                nonce: nonce + 1n,
+                expiry: ethers.MaxUint256,
+              },
+            )
+            .then(ethers.Signature.from);
+
+          await expect(this.votes.delegateBySig(this.delegatee, nonce + 1n, ethers.MaxUint256, v, r, s))
+            .to.be.revertedWithCustomError(this.votes, 'InvalidAccountNonce')
+            .withArgs(this.delegator.address, 0);
         });
 
         it('rejects expired permit', async function () {
-          const expiry = (await time.latest()) - time.duration.weeks(1);
-          const { v, r, s } = await buildAndSignDelegation(
-            this.votes,
-            {
-              delegatee,
-              nonce,
-              expiry,
-            },
-            delegator.getPrivateKey(),
-          );
-
-          await expectRevertCustomError(
-            this.votes.delegateBySig(delegatee, nonce, expiry, v, r, s),
-            'VotesExpiredSignature',
-            [expiry],
-          );
+          const expiry = (await time.clock.timestamp()) - 1n;
+          const { r, s, v } = await this.delegator
+            .signTypedData(
+              this.domain,
+              { Delegation },
+              {
+                delegatee: this.delegatee.address,
+                nonce,
+                expiry,
+              },
+            )
+            .then(ethers.Signature.from);
+
+          await expect(this.votes.delegateBySig(this.delegatee, nonce, expiry, v, r, s))
+            .to.be.revertedWithCustomError(this.votes, 'VotesExpiredSignature')
+            .withArgs(expiry);
         });
       });
     });
 
     describe('getPastTotalSupply', function () {
       beforeEach(async function () {
-        await this.votes.delegate(accounts[1], { from: accounts[1] });
+        await this.votes.connect(this.alice).delegate(this.alice);
       });
 
       it('reverts if block number >= current block', async function () {
         const timepoint = 5e10;
         const clock = await this.votes.clock();
-        await expectRevertCustomError(this.votes.getPastTotalSupply(timepoint), 'ERC5805FutureLookup', [
-          timepoint,
-          clock,
-        ]);
+        await expect(this.votes.getPastTotalSupply(timepoint))
+          .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup')
+          .withArgs(timepoint, clock);
       });
 
       it('returns 0 if there are no checkpoints', async function () {
-        expect(await this.votes.getPastTotalSupply(0)).to.be.bignumber.equal('0');
+        expect(await this.votes.getPastTotalSupply(0n)).to.equal(0n);
       });
 
       it('returns the correct checkpointed total supply', async function () {
         const weight = tokens.map(token => getWeight(token));
 
         // t0 = mint #0
-        const t0 = await this.votes.$_mint(accounts[1], tokens[0]);
-        await time.advanceBlock();
+        const t0 = await this.votes.$_mint(this.alice, tokens[0]);
+        await mine();
         // t1 = mint #1
-        const t1 = await this.votes.$_mint(accounts[1], tokens[1]);
-        await time.advanceBlock();
+        const t1 = await this.votes.$_mint(this.alice, tokens[1]);
+        await mine();
         // t2 = burn #1
-        const t2 = await this.votes.$_burn(...(fungible ? [accounts[1]] : []), tokens[1]);
-        await time.advanceBlock();
+        const t2 = await this.votes.$_burn(...(fungible ? [this.alice] : []), tokens[1]);
+        await mine();
         // t3 = mint #2
-        const t3 = await this.votes.$_mint(accounts[1], tokens[2]);
-        await time.advanceBlock();
+        const t3 = await this.votes.$_mint(this.alice, tokens[2]);
+        await mine();
         // t4 = burn #0
-        const t4 = await this.votes.$_burn(...(fungible ? [accounts[1]] : []), tokens[0]);
-        await time.advanceBlock();
+        const t4 = await this.votes.$_burn(...(fungible ? [this.alice] : []), tokens[0]);
+        await mine();
         // t5 = burn #2
-        const t5 = await this.votes.$_burn(...(fungible ? [accounts[1]] : []), tokens[2]);
-        await time.advanceBlock();
-
-        t0.timepoint = await clockFromReceipt[mode](t0.receipt);
-        t1.timepoint = await clockFromReceipt[mode](t1.receipt);
-        t2.timepoint = await clockFromReceipt[mode](t2.receipt);
-        t3.timepoint = await clockFromReceipt[mode](t3.receipt);
-        t4.timepoint = await clockFromReceipt[mode](t4.receipt);
-        t5.timepoint = await clockFromReceipt[mode](t5.receipt);
-
-        expect(await this.votes.getPastTotalSupply(t0.timepoint - 1)).to.be.bignumber.equal('0');
-        expect(await this.votes.getPastTotalSupply(t0.timepoint)).to.be.bignumber.equal(weight[0]);
-        expect(await this.votes.getPastTotalSupply(t0.timepoint + 1)).to.be.bignumber.equal(weight[0]);
-        expect(await this.votes.getPastTotalSupply(t1.timepoint)).to.be.bignumber.equal(weight[0].add(weight[1]));
-        expect(await this.votes.getPastTotalSupply(t1.timepoint + 1)).to.be.bignumber.equal(weight[0].add(weight[1]));
-        expect(await this.votes.getPastTotalSupply(t2.timepoint)).to.be.bignumber.equal(weight[0]);
-        expect(await this.votes.getPastTotalSupply(t2.timepoint + 1)).to.be.bignumber.equal(weight[0]);
-        expect(await this.votes.getPastTotalSupply(t3.timepoint)).to.be.bignumber.equal(weight[0].add(weight[2]));
-        expect(await this.votes.getPastTotalSupply(t3.timepoint + 1)).to.be.bignumber.equal(weight[0].add(weight[2]));
-        expect(await this.votes.getPastTotalSupply(t4.timepoint)).to.be.bignumber.equal(weight[2]);
-        expect(await this.votes.getPastTotalSupply(t4.timepoint + 1)).to.be.bignumber.equal(weight[2]);
-        expect(await this.votes.getPastTotalSupply(t5.timepoint)).to.be.bignumber.equal('0');
-        await expectRevertCustomError(this.votes.getPastTotalSupply(t5.timepoint + 1), 'ERC5805FutureLookup', [
-          t5.timepoint + 1, // timepoint
-          t5.timepoint + 1, // clock
-        ]);
+        const t5 = await this.votes.$_burn(...(fungible ? [this.alice] : []), tokens[2]);
+        await mine();
+
+        t0.timepoint = await time.clockFromReceipt[mode](t0);
+        t1.timepoint = await time.clockFromReceipt[mode](t1);
+        t2.timepoint = await time.clockFromReceipt[mode](t2);
+        t3.timepoint = await time.clockFromReceipt[mode](t3);
+        t4.timepoint = await time.clockFromReceipt[mode](t4);
+        t5.timepoint = await time.clockFromReceipt[mode](t5);
+
+        expect(await this.votes.getPastTotalSupply(t0.timepoint - 1n)).to.equal(0);
+        expect(await this.votes.getPastTotalSupply(t0.timepoint)).to.equal(weight[0]);
+        expect(await this.votes.getPastTotalSupply(t0.timepoint + 1n)).to.equal(weight[0]);
+        expect(await this.votes.getPastTotalSupply(t1.timepoint)).to.equal(weight[0] + weight[1]);
+        expect(await this.votes.getPastTotalSupply(t1.timepoint + 1n)).to.equal(weight[0] + weight[1]);
+        expect(await this.votes.getPastTotalSupply(t2.timepoint)).to.equal(weight[0]);
+        expect(await this.votes.getPastTotalSupply(t2.timepoint + 1n)).to.equal(weight[0]);
+        expect(await this.votes.getPastTotalSupply(t3.timepoint)).to.equal(weight[0] + weight[2]);
+        expect(await this.votes.getPastTotalSupply(t3.timepoint + 1n)).to.equal(weight[0] + weight[2]);
+        expect(await this.votes.getPastTotalSupply(t4.timepoint)).to.equal(weight[2]);
+        expect(await this.votes.getPastTotalSupply(t4.timepoint + 1n)).to.equal(weight[2]);
+        expect(await this.votes.getPastTotalSupply(t5.timepoint)).to.equal(0);
+        await expect(this.votes.getPastTotalSupply(t5.timepoint + 1n))
+          .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup')
+          .withArgs(t5.timepoint + 1n, t5.timepoint + 1n);
       });
     });
 
@@ -305,44 +279,41 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl
     // https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js.
     describe('Compound test suite', function () {
       beforeEach(async function () {
-        await this.votes.$_mint(accounts[1], tokens[0]);
-        await this.votes.$_mint(accounts[1], tokens[1]);
-        await this.votes.$_mint(accounts[1], tokens[2]);
+        await this.votes.$_mint(this.alice, tokens[0]);
+        await this.votes.$_mint(this.alice, tokens[1]);
+        await this.votes.$_mint(this.alice, tokens[2]);
       });
 
       describe('getPastVotes', function () {
         it('reverts if block number >= current block', async function () {
           const clock = await this.votes.clock();
           const timepoint = 5e10; // far in the future
-          await expectRevertCustomError(this.votes.getPastVotes(accounts[2], timepoint), 'ERC5805FutureLookup', [
-            timepoint,
-            clock,
-          ]);
+          await expect(this.votes.getPastVotes(this.bob, timepoint))
+            .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup')
+            .withArgs(timepoint, clock);
         });
 
         it('returns 0 if there are no checkpoints', async function () {
-          expect(await this.votes.getPastVotes(accounts[2], 0)).to.be.bignumber.equal('0');
+          expect(await this.votes.getPastVotes(this.bob, 0n)).to.equal(0n);
         });
 
         it('returns the latest block if >= last checkpoint block', async function () {
-          const { receipt } = await this.votes.delegate(accounts[2], { from: accounts[1] });
-          const timepoint = await clockFromReceipt[mode](receipt);
-          await time.advanceBlock();
-          await time.advanceBlock();
-
-          const latest = await this.votes.getVotes(accounts[2]);
-          expect(await this.votes.getPastVotes(accounts[2], timepoint)).to.be.bignumber.equal(latest);
-          expect(await this.votes.getPastVotes(accounts[2], timepoint + 1)).to.be.bignumber.equal(latest);
+          const delegate = await this.votes.connect(this.alice).delegate(this.bob);
+          const timepoint = await time.clockFromReceipt[mode](delegate);
+          await mine(2);
+
+          const latest = await this.votes.getVotes(this.bob);
+          expect(await this.votes.getPastVotes(this.bob, timepoint)).to.equal(latest);
+          expect(await this.votes.getPastVotes(this.bob, timepoint + 1n)).to.equal(latest);
         });
 
         it('returns zero if < first checkpoint block', async function () {
-          await time.advanceBlock();
-          const { receipt } = await this.votes.delegate(accounts[2], { from: accounts[1] });
-          const timepoint = await clockFromReceipt[mode](receipt);
-          await time.advanceBlock();
-          await time.advanceBlock();
+          await mine();
+          const delegate = await this.votes.connect(this.alice).delegate(this.bob);
+          const timepoint = await time.clockFromReceipt[mode](delegate);
+          await mine(2);
 
-          expect(await this.votes.getPastVotes(accounts[2], timepoint - 1)).to.be.bignumber.equal('0');
+          expect(await this.votes.getPastVotes(this.bob, timepoint - 1n)).to.equal(0n);
         });
       });
     });

+ 60 - 50
test/governance/utils/Votes.test.js

@@ -1,90 +1,100 @@
-const { constants } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
-const { clockFromReceipt } = require('../../helpers/time');
-const { BNsum } = require('../../helpers/math');
-const { expectRevertCustomError } = require('../../helpers/customError');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
-require('array.prototype.at/auto');
+const { bigint: time } = require('../../helpers/time');
+const { sum } = require('../../helpers/math');
+const { zip } = require('../../helpers/iterate');
 
 const { shouldBehaveLikeVotes } = require('./Votes.behavior');
 
 const MODES = {
-  blocknumber: artifacts.require('$VotesMock'),
-  timestamp: artifacts.require('$VotesTimestampMock'),
+  blocknumber: '$VotesMock',
+  timestamp: '$VotesTimestampMock',
 };
 
-contract('Votes', function (accounts) {
-  const [account1, account2, account3] = accounts;
-  const amounts = {
-    [account1]: web3.utils.toBN('10000000000000000000000000'),
-    [account2]: web3.utils.toBN('10'),
-    [account3]: web3.utils.toBN('20'),
-  };
-
-  const name = 'My Vote';
-  const version = '1';
+const AMOUNTS = [ethers.parseEther('10000000'), 10n, 20n];
 
+describe('Votes', function () {
   for (const [mode, artifact] of Object.entries(MODES)) {
+    const fixture = async () => {
+      const accounts = await ethers.getSigners();
+
+      const amounts = Object.fromEntries(
+        zip(
+          accounts.slice(0, AMOUNTS.length).map(({ address }) => address),
+          AMOUNTS,
+        ),
+      );
+
+      const name = 'My Vote';
+      const version = '1';
+      const votes = await ethers.deployContract(artifact, [name, version]);
+
+      return { accounts, amounts, votes, name, version };
+    };
+
     describe(`vote with ${mode}`, function () {
       beforeEach(async function () {
-        this.votes = await artifact.new(name, version);
+        Object.assign(this, await loadFixture(fixture));
       });
 
-      shouldBehaveLikeVotes(accounts, Object.values(amounts), { mode, fungible: true });
+      shouldBehaveLikeVotes(AMOUNTS, { mode, fungible: true });
 
       it('starts with zero votes', async function () {
-        expect(await this.votes.getTotalSupply()).to.be.bignumber.equal('0');
+        expect(await this.votes.getTotalSupply()).to.equal(0n);
       });
 
       describe('performs voting operations', function () {
         beforeEach(async function () {
           this.txs = [];
-          for (const [account, amount] of Object.entries(amounts)) {
+          for (const [account, amount] of Object.entries(this.amounts)) {
             this.txs.push(await this.votes.$_mint(account, amount));
           }
         });
 
         it('reverts if block number >= current block', async function () {
-          const lastTxTimepoint = await clockFromReceipt[mode](this.txs.at(-1).receipt);
+          const lastTxTimepoint = await time.clockFromReceipt[mode](this.txs.at(-1));
           const clock = await this.votes.clock();
-          await expectRevertCustomError(this.votes.getPastTotalSupply(lastTxTimepoint + 1), 'ERC5805FutureLookup', [
-            lastTxTimepoint + 1,
-            clock,
-          ]);
+          await expect(this.votes.getPastTotalSupply(lastTxTimepoint))
+            .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup')
+            .withArgs(lastTxTimepoint, clock);
         });
 
         it('delegates', async function () {
-          expect(await this.votes.getVotes(account1)).to.be.bignumber.equal('0');
-          expect(await this.votes.getVotes(account2)).to.be.bignumber.equal('0');
-          expect(await this.votes.delegates(account1)).to.be.equal(constants.ZERO_ADDRESS);
-          expect(await this.votes.delegates(account2)).to.be.equal(constants.ZERO_ADDRESS);
-
-          await this.votes.delegate(account1, account1);
-
-          expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(amounts[account1]);
-          expect(await this.votes.getVotes(account2)).to.be.bignumber.equal('0');
-          expect(await this.votes.delegates(account1)).to.be.equal(account1);
-          expect(await this.votes.delegates(account2)).to.be.equal(constants.ZERO_ADDRESS);
-
-          await this.votes.delegate(account2, account1);
-
-          expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(amounts[account1].add(amounts[account2]));
-          expect(await this.votes.getVotes(account2)).to.be.bignumber.equal('0');
-          expect(await this.votes.delegates(account1)).to.be.equal(account1);
-          expect(await this.votes.delegates(account2)).to.be.equal(account1);
+          expect(await this.votes.getVotes(this.accounts[0])).to.equal(0n);
+          expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n);
+          expect(await this.votes.delegates(this.accounts[0])).to.equal(ethers.ZeroAddress);
+          expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress);
+
+          await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[0]));
+
+          expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[0].address]);
+          expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n);
+          expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0].address);
+          expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress);
+
+          await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0]));
+
+          expect(await this.votes.getVotes(this.accounts[0])).to.equal(
+            this.amounts[this.accounts[0].address] + this.amounts[this.accounts[1].address],
+          );
+          expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n);
+          expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0].address);
+          expect(await this.votes.delegates(this.accounts[1])).to.equal(this.accounts[0].address);
         });
 
         it('cross delegates', async function () {
-          await this.votes.delegate(account1, account2);
-          await this.votes.delegate(account2, account1);
+          await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1].address));
+          await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0].address));
 
-          expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(amounts[account2]);
-          expect(await this.votes.getVotes(account2)).to.be.bignumber.equal(amounts[account1]);
+          expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[1].address]);
+          expect(await this.votes.getVotes(this.accounts[1])).to.equal(this.amounts[this.accounts[0].address]);
         });
 
         it('returns total amount of votes', async function () {
-          const totalSupply = BNsum(...Object.values(amounts));
-          expect(await this.votes.getTotalSupply()).to.be.bignumber.equal(totalSupply);
+          const totalSupply = sum(...Object.values(this.amounts));
+          expect(await this.votes.getTotalSupply()).to.equal(totalSupply);
         });
       });
     });

+ 142 - 197
test/helpers/governance.js

@@ -1,23 +1,10 @@
-const { web3 } = require('hardhat');
-const { forward } = require('../helpers/time');
+const { ethers } = require('hardhat');
+const { forward } = require('./time');
 const { ProposalState } = require('./enums');
-
-function zip(...args) {
-  return Array(Math.max(...args.map(array => array.length)))
-    .fill()
-    .map((_, i) => args.map(array => array[i]));
-}
-
-function concatHex(...args) {
-  return web3.utils.bytesToHex([].concat(...args.map(h => web3.utils.hexToBytes(h || '0x'))));
-}
-
-function concatOpts(args, opts = null) {
-  return opts ? args.concat(opts) : args;
-}
+const { unique } = require('./iterate');
 
 const timelockSalt = (address, descriptionHash) =>
-  '0x' + web3.utils.toBN(address).shln(96).xor(web3.utils.toBN(descriptionHash)).toString(16, 64);
+  ethers.toBeHex((ethers.toBigInt(address) << 96n) ^ ethers.toBigInt(descriptionHash), 32);
 
 class GovernorHelper {
   constructor(governor, mode = 'blocknumber') {
@@ -25,229 +12,187 @@ class GovernorHelper {
     this.mode = mode;
   }
 
-  delegate(delegation = {}, opts = null) {
+  connect(account) {
+    this.governor = this.governor.connect(account);
+    return this;
+  }
+
+  /// Setter and getters
+  /**
+   * Specify a proposal either as
+   * 1) an array of objects [{ target, value, data }]
+   * 2) an object of arrays { targets: [], values: [], data: [] }
+   */
+  setProposal(actions, description) {
+    if (Array.isArray(actions)) {
+      this.targets = actions.map(a => a.target);
+      this.values = actions.map(a => a.value || 0n);
+      this.data = actions.map(a => a.data || '0x');
+    } else {
+      ({ targets: this.targets, values: this.values, data: this.data } = actions);
+    }
+    this.description = description;
+    return this;
+  }
+
+  get id() {
+    return ethers.keccak256(
+      ethers.AbiCoder.defaultAbiCoder().encode(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], this.shortProposal),
+    );
+  }
+
+  // used for checking events
+  get signatures() {
+    return this.data.map(() => '');
+  }
+
+  get descriptionHash() {
+    return ethers.id(this.description);
+  }
+
+  // condensed version for queueing end executing
+  get shortProposal() {
+    return [this.targets, this.values, this.data, this.descriptionHash];
+  }
+
+  // full version for proposing
+  get fullProposal() {
+    return [this.targets, this.values, this.data, this.description];
+  }
+
+  get currentProposal() {
+    return this;
+  }
+
+  /// Proposal lifecycle
+  delegate(delegation) {
     return Promise.all([
-      delegation.token.delegate(delegation.to, { from: delegation.to }),
-      delegation.value && delegation.token.transfer(...concatOpts([delegation.to, delegation.value]), opts),
-      delegation.tokenId &&
+      delegation.token.connect(delegation.to).delegate(delegation.to),
+      delegation.value === undefined ||
+        delegation.token.connect(this.governor.runner).transfer(delegation.to, delegation.value),
+      delegation.tokenId === undefined ||
         delegation.token
           .ownerOf(delegation.tokenId)
           .then(owner =>
-            delegation.token.transferFrom(...concatOpts([owner, delegation.to, delegation.tokenId], opts)),
+            delegation.token.connect(this.governor.runner).transferFrom(owner, delegation.to, delegation.tokenId),
           ),
     ]);
   }
 
-  propose(opts = null) {
-    const proposal = this.currentProposal;
-
-    return this.governor.methods[
-      proposal.useCompatibilityInterface
-        ? 'propose(address[],uint256[],string[],bytes[],string)'
-        : 'propose(address[],uint256[],bytes[],string)'
-    ](...concatOpts(proposal.fullProposal, opts));
+  propose() {
+    return this.governor.propose(...this.fullProposal);
   }
 
-  queue(opts = null) {
-    const proposal = this.currentProposal;
-
-    return proposal.useCompatibilityInterface
-      ? this.governor.methods['queue(uint256)'](...concatOpts([proposal.id], opts))
-      : this.governor.methods['queue(address[],uint256[],bytes[],bytes32)'](
-          ...concatOpts(proposal.shortProposal, opts),
-        );
+  queue() {
+    return this.governor.queue(...this.shortProposal);
   }
 
-  execute(opts = null) {
-    const proposal = this.currentProposal;
-
-    return proposal.useCompatibilityInterface
-      ? this.governor.methods['execute(uint256)'](...concatOpts([proposal.id], opts))
-      : this.governor.methods['execute(address[],uint256[],bytes[],bytes32)'](
-          ...concatOpts(proposal.shortProposal, opts),
-        );
+  execute() {
+    return this.governor.execute(...this.shortProposal);
   }
 
-  cancel(visibility = 'external', opts = null) {
-    const proposal = this.currentProposal;
-
+  cancel(visibility = 'external') {
     switch (visibility) {
       case 'external':
-        if (proposal.useCompatibilityInterface) {
-          return this.governor.methods['cancel(uint256)'](...concatOpts([proposal.id], opts));
-        } else {
-          return this.governor.methods['cancel(address[],uint256[],bytes[],bytes32)'](
-            ...concatOpts(proposal.shortProposal, opts),
-          );
-        }
+        return this.governor.cancel(...this.shortProposal);
+
       case 'internal':
-        return this.governor.methods['$_cancel(address[],uint256[],bytes[],bytes32)'](
-          ...concatOpts(proposal.shortProposal, opts),
-        );
+        return this.governor.$_cancel(...this.shortProposal);
+
       default:
         throw new Error(`unsupported visibility "${visibility}"`);
     }
   }
 
-  vote(vote = {}, opts = null) {
-    const proposal = this.currentProposal;
-
-    return vote.signature
-      ? // if signature, and either params or reason →
-        vote.params || vote.reason
-        ? this.sign(vote).then(signature =>
-            this.governor.castVoteWithReasonAndParamsBySig(
-              ...concatOpts(
-                [proposal.id, vote.support, vote.voter, vote.reason || '', vote.params || '', signature],
-                opts,
-              ),
-            ),
-          )
-        : this.sign(vote).then(signature =>
-            this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, vote.voter, signature], opts)),
-          )
-      : vote.params
-      ? // otherwise if params
-        this.governor.castVoteWithReasonAndParams(
-          ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params], opts),
-        )
-      : vote.reason
-      ? // otherwise if reason
-        this.governor.castVoteWithReason(...concatOpts([proposal.id, vote.support, vote.reason], opts))
-      : this.governor.castVote(...concatOpts([proposal.id, vote.support], opts));
-  }
-
-  sign(vote = {}) {
-    return vote.signature(this.governor, this.forgeMessage(vote));
-  }
-
-  forgeMessage(vote = {}) {
-    const proposal = this.currentProposal;
-
-    const message = { proposalId: proposal.id, support: vote.support, voter: vote.voter, nonce: vote.nonce };
-
-    if (vote.params || vote.reason) {
-      message.reason = vote.reason || '';
-      message.params = vote.params || '';
+  async vote(vote = {}) {
+    let method = 'castVote'; // default
+    let args = [this.id, vote.support]; // base
+
+    if (vote.signature) {
+      const sign = await vote.signature(this.governor, this.forgeMessage(vote));
+      if (vote.params || vote.reason) {
+        method = 'castVoteWithReasonAndParamsBySig';
+        args.push(vote.voter, vote.reason ?? '', vote.params ?? '0x', sign);
+      } else {
+        method = 'castVoteBySig';
+        args.push(vote.voter, sign);
+      }
+    } else if (vote.params) {
+      method = 'castVoteWithReasonAndParams';
+      args.push(vote.reason ?? '', vote.params);
+    } else if (vote.reason) {
+      method = 'castVoteWithReason';
+      args.push(vote.reason);
     }
 
-    return message;
+    return await this.governor[method](...args);
   }
 
-  async waitForSnapshot(offset = 0) {
-    const proposal = this.currentProposal;
-    const timepoint = await this.governor.proposalSnapshot(proposal.id);
-    return forward[this.mode](timepoint.addn(offset));
+  /// Clock helpers
+  async waitForSnapshot(offset = 0n) {
+    const timepoint = await this.governor.proposalSnapshot(this.id);
+    return forward[this.mode](timepoint + offset);
   }
 
-  async waitForDeadline(offset = 0) {
-    const proposal = this.currentProposal;
-    const timepoint = await this.governor.proposalDeadline(proposal.id);
-    return forward[this.mode](timepoint.addn(offset));
+  async waitForDeadline(offset = 0n) {
+    const timepoint = await this.governor.proposalDeadline(this.id);
+    return forward[this.mode](timepoint + offset);
   }
 
-  async waitForEta(offset = 0) {
-    const proposal = this.currentProposal;
-    const timestamp = await this.governor.proposalEta(proposal.id);
-    return forward.timestamp(timestamp.addn(offset));
+  async waitForEta(offset = 0n) {
+    const timestamp = await this.governor.proposalEta(this.id);
+    return forward.timestamp(timestamp + offset);
   }
 
-  /**
-   * Specify a proposal either as
-   * 1) an array of objects [{ target, value, data, signature? }]
-   * 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] }
-   */
-  setProposal(actions, description) {
-    let targets, values, signatures, data, useCompatibilityInterface;
+  /// Other helpers
+  forgeMessage(vote = {}) {
+    const message = { proposalId: this.id, support: vote.support, voter: vote.voter, nonce: vote.nonce };
 
-    if (Array.isArray(actions)) {
-      useCompatibilityInterface = actions.some(a => 'signature' in a);
-      targets = actions.map(a => a.target);
-      values = actions.map(a => a.value || '0');
-      signatures = actions.map(a => a.signature || '');
-      data = actions.map(a => a.data || '0x');
-    } else {
-      useCompatibilityInterface = Array.isArray(actions.signatures);
-      ({ targets, values, signatures = [], data } = actions);
+    if (vote.params || vote.reason) {
+      message.reason = vote.reason ?? '';
+      message.params = vote.params ?? '0x';
     }
 
-    const fulldata = zip(
-      signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)),
-      data,
-    ).map(hexs => concatHex(...hexs));
-
-    const descriptionHash = web3.utils.keccak256(description);
-
-    // condensed version for queueing end executing
-    const shortProposal = [targets, values, fulldata, descriptionHash];
-
-    // full version for proposing
-    const fullProposal = [targets, values, ...(useCompatibilityInterface ? [signatures] : []), data, description];
-
-    // proposal id
-    const id = web3.utils.toBN(
-      web3.utils.keccak256(
-        web3.eth.abi.encodeParameters(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], shortProposal),
-      ),
-    );
-
-    this.currentProposal = {
-      id,
-      targets,
-      values,
-      signatures,
-      data,
-      fulldata,
-      description,
-      descriptionHash,
-      shortProposal,
-      fullProposal,
-      useCompatibilityInterface,
-    };
-
-    return this.currentProposal;
+    return message;
   }
-}
 
-/**
- * Encodes a list ProposalStates into a bytes32 representation where each bit enabled corresponds to
- * the underlying position in the `ProposalState` enum. For example:
- *
- * 0x000...10000
- *   ^^^^^^------ ...
- *         ^----- Succeeded
- *          ^---- Defeated
- *           ^--- Canceled
- *            ^-- Active
- *             ^- Pending
- */
-function proposalStatesToBitMap(proposalStates, options = {}) {
-  if (!Array.isArray(proposalStates)) {
-    proposalStates = [proposalStates];
-  }
-  const statesCount = Object.keys(ProposalState).length;
-  let result = 0;
-
-  const uniqueProposalStates = new Set(proposalStates.map(bn => bn.toNumber())); // Remove duplicates
-  for (const state of uniqueProposalStates) {
-    if (state < 0 || state >= statesCount) {
-      expect.fail(`ProposalState ${state} out of possible states (0...${statesCount}-1)`);
-    } else {
-      result |= 1 << state;
+  /**
+   * Encodes a list ProposalStates into a bytes32 representation where each bit enabled corresponds to
+   * the underlying position in the `ProposalState` enum. For example:
+   *
+   * 0x000...10000
+   *   ^^^^^^------ ...
+   *         ^----- Succeeded
+   *          ^---- Defeated
+   *           ^--- Canceled
+   *            ^-- Active
+   *             ^- Pending
+   */
+  static proposalStatesToBitMap(proposalStates, options = {}) {
+    if (!Array.isArray(proposalStates)) {
+      proposalStates = [proposalStates];
+    }
+    const statesCount = BigInt(Object.keys(ProposalState).length);
+    let result = 0n;
+
+    for (const state of unique(...proposalStates)) {
+      if (state < 0n || state >= statesCount) {
+        expect.fail(`ProposalState ${state} out of possible states (0...${statesCount}-1)`);
+      } else {
+        result |= 1n << state;
+      }
     }
-  }
 
-  if (options.inverted) {
-    const mask = 2 ** statesCount - 1;
-    result = result ^ mask;
-  }
+    if (options.inverted) {
+      const mask = 2n ** statesCount - 1n;
+      result = result ^ mask;
+    }
 
-  const hex = web3.utils.numberToHex(result);
-  return web3.utils.padLeft(hex, 64);
+    return ethers.toBeHex(result, 32);
+  }
 }
 
 module.exports = {
   GovernorHelper,
-  proposalStatesToBitMap,
   timelockSalt,
 };

+ 7 - 0
test/helpers/iterate.js

@@ -3,8 +3,15 @@ const mapValues = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v
 
 // Cartesian product of a list of arrays
 const product = (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]);
+const unique = (...array) => array.filter((obj, i) => array.indexOf(obj) === i);
+const zip = (...args) =>
+  Array(Math.max(...args.map(array => array.length)))
+    .fill()
+    .map((_, i) => args.map(array => array[i]));
 
 module.exports = {
   mapValues,
   product,
+  unique,
+  zip,
 };

+ 5 - 6
test/helpers/time.js

@@ -1,3 +1,4 @@
+const { ethers } = require('hardhat');
 const { time, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers');
 const { mapValues } = require('./iterate');
 
@@ -8,9 +9,7 @@ module.exports = {
   },
   clockFromReceipt: {
     blocknumber: receipt => Promise.resolve(receipt.blockNumber),
-    timestamp: receipt => web3.eth.getBlock(receipt.blockNumber).then(block => block.timestamp),
-    // TODO: update for ethers receipt
-    // timestamp: receipt => receipt.getBlock().then(block => block.timestamp),
+    timestamp: receipt => ethers.provider.getBlock(receipt.blockNumber).then(block => block.timestamp),
   },
   forward: {
     blocknumber: mineUpTo,
@@ -21,8 +20,8 @@ module.exports = {
 
 // TODO: deprecate the old version in favor of this one
 module.exports.bigint = {
-  clock: mapValues(module.exports.clock, fn => () => fn().then(BigInt)),
-  clockFromReceipt: mapValues(module.exports.clockFromReceipt, fn => receipt => fn(receipt).then(BigInt)),
+  clock: mapValues(module.exports.clock, fn => () => fn().then(ethers.toBigInt)),
+  clockFromReceipt: mapValues(module.exports.clockFromReceipt, fn => receipt => fn(receipt).then(ethers.toBigInt)),
   forward: module.exports.forward,
-  duration: mapValues(module.exports.duration, fn => n => BigInt(fn(n))),
+  duration: mapValues(module.exports.duration, fn => n => ethers.toBigInt(fn(ethers.toNumber(n)))),
 };

+ 7 - 18
test/helpers/txpool.js

@@ -1,30 +1,20 @@
 const { network } = require('hardhat');
-const { promisify } = require('util');
-
-const queue = promisify(setImmediate);
-
-async function countPendingTransactions() {
-  return parseInt(await network.provider.send('eth_getBlockTransactionCountByNumber', ['pending']));
-}
+const { mine } = require('@nomicfoundation/hardhat-network-helpers');
+const { unique } = require('./iterate');
 
 async function batchInBlock(txs) {
   try {
     // disable auto-mining
     await network.provider.send('evm_setAutomine', [false]);
     // send all transactions
-    const promises = txs.map(fn => fn());
-    // wait for node to have all pending transactions
-    while (txs.length > (await countPendingTransactions())) {
-      await queue();
-    }
+    const responses = await Promise.all(txs.map(fn => fn()));
     // mine one block
-    await network.provider.send('evm_mine');
+    await mine();
     // fetch receipts
-    const receipts = await Promise.all(promises);
+    const receipts = await Promise.all(responses.map(response => response.wait()));
     // Sanity check, all tx should be in the same block
-    const minedBlocks = new Set(receipts.map(({ receipt }) => receipt.blockNumber));
-    expect(minedBlocks.size).to.equal(1);
-
+    expect(unique(receipts.map(receipt => receipt.blockNumber))).to.have.lengthOf(1);
+    // return responses
     return receipts;
   } finally {
     // enable auto-mining
@@ -33,6 +23,5 @@ async function batchInBlock(txs) {
 }
 
 module.exports = {
-  countPendingTransactions,
   batchInBlock,
 };

+ 2 - 4
test/token/ERC20/extensions/ERC20Permit.test.js

@@ -3,9 +3,7 @@ const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
 const { getDomain, domainSeparator, Permit } = require('../../../helpers/eip712');
-const {
-  bigint: { clock, duration },
-} = require('../../../helpers/time');
+const { bigint: time } = require('../../../helpers/time');
 
 const name = 'My Token';
 const symbol = 'MTKN';
@@ -97,7 +95,7 @@ describe('ERC20Permit', function () {
     });
 
     it('rejects expired permit', async function () {
-      const deadline = (await clock.timestamp()) - duration.weeks(1);
+      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))

+ 385 - 426
test/token/ERC20/extensions/ERC20Votes.test.js

@@ -1,585 +1,544 @@
-/* eslint-disable */
-
-const { BN, constants, expectEvent, time } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
-const { MAX_UINT256, ZERO_ADDRESS } = constants;
+const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers');
+
+const { getDomain, Delegation } = require('../../../helpers/eip712');
+const { batchInBlock } = require('../../../helpers/txpool');
+const { bigint: time } = require('../../../helpers/time');
 
 const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior');
-const { fromRpcSig } = require('ethereumjs-util');
-const ethSigUtil = require('eth-sig-util');
-const Wallet = require('ethereumjs-wallet').default;
 
-const { batchInBlock } = require('../../../helpers/txpool');
-const { getDomain, domainType, Delegation } = require('../../../helpers/eip712');
-const { clock, clockFromReceipt } = require('../../../helpers/time');
-const { expectRevertCustomError } = require('../../../helpers/customError');
+const TOKENS = [
+  { Token: '$ERC20Votes', mode: 'blocknumber' },
+  { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
+];
+
+const name = 'My Token';
+const symbol = 'MTKN';
+const version = '1';
+const supply = ethers.parseEther('10000000');
 
-const MODES = {
-  blocknumber: artifacts.require('$ERC20Votes'),
-  timestamp: artifacts.require('$ERC20VotesTimestampMock'),
-};
+describe('ERC20Votes', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      // accounts is required by shouldBehaveLikeVotes
+      const accounts = await ethers.getSigners();
+      const [holder, recipient, delegatee, other1, other2] = accounts;
 
-contract('ERC20Votes', function (accounts) {
-  const [holder, recipient, holderDelegatee, other1, other2] = accounts;
+      const token = await ethers.deployContract(Token, [name, symbol, name, version]);
+      const domain = await getDomain(token);
 
-  const name = 'My Token';
-  const symbol = 'MTKN';
-  const version = '1';
-  const supply = new BN('10000000000000000000000000');
+      return { accounts, holder, recipient, delegatee, other1, other2, token, domain };
+    };
 
-  for (const [mode, artifact] of Object.entries(MODES)) {
     describe(`vote with ${mode}`, function () {
       beforeEach(async function () {
-        this.token = await artifact.new(name, symbol, name, version);
+        Object.assign(this, await loadFixture(fixture));
         this.votes = this.token;
       });
 
       // includes ERC6372 behavior check
-      shouldBehaveLikeVotes(accounts, [1, 17, 42], { mode, fungible: true });
+      shouldBehaveLikeVotes([1, 17, 42], { mode, fungible: true });
 
       it('initial nonce is 0', async function () {
-        expect(await this.token.nonces(holder)).to.be.bignumber.equal('0');
+        expect(await this.token.nonces(this.holder)).to.equal(0n);
       });
 
       it('minting restriction', async function () {
-        const value = web3.utils.toBN(1).shln(208);
-        await expectRevertCustomError(this.token.$_mint(holder, value), 'ERC20ExceededSafeSupply', [
-          value,
-          value.subn(1),
-        ]);
+        const value = 2n ** 208n;
+        await expect(this.token.$_mint(this.holder, value))
+          .to.be.revertedWithCustomError(this.token, 'ERC20ExceededSafeSupply')
+          .withArgs(value, value - 1n);
       });
 
       it('recent checkpoints', async function () {
-        await this.token.delegate(holder, { from: holder });
+        await this.token.connect(this.holder).delegate(this.holder);
         for (let i = 0; i < 6; i++) {
-          await this.token.$_mint(holder, 1);
+          await this.token.$_mint(this.holder, 1n);
         }
-        const timepoint = await clock[mode]();
-        expect(await this.token.numCheckpoints(holder)).to.be.bignumber.equal('6');
+        const timepoint = await time.clock[mode]();
+        expect(await this.token.numCheckpoints(this.holder)).to.equal(6n);
         // recent
-        expect(await this.token.getPastVotes(holder, timepoint - 1)).to.be.bignumber.equal('5');
+        expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(5n);
         // non-recent
-        expect(await this.token.getPastVotes(holder, timepoint - 6)).to.be.bignumber.equal('0');
+        expect(await this.token.getPastVotes(this.holder, timepoint - 6n)).to.equal(0n);
       });
 
       describe('set delegation', function () {
         describe('call', function () {
           it('delegation with balance', async function () {
-            await this.token.$_mint(holder, supply);
-            expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS);
-
-            const { receipt } = await this.token.delegate(holder, { from: holder });
-            const timepoint = await clockFromReceipt[mode](receipt);
-
-            expectEvent(receipt, 'DelegateChanged', {
-              delegator: holder,
-              fromDelegate: ZERO_ADDRESS,
-              toDelegate: holder,
-            });
-            expectEvent(receipt, 'DelegateVotesChanged', {
-              delegate: holder,
-              previousVotes: '0',
-              newVotes: supply,
-            });
-
-            expect(await this.token.delegates(holder)).to.be.equal(holder);
-
-            expect(await this.token.getVotes(holder)).to.be.bignumber.equal(supply);
-            expect(await this.token.getPastVotes(holder, timepoint - 1)).to.be.bignumber.equal('0');
-            await time.advanceBlock();
-            expect(await this.token.getPastVotes(holder, timepoint)).to.be.bignumber.equal(supply);
+            await this.token.$_mint(this.holder, supply);
+            expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress);
+
+            const tx = await this.token.connect(this.holder).delegate(this.holder);
+            const timepoint = await time.clockFromReceipt[mode](tx);
+
+            await expect(tx)
+              .to.emit(this.token, 'DelegateChanged')
+              .withArgs(this.holder.address, ethers.ZeroAddress, this.holder.address)
+              .to.emit(this.token, 'DelegateVotesChanged')
+              .withArgs(this.holder.address, 0n, supply);
+
+            expect(await this.token.delegates(this.holder)).to.equal(this.holder.address);
+            expect(await this.token.getVotes(this.holder)).to.equal(supply);
+            expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(0n);
+            await mine();
+            expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(supply);
           });
 
           it('delegation without balance', async function () {
-            expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS);
+            expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress);
 
-            const { receipt } = await this.token.delegate(holder, { from: holder });
-            expectEvent(receipt, 'DelegateChanged', {
-              delegator: holder,
-              fromDelegate: ZERO_ADDRESS,
-              toDelegate: holder,
-            });
-            expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
+            await expect(this.token.connect(this.holder).delegate(this.holder))
+              .to.emit(this.token, 'DelegateChanged')
+              .withArgs(this.holder.address, ethers.ZeroAddress, this.holder.address)
+              .to.not.emit(this.token, 'DelegateVotesChanged');
 
-            expect(await this.token.delegates(holder)).to.be.equal(holder);
+            expect(await this.token.delegates(this.holder)).to.equal(this.holder.address);
           });
         });
 
         describe('with signature', function () {
-          const delegator = Wallet.generate();
-          const delegatorAddress = web3.utils.toChecksumAddress(delegator.getAddressString());
-          const nonce = 0;
-
-          const buildData = (contract, message) =>
-            getDomain(contract).then(domain => ({
-              primaryType: 'Delegation',
-              types: { EIP712Domain: domainType(domain), Delegation },
-              domain,
-              message,
-            }));
+          const nonce = 0n;
 
           beforeEach(async function () {
-            await this.token.$_mint(delegatorAddress, supply);
+            await this.token.$_mint(this.holder, supply);
           });
 
           it('accept signed delegation', async function () {
-            const { v, r, s } = await buildData(this.token, {
-              delegatee: delegatorAddress,
-              nonce,
-              expiry: MAX_UINT256,
-            }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data })));
-
-            expect(await this.token.delegates(delegatorAddress)).to.be.equal(ZERO_ADDRESS);
-
-            const { receipt } = await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s);
-            const timepoint = await clockFromReceipt[mode](receipt);
-
-            expectEvent(receipt, 'DelegateChanged', {
-              delegator: delegatorAddress,
-              fromDelegate: ZERO_ADDRESS,
-              toDelegate: delegatorAddress,
-            });
-            expectEvent(receipt, 'DelegateVotesChanged', {
-              delegate: delegatorAddress,
-              previousVotes: '0',
-              newVotes: supply,
-            });
-
-            expect(await this.token.delegates(delegatorAddress)).to.be.equal(delegatorAddress);
-
-            expect(await this.token.getVotes(delegatorAddress)).to.be.bignumber.equal(supply);
-            expect(await this.token.getPastVotes(delegatorAddress, timepoint - 1)).to.be.bignumber.equal('0');
-            await time.advanceBlock();
-            expect(await this.token.getPastVotes(delegatorAddress, timepoint)).to.be.bignumber.equal(supply);
+            const { r, s, v } = await this.holder
+              .signTypedData(
+                this.domain,
+                { Delegation },
+                {
+                  delegatee: this.holder.address,
+                  nonce,
+                  expiry: ethers.MaxUint256,
+                },
+              )
+              .then(ethers.Signature.from);
+
+            expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress);
+
+            const tx = await this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s);
+            const timepoint = await time.clockFromReceipt[mode](tx);
+
+            await expect(tx)
+              .to.emit(this.token, 'DelegateChanged')
+              .withArgs(this.holder.address, ethers.ZeroAddress, this.holder.address)
+              .to.emit(this.token, 'DelegateVotesChanged')
+              .withArgs(this.holder.address, 0n, supply);
+
+            expect(await this.token.delegates(this.holder)).to.equal(this.holder.address);
+
+            expect(await this.token.getVotes(this.holder)).to.equal(supply);
+            expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(0n);
+            await mine();
+            expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(supply);
           });
 
           it('rejects reused signature', async function () {
-            const { v, r, s } = await buildData(this.token, {
-              delegatee: delegatorAddress,
-              nonce,
-              expiry: MAX_UINT256,
-            }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data })));
-
-            await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s);
-
-            await expectRevertCustomError(
-              this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s),
-              'InvalidAccountNonce',
-              [delegatorAddress, nonce + 1],
-            );
+            const { r, s, v } = await this.holder
+              .signTypedData(
+                this.domain,
+                { Delegation },
+                {
+                  delegatee: this.holder.address,
+                  nonce,
+                  expiry: ethers.MaxUint256,
+                },
+              )
+              .then(ethers.Signature.from);
+
+            await this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s);
+
+            await expect(this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s))
+              .to.be.revertedWithCustomError(this.token, 'InvalidAccountNonce')
+              .withArgs(this.holder.address, nonce + 1n);
           });
 
           it('rejects bad delegatee', async function () {
-            const { v, r, s } = await buildData(this.token, {
-              delegatee: delegatorAddress,
-              nonce,
-              expiry: MAX_UINT256,
-            }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data })));
-
-            const receipt = await this.token.delegateBySig(holderDelegatee, nonce, MAX_UINT256, v, r, s);
-            const { args } = receipt.logs.find(({ event }) => event == 'DelegateChanged');
-            expect(args.delegator).to.not.be.equal(delegatorAddress);
-            expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS);
-            expect(args.toDelegate).to.be.equal(holderDelegatee);
+            const { r, s, v } = await this.holder
+              .signTypedData(
+                this.domain,
+                { Delegation },
+                {
+                  delegatee: this.holder.address,
+                  nonce,
+                  expiry: ethers.MaxUint256,
+                },
+              )
+              .then(ethers.Signature.from);
+
+            const tx = await this.token.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s);
+
+            const { args } = await tx
+              .wait()
+              .then(receipt => receipt.logs.find(event => event.fragment.name == 'DelegateChanged'));
+            expect(args[0]).to.not.equal(this.holder.address);
+            expect(args[1]).to.equal(ethers.ZeroAddress);
+            expect(args[2]).to.equal(this.delegatee.address);
           });
 
           it('rejects bad nonce', async function () {
-            const sig = await buildData(this.token, {
-              delegatee: delegatorAddress,
-              nonce,
-              expiry: MAX_UINT256,
-            }).then(data => ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }));
-            const { r, s, v } = fromRpcSig(sig);
-
-            const domain = await getDomain(this.token);
-            const typedMessage = {
-              primaryType: 'Delegation',
-              types: { EIP712Domain: domainType(domain), Delegation },
-              domain,
-              message: { delegatee: delegatorAddress, nonce: nonce + 1, expiry: MAX_UINT256 },
-            };
-
-            await expectRevertCustomError(
-              this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s),
-              'InvalidAccountNonce',
-              [ethSigUtil.recoverTypedSignature({ data: typedMessage, sig }), nonce],
+            const { r, s, v, serialized } = await this.holder
+              .signTypedData(
+                this.domain,
+                { Delegation },
+                {
+                  delegatee: this.holder.address,
+                  nonce,
+                  expiry: ethers.MaxUint256,
+                },
+              )
+              .then(ethers.Signature.from);
+
+            const recovered = ethers.verifyTypedData(
+              this.domain,
+              { Delegation },
+              {
+                delegatee: this.holder.address,
+                nonce: nonce + 1n,
+                expiry: ethers.MaxUint256,
+              },
+              serialized,
             );
+
+            await expect(this.token.delegateBySig(this.holder, nonce + 1n, ethers.MaxUint256, v, r, s))
+              .to.be.revertedWithCustomError(this.token, 'InvalidAccountNonce')
+              .withArgs(recovered, nonce);
           });
 
           it('rejects expired permit', async function () {
-            const expiry = (await time.latest()) - time.duration.weeks(1);
-            const { v, r, s } = await buildData(this.token, {
-              delegatee: delegatorAddress,
-              nonce,
-              expiry,
-            }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data })));
-
-            await expectRevertCustomError(
-              this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s),
-              'VotesExpiredSignature',
-              [expiry],
-            );
+            const expiry = (await time.clock.timestamp()) - time.duration.weeks(1);
+
+            const { r, s, v } = await this.holder
+              .signTypedData(
+                this.domain,
+                { Delegation },
+                {
+                  delegatee: this.holder.address,
+                  nonce,
+                  expiry,
+                },
+              )
+              .then(ethers.Signature.from);
+
+            await expect(this.token.delegateBySig(this.holder, nonce, expiry, v, r, s))
+              .to.be.revertedWithCustomError(this.token, 'VotesExpiredSignature')
+              .withArgs(expiry);
           });
         });
       });
 
       describe('change delegation', function () {
         beforeEach(async function () {
-          await this.token.$_mint(holder, supply);
-          await this.token.delegate(holder, { from: holder });
+          await this.token.$_mint(this.holder, supply);
+          await this.token.connect(this.holder).delegate(this.holder);
         });
 
         it('call', async function () {
-          expect(await this.token.delegates(holder)).to.be.equal(holder);
-
-          const { receipt } = await this.token.delegate(holderDelegatee, { from: holder });
-          const timepoint = await clockFromReceipt[mode](receipt);
-
-          expectEvent(receipt, 'DelegateChanged', {
-            delegator: holder,
-            fromDelegate: holder,
-            toDelegate: holderDelegatee,
-          });
-          expectEvent(receipt, 'DelegateVotesChanged', {
-            delegate: holder,
-            previousVotes: supply,
-            newVotes: '0',
-          });
-          expectEvent(receipt, 'DelegateVotesChanged', {
-            delegate: holderDelegatee,
-            previousVotes: '0',
-            newVotes: supply,
-          });
-
-          expect(await this.token.delegates(holder)).to.be.equal(holderDelegatee);
-
-          expect(await this.token.getVotes(holder)).to.be.bignumber.equal('0');
-          expect(await this.token.getVotes(holderDelegatee)).to.be.bignumber.equal(supply);
-          expect(await this.token.getPastVotes(holder, timepoint - 1)).to.be.bignumber.equal(supply);
-          expect(await this.token.getPastVotes(holderDelegatee, timepoint - 1)).to.be.bignumber.equal('0');
-          await time.advanceBlock();
-          expect(await this.token.getPastVotes(holder, timepoint)).to.be.bignumber.equal('0');
-          expect(await this.token.getPastVotes(holderDelegatee, timepoint)).to.be.bignumber.equal(supply);
+          expect(await this.token.delegates(this.holder)).to.equal(this.holder.address);
+
+          const tx = await this.token.connect(this.holder).delegate(this.delegatee);
+          const timepoint = await time.clockFromReceipt[mode](tx);
+
+          await expect(tx)
+            .to.emit(this.token, 'DelegateChanged')
+            .withArgs(this.holder.address, this.holder.address, this.delegatee.address)
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.holder.address, supply, 0n)
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.delegatee.address, 0n, supply);
+
+          expect(await this.token.delegates(this.holder)).to.equal(this.delegatee.address);
+
+          expect(await this.token.getVotes(this.holder)).to.equal(0n);
+          expect(await this.token.getVotes(this.delegatee)).to.equal(supply);
+          expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(supply);
+          expect(await this.token.getPastVotes(this.delegatee, timepoint - 1n)).to.equal(0n);
+          await mine();
+          expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(0n);
+          expect(await this.token.getPastVotes(this.delegatee, timepoint)).to.equal(supply);
         });
       });
 
       describe('transfers', function () {
         beforeEach(async function () {
-          await this.token.$_mint(holder, supply);
+          await this.token.$_mint(this.holder, supply);
         });
 
         it('no delegation', async function () {
-          const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
-          expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
-          expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
+          await expect(this.token.connect(this.holder).transfer(this.recipient, 1n))
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, 1n)
+            .to.not.emit(this.token, 'DelegateVotesChanged');
 
-          this.holderVotes = '0';
-          this.recipientVotes = '0';
+          this.holderVotes = 0n;
+          this.recipientVotes = 0n;
         });
 
         it('sender delegation', async function () {
-          await this.token.delegate(holder, { from: holder });
-
-          const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
-          expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
-          expectEvent(receipt, 'DelegateVotesChanged', {
-            delegate: holder,
-            previousVotes: supply,
-            newVotes: supply.subn(1),
-          });
-
-          const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
-          expect(
-            receipt.logs
-              .filter(({ event }) => event == 'DelegateVotesChanged')
-              .every(({ logIndex }) => transferLogIndex < logIndex),
-          ).to.be.equal(true);
-
-          this.holderVotes = supply.subn(1);
-          this.recipientVotes = '0';
+          await this.token.connect(this.holder).delegate(this.holder);
+
+          const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n);
+          await expect(tx)
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, 1n)
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.holder.address, supply, supply - 1n);
+
+          const { logs } = await tx.wait();
+          const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
+          for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
+            expect(event.index).to.lt(index);
+          }
+
+          this.holderVotes = supply - 1n;
+          this.recipientVotes = 0n;
         });
 
         it('receiver delegation', async function () {
-          await this.token.delegate(recipient, { from: recipient });
-
-          const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
-          expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
-          expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousVotes: '0', newVotes: '1' });
-
-          const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
-          expect(
-            receipt.logs
-              .filter(({ event }) => event == 'DelegateVotesChanged')
-              .every(({ logIndex }) => transferLogIndex < logIndex),
-          ).to.be.equal(true);
-
-          this.holderVotes = '0';
-          this.recipientVotes = '1';
+          await this.token.connect(this.recipient).delegate(this.recipient);
+
+          const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n);
+          await expect(tx)
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, 1n)
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.recipient.address, 0n, 1n);
+
+          const { logs } = await tx.wait();
+          const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
+          for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
+            expect(event.index).to.lt(index);
+          }
+
+          this.holderVotes = 0n;
+          this.recipientVotes = 1n;
         });
 
         it('full delegation', async function () {
-          await this.token.delegate(holder, { from: holder });
-          await this.token.delegate(recipient, { from: recipient });
-
-          const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
-          expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
-          expectEvent(receipt, 'DelegateVotesChanged', {
-            delegate: holder,
-            previousVotes: supply,
-            newVotes: supply.subn(1),
-          });
-          expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousVotes: '0', newVotes: '1' });
-
-          const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
-          expect(
-            receipt.logs
-              .filter(({ event }) => event == 'DelegateVotesChanged')
-              .every(({ logIndex }) => transferLogIndex < logIndex),
-          ).to.be.equal(true);
-
-          this.holderVotes = supply.subn(1);
-          this.recipientVotes = '1';
+          await this.token.connect(this.holder).delegate(this.holder);
+          await this.token.connect(this.recipient).delegate(this.recipient);
+
+          const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n);
+          await expect(tx)
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, 1n)
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.holder.address, supply, supply - 1n)
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.recipient.address, 0n, 1n);
+
+          const { logs } = await tx.wait();
+          const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
+          for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
+            expect(event.index).to.lt(index);
+          }
+
+          this.holderVotes = supply - 1n;
+          this.recipientVotes = 1n;
         });
 
         afterEach(async function () {
-          expect(await this.token.getVotes(holder)).to.be.bignumber.equal(this.holderVotes);
-          expect(await this.token.getVotes(recipient)).to.be.bignumber.equal(this.recipientVotes);
+          expect(await this.token.getVotes(this.holder)).to.equal(this.holderVotes);
+          expect(await this.token.getVotes(this.recipient)).to.equal(this.recipientVotes);
 
           // need to advance 2 blocks to see the effect of a transfer on "getPastVotes"
-          const timepoint = await clock[mode]();
-          await time.advanceBlock();
-          expect(await this.token.getPastVotes(holder, timepoint)).to.be.bignumber.equal(this.holderVotes);
-          expect(await this.token.getPastVotes(recipient, timepoint)).to.be.bignumber.equal(this.recipientVotes);
+          const timepoint = await time.clock[mode]();
+          await mine();
+          expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(this.holderVotes);
+          expect(await this.token.getPastVotes(this.recipient, timepoint)).to.equal(this.recipientVotes);
         });
       });
 
       // The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js.
       describe('Compound test suite', function () {
         beforeEach(async function () {
-          await this.token.$_mint(holder, supply);
+          await this.token.$_mint(this.holder, supply);
         });
 
         describe('balanceOf', function () {
           it('grants to initial account', async function () {
-            expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('10000000000000000000000000');
+            expect(await this.token.balanceOf(this.holder)).to.equal(supply);
           });
         });
 
         describe('numCheckpoints', function () {
           it('returns the number of checkpoints for a delegate', async function () {
-            await this.token.transfer(recipient, '100', { from: holder }); //give an account a few tokens for readability
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0');
-
-            const t1 = await this.token.delegate(other1, { from: recipient });
-            t1.timepoint = await clockFromReceipt[mode](t1.receipt);
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1');
-
-            const t2 = await this.token.transfer(other2, 10, { from: recipient });
-            t2.timepoint = await clockFromReceipt[mode](t2.receipt);
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2');
-
-            const t3 = await this.token.transfer(other2, 10, { from: recipient });
-            t3.timepoint = await clockFromReceipt[mode](t3.receipt);
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('3');
-
-            const t4 = await this.token.transfer(recipient, 20, { from: holder });
-            t4.timepoint = await clockFromReceipt[mode](t4.receipt);
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('4');
-
-            expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([t1.timepoint.toString(), '100']);
-            expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([t2.timepoint.toString(), '90']);
-            expect(await this.token.checkpoints(other1, 2)).to.be.deep.equal([t3.timepoint.toString(), '80']);
-            expect(await this.token.checkpoints(other1, 3)).to.be.deep.equal([t4.timepoint.toString(), '100']);
-
-            await time.advanceBlock();
-            expect(await this.token.getPastVotes(other1, t1.timepoint)).to.be.bignumber.equal('100');
-            expect(await this.token.getPastVotes(other1, t2.timepoint)).to.be.bignumber.equal('90');
-            expect(await this.token.getPastVotes(other1, t3.timepoint)).to.be.bignumber.equal('80');
-            expect(await this.token.getPastVotes(other1, t4.timepoint)).to.be.bignumber.equal('100');
+            await this.token.connect(this.holder).transfer(this.recipient, 100n); //give an account a few tokens for readability
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(0n);
+
+            const t1 = await this.token.connect(this.recipient).delegate(this.other1);
+            t1.timepoint = await time.clockFromReceipt[mode](t1);
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(1n);
+
+            const t2 = await this.token.connect(this.recipient).transfer(this.other2, 10);
+            t2.timepoint = await time.clockFromReceipt[mode](t2);
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(2n);
+
+            const t3 = await this.token.connect(this.recipient).transfer(this.other2, 10);
+            t3.timepoint = await time.clockFromReceipt[mode](t3);
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(3n);
+
+            const t4 = await this.token.connect(this.holder).transfer(this.recipient, 20);
+            t4.timepoint = await time.clockFromReceipt[mode](t4);
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(4n);
+
+            expect(await this.token.checkpoints(this.other1, 0n)).to.deep.equal([t1.timepoint, 100n]);
+            expect(await this.token.checkpoints(this.other1, 1n)).to.deep.equal([t2.timepoint, 90n]);
+            expect(await this.token.checkpoints(this.other1, 2n)).to.deep.equal([t3.timepoint, 80n]);
+            expect(await this.token.checkpoints(this.other1, 3n)).to.deep.equal([t4.timepoint, 100n]);
+            await mine();
+            expect(await this.token.getPastVotes(this.other1, t1.timepoint)).to.equal(100n);
+            expect(await this.token.getPastVotes(this.other1, t2.timepoint)).to.equal(90n);
+            expect(await this.token.getPastVotes(this.other1, t3.timepoint)).to.equal(80n);
+            expect(await this.token.getPastVotes(this.other1, t4.timepoint)).to.equal(100n);
           });
 
           it('does not add more than one checkpoint in a block', async function () {
-            await this.token.transfer(recipient, '100', { from: holder });
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0');
+            await this.token.connect(this.holder).transfer(this.recipient, 100n);
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(0n);
 
             const [t1, t2, t3] = await batchInBlock([
-              () => this.token.delegate(other1, { from: recipient, gas: 200000 }),
-              () => this.token.transfer(other2, 10, { from: recipient, gas: 200000 }),
-              () => this.token.transfer(other2, 10, { from: recipient, gas: 200000 }),
+              () => this.token.connect(this.recipient).delegate(this.other1, { gasLimit: 200000 }),
+              () => this.token.connect(this.recipient).transfer(this.other2, 10n, { gasLimit: 200000 }),
+              () => this.token.connect(this.recipient).transfer(this.other2, 10n, { gasLimit: 200000 }),
             ]);
-            t1.timepoint = await clockFromReceipt[mode](t1.receipt);
-            t2.timepoint = await clockFromReceipt[mode](t2.receipt);
-            t3.timepoint = await clockFromReceipt[mode](t3.receipt);
+            t1.timepoint = await time.clockFromReceipt[mode](t1);
+            t2.timepoint = await time.clockFromReceipt[mode](t2);
+            t3.timepoint = await time.clockFromReceipt[mode](t3);
 
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1');
-            expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([t1.timepoint.toString(), '80']);
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(1);
+            expect(await this.token.checkpoints(this.other1, 0n)).to.be.deep.equal([t1.timepoint, 80n]);
 
-            const t4 = await this.token.transfer(recipient, 20, { from: holder });
-            t4.timepoint = await clockFromReceipt[mode](t4.receipt);
+            const t4 = await this.token.connect(this.holder).transfer(this.recipient, 20n);
+            t4.timepoint = await time.clockFromReceipt[mode](t4);
 
-            expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2');
-            expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([t4.timepoint.toString(), '100']);
+            expect(await this.token.numCheckpoints(this.other1)).to.equal(2n);
+            expect(await this.token.checkpoints(this.other1, 1n)).to.be.deep.equal([t4.timepoint, 100n]);
           });
         });
 
         describe('getPastVotes', function () {
           it('reverts if block number >= current block', async function () {
             const clock = await this.token.clock();
-            await expectRevertCustomError(this.token.getPastVotes(other1, 5e10), 'ERC5805FutureLookup', [5e10, clock]);
+            await expect(this.token.getPastVotes(this.other1, 50_000_000_000n))
+              .to.be.revertedWithCustomError(this.token, 'ERC5805FutureLookup')
+              .withArgs(50_000_000_000n, clock);
           });
 
           it('returns 0 if there are no checkpoints', async function () {
-            expect(await this.token.getPastVotes(other1, 0)).to.be.bignumber.equal('0');
+            expect(await this.token.getPastVotes(this.other1, 0n)).to.equal(0n);
           });
 
           it('returns the latest block if >= last checkpoint block', async function () {
-            const { receipt } = await this.token.delegate(other1, { from: holder });
-            const timepoint = await clockFromReceipt[mode](receipt);
-            await time.advanceBlock();
-            await time.advanceBlock();
+            const tx = await this.token.connect(this.holder).delegate(this.other1);
+            const timepoint = await time.clockFromReceipt[mode](tx);
+            await mine(2);
 
-            expect(await this.token.getPastVotes(other1, timepoint)).to.be.bignumber.equal(
-              '10000000000000000000000000',
-            );
-            expect(await this.token.getPastVotes(other1, timepoint + 1)).to.be.bignumber.equal(
-              '10000000000000000000000000',
-            );
+            expect(await this.token.getPastVotes(this.other1, timepoint)).to.equal(supply);
+            expect(await this.token.getPastVotes(this.other1, timepoint + 1n)).to.equal(supply);
           });
 
           it('returns zero if < first checkpoint block', async function () {
-            await time.advanceBlock();
-            const { receipt } = await this.token.delegate(other1, { from: holder });
-            const timepoint = await clockFromReceipt[mode](receipt);
-            await time.advanceBlock();
-            await time.advanceBlock();
-
-            expect(await this.token.getPastVotes(other1, timepoint - 1)).to.be.bignumber.equal('0');
-            expect(await this.token.getPastVotes(other1, timepoint + 1)).to.be.bignumber.equal(
-              '10000000000000000000000000',
-            );
+            await mine();
+            const tx = await this.token.connect(this.holder).delegate(this.other1);
+            const timepoint = await time.clockFromReceipt[mode](tx);
+            await mine(2);
+
+            expect(await this.token.getPastVotes(this.other1, timepoint - 1n)).to.equal(0n);
+            expect(await this.token.getPastVotes(this.other1, timepoint + 1n)).to.equal(supply);
           });
 
           it('generally returns the voting balance at the appropriate checkpoint', async function () {
-            const t1 = await this.token.delegate(other1, { from: holder });
-            await time.advanceBlock();
-            await time.advanceBlock();
-            const t2 = await this.token.transfer(other2, 10, { from: holder });
-            await time.advanceBlock();
-            await time.advanceBlock();
-            const t3 = await this.token.transfer(other2, 10, { from: holder });
-            await time.advanceBlock();
-            await time.advanceBlock();
-            const t4 = await this.token.transfer(holder, 20, { from: other2 });
-            await time.advanceBlock();
-            await time.advanceBlock();
-
-            t1.timepoint = await clockFromReceipt[mode](t1.receipt);
-            t2.timepoint = await clockFromReceipt[mode](t2.receipt);
-            t3.timepoint = await clockFromReceipt[mode](t3.receipt);
-            t4.timepoint = await clockFromReceipt[mode](t4.receipt);
-
-            expect(await this.token.getPastVotes(other1, t1.timepoint - 1)).to.be.bignumber.equal('0');
-            expect(await this.token.getPastVotes(other1, t1.timepoint)).to.be.bignumber.equal(
-              '10000000000000000000000000',
-            );
-            expect(await this.token.getPastVotes(other1, t1.timepoint + 1)).to.be.bignumber.equal(
-              '10000000000000000000000000',
-            );
-            expect(await this.token.getPastVotes(other1, t2.timepoint)).to.be.bignumber.equal(
-              '9999999999999999999999990',
-            );
-            expect(await this.token.getPastVotes(other1, t2.timepoint + 1)).to.be.bignumber.equal(
-              '9999999999999999999999990',
-            );
-            expect(await this.token.getPastVotes(other1, t3.timepoint)).to.be.bignumber.equal(
-              '9999999999999999999999980',
-            );
-            expect(await this.token.getPastVotes(other1, t3.timepoint + 1)).to.be.bignumber.equal(
-              '9999999999999999999999980',
-            );
-            expect(await this.token.getPastVotes(other1, t4.timepoint)).to.be.bignumber.equal(
-              '10000000000000000000000000',
-            );
-            expect(await this.token.getPastVotes(other1, t4.timepoint + 1)).to.be.bignumber.equal(
-              '10000000000000000000000000',
-            );
+            const t1 = await this.token.connect(this.holder).delegate(this.other1);
+            await mine(2);
+            const t2 = await this.token.connect(this.holder).transfer(this.other2, 10);
+            await mine(2);
+            const t3 = await this.token.connect(this.holder).transfer(this.other2, 10);
+            await mine(2);
+            const t4 = await this.token.connect(this.other2).transfer(this.holder, 20);
+            await mine(2);
+
+            t1.timepoint = await time.clockFromReceipt[mode](t1);
+            t2.timepoint = await time.clockFromReceipt[mode](t2);
+            t3.timepoint = await time.clockFromReceipt[mode](t3);
+            t4.timepoint = await time.clockFromReceipt[mode](t4);
+
+            expect(await this.token.getPastVotes(this.other1, t1.timepoint - 1n)).to.equal(0n);
+            expect(await this.token.getPastVotes(this.other1, t1.timepoint)).to.equal(supply);
+            expect(await this.token.getPastVotes(this.other1, t1.timepoint + 1n)).to.equal(supply);
+            expect(await this.token.getPastVotes(this.other1, t2.timepoint)).to.equal(supply - 10n);
+            expect(await this.token.getPastVotes(this.other1, t2.timepoint + 1n)).to.equal(supply - 10n);
+            expect(await this.token.getPastVotes(this.other1, t3.timepoint)).to.equal(supply - 20n);
+            expect(await this.token.getPastVotes(this.other1, t3.timepoint + 1n)).to.equal(supply - 20n);
+            expect(await this.token.getPastVotes(this.other1, t4.timepoint)).to.equal(supply);
+            expect(await this.token.getPastVotes(this.other1, t4.timepoint + 1n)).to.equal(supply);
           });
         });
       });
 
       describe('getPastTotalSupply', function () {
         beforeEach(async function () {
-          await this.token.delegate(holder, { from: holder });
+          await this.token.connect(this.holder).delegate(this.holder);
         });
 
         it('reverts if block number >= current block', async function () {
           const clock = await this.token.clock();
-          await expectRevertCustomError(this.token.getPastTotalSupply(5e10), 'ERC5805FutureLookup', [5e10, clock]);
+          await expect(this.token.getPastTotalSupply(50_000_000_000n))
+            .to.be.revertedWithCustomError(this.token, 'ERC5805FutureLookup')
+            .withArgs(50_000_000_000n, clock);
         });
 
         it('returns 0 if there are no checkpoints', async function () {
-          expect(await this.token.getPastTotalSupply(0)).to.be.bignumber.equal('0');
+          expect(await this.token.getPastTotalSupply(0n)).to.equal(0n);
         });
 
         it('returns the latest block if >= last checkpoint block', async function () {
-          const { receipt } = await this.token.$_mint(holder, supply);
-          const timepoint = await clockFromReceipt[mode](receipt);
-          await time.advanceBlock();
-          await time.advanceBlock();
+          const tx = await this.token.$_mint(this.holder, supply);
+          const timepoint = await time.clockFromReceipt[mode](tx);
+          await mine(2);
 
-          expect(await this.token.getPastTotalSupply(timepoint)).to.be.bignumber.equal(supply);
-          expect(await this.token.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal(supply);
+          expect(await this.token.getPastTotalSupply(timepoint)).to.equal(supply);
+          expect(await this.token.getPastTotalSupply(timepoint + 1n)).to.equal(supply);
         });
 
         it('returns zero if < first checkpoint block', async function () {
-          await time.advanceBlock();
-          const { receipt } = await this.token.$_mint(holder, supply);
-          const timepoint = await clockFromReceipt[mode](receipt);
-          await time.advanceBlock();
-          await time.advanceBlock();
-
-          expect(await this.token.getPastTotalSupply(timepoint - 1)).to.be.bignumber.equal('0');
-          expect(await this.token.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal(
-            '10000000000000000000000000',
-          );
+          await mine();
+          const tx = await this.token.$_mint(this.holder, supply);
+          const timepoint = await time.clockFromReceipt[mode](tx);
+          await mine(2);
+
+          expect(await this.token.getPastTotalSupply(timepoint - 1n)).to.equal(0n);
+          expect(await this.token.getPastTotalSupply(timepoint + 1n)).to.equal(supply);
         });
 
         it('generally returns the voting balance at the appropriate checkpoint', async function () {
-          const t1 = await this.token.$_mint(holder, supply);
-          await time.advanceBlock();
-          await time.advanceBlock();
-          const t2 = await this.token.$_burn(holder, 10);
-          await time.advanceBlock();
-          await time.advanceBlock();
-          const t3 = await this.token.$_burn(holder, 10);
-          await time.advanceBlock();
-          await time.advanceBlock();
-          const t4 = await this.token.$_mint(holder, 20);
-          await time.advanceBlock();
-          await time.advanceBlock();
-
-          t1.timepoint = await clockFromReceipt[mode](t1.receipt);
-          t2.timepoint = await clockFromReceipt[mode](t2.receipt);
-          t3.timepoint = await clockFromReceipt[mode](t3.receipt);
-          t4.timepoint = await clockFromReceipt[mode](t4.receipt);
-
-          expect(await this.token.getPastTotalSupply(t1.timepoint - 1)).to.be.bignumber.equal('0');
-          expect(await this.token.getPastTotalSupply(t1.timepoint)).to.be.bignumber.equal('10000000000000000000000000');
-          expect(await this.token.getPastTotalSupply(t1.timepoint + 1)).to.be.bignumber.equal(
-            '10000000000000000000000000',
-          );
-          expect(await this.token.getPastTotalSupply(t2.timepoint)).to.be.bignumber.equal('9999999999999999999999990');
-          expect(await this.token.getPastTotalSupply(t2.timepoint + 1)).to.be.bignumber.equal(
-            '9999999999999999999999990',
-          );
-          expect(await this.token.getPastTotalSupply(t3.timepoint)).to.be.bignumber.equal('9999999999999999999999980');
-          expect(await this.token.getPastTotalSupply(t3.timepoint + 1)).to.be.bignumber.equal(
-            '9999999999999999999999980',
-          );
-          expect(await this.token.getPastTotalSupply(t4.timepoint)).to.be.bignumber.equal('10000000000000000000000000');
-          expect(await this.token.getPastTotalSupply(t4.timepoint + 1)).to.be.bignumber.equal(
-            '10000000000000000000000000',
-          );
+          const t1 = await this.token.$_mint(this.holder, supply);
+          await mine(2);
+          const t2 = await this.token.$_burn(this.holder, 10n);
+          await mine(2);
+          const t3 = await this.token.$_burn(this.holder, 10n);
+          await mine(2);
+          const t4 = await this.token.$_mint(this.holder, 20n);
+          await mine(2);
+
+          t1.timepoint = await time.clockFromReceipt[mode](t1);
+          t2.timepoint = await time.clockFromReceipt[mode](t2);
+          t3.timepoint = await time.clockFromReceipt[mode](t3);
+          t4.timepoint = await time.clockFromReceipt[mode](t4);
+
+          expect(await this.token.getPastTotalSupply(t1.timepoint - 1n)).to.equal(0n);
+          expect(await this.token.getPastTotalSupply(t1.timepoint)).to.equal(supply);
+          expect(await this.token.getPastTotalSupply(t1.timepoint + 1n)).to.equal(supply);
+          expect(await this.token.getPastTotalSupply(t2.timepoint)).to.equal(supply - 10n);
+          expect(await this.token.getPastTotalSupply(t2.timepoint + 1n)).to.equal(supply - 10n);
+          expect(await this.token.getPastTotalSupply(t3.timepoint)).to.equal(supply - 20n);
+          expect(await this.token.getPastTotalSupply(t3.timepoint + 1n)).to.equal(supply - 20n);
+          expect(await this.token.getPastTotalSupply(t4.timepoint)).to.equal(supply);
+          expect(await this.token.getPastTotalSupply(t4.timepoint + 1n)).to.equal(supply);
         });
       });
     });

+ 136 - 125
test/token/ERC721/extensions/ERC721Votes.test.js

@@ -1,181 +1,192 @@
-/* eslint-disable */
-
-const { expectEvent, time } = require('@openzeppelin/test-helpers');
+const { ethers } = require('hardhat');
 const { expect } = require('chai');
+const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers');
 
-const { clock, clockFromReceipt } = require('../../../helpers/time');
+const { bigint: time } = require('../../../helpers/time');
 
 const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior');
 
-const MODES = {
-  blocknumber: artifacts.require('$ERC721Votes'),
+const TOKENS = [
+  { Token: '$ERC721Votes', mode: 'blocknumber' },
   // no timestamp mode for ERC721Votes yet
-};
+];
+
+const name = 'My Vote';
+const symbol = 'MTKN';
+const version = '1';
+const tokens = [ethers.parseEther('10000000'), 10n, 20n, 30n];
+
+describe('ERC721Votes', function () {
+  for (const { Token, mode } of TOKENS) {
+    const fixture = async () => {
+      // accounts is required by shouldBehaveLikeVotes
+      const accounts = await ethers.getSigners();
+      const [holder, recipient, other1, other2] = accounts;
 
-contract('ERC721Votes', function (accounts) {
-  const [account1, account2, other1, other2] = accounts;
+      const token = await ethers.deployContract(Token, [name, symbol, name, version]);
 
-  const name = 'My Vote';
-  const symbol = 'MTKN';
-  const version = '1';
-  const tokens = ['10000000000000000000000000', '10', '20', '30'].map(n => web3.utils.toBN(n));
+      return { accounts, holder, recipient, other1, other2, token };
+    };
 
-  for (const [mode, artifact] of Object.entries(MODES)) {
     describe(`vote with ${mode}`, function () {
       beforeEach(async function () {
-        this.votes = await artifact.new(name, symbol, name, version);
+        Object.assign(this, await loadFixture(fixture));
+        this.votes = this.token;
       });
 
       // includes ERC6372 behavior check
-      shouldBehaveLikeVotes(accounts, tokens, { mode, fungible: false });
+      shouldBehaveLikeVotes(tokens, { mode, fungible: false });
 
       describe('balanceOf', function () {
         beforeEach(async function () {
-          await this.votes.$_mint(account1, tokens[0]);
-          await this.votes.$_mint(account1, tokens[1]);
-          await this.votes.$_mint(account1, tokens[2]);
-          await this.votes.$_mint(account1, tokens[3]);
+          await this.votes.$_mint(this.holder, tokens[0]);
+          await this.votes.$_mint(this.holder, tokens[1]);
+          await this.votes.$_mint(this.holder, tokens[2]);
+          await this.votes.$_mint(this.holder, tokens[3]);
         });
 
         it('grants to initial account', async function () {
-          expect(await this.votes.balanceOf(account1)).to.be.bignumber.equal('4');
+          expect(await this.votes.balanceOf(this.holder)).to.equal(4n);
         });
       });
 
       describe('transfers', function () {
         beforeEach(async function () {
-          await this.votes.$_mint(account1, tokens[0]);
+          await this.votes.$_mint(this.holder, tokens[0]);
         });
 
         it('no delegation', async function () {
-          const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 });
-          expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] });
-          expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
+          await expect(this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]))
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, tokens[0])
+            .to.not.emit(this.token, 'DelegateVotesChanged');
 
-          this.account1Votes = '0';
-          this.account2Votes = '0';
+          this.holderVotes = 0n;
+          this.recipientVotes = 0n;
         });
 
         it('sender delegation', async function () {
-          await this.votes.delegate(account1, { from: account1 });
-
-          const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 });
-          expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] });
-          expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousVotes: '1', newVotes: '0' });
-
-          const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
-          expect(
-            receipt.logs
-              .filter(({ event }) => event == 'DelegateVotesChanged')
-              .every(({ logIndex }) => transferLogIndex < logIndex),
-          ).to.be.equal(true);
-
-          this.account1Votes = '0';
-          this.account2Votes = '0';
+          await this.votes.connect(this.holder).delegate(this.holder);
+
+          const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
+          await expect(tx)
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, tokens[0])
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.holder.address, 1n, 0n);
+
+          const { logs } = await tx.wait();
+          const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
+          for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
+            expect(event.index).to.lt(index);
+          }
+
+          this.holderVotes = 0n;
+          this.recipientVotes = 0n;
         });
 
         it('receiver delegation', async function () {
-          await this.votes.delegate(account2, { from: account2 });
-
-          const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 });
-          expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] });
-          expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousVotes: '0', newVotes: '1' });
-
-          const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
-          expect(
-            receipt.logs
-              .filter(({ event }) => event == 'DelegateVotesChanged')
-              .every(({ logIndex }) => transferLogIndex < logIndex),
-          ).to.be.equal(true);
-
-          this.account1Votes = '0';
-          this.account2Votes = '1';
+          await this.votes.connect(this.recipient).delegate(this.recipient);
+
+          const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
+          await expect(tx)
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, tokens[0])
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.recipient.address, 0n, 1n);
+
+          const { logs } = await tx.wait();
+          const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
+          for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
+            expect(event.index).to.lt(index);
+          }
+
+          this.holderVotes = 0n;
+          this.recipientVotes = 1n;
         });
 
         it('full delegation', async function () {
-          await this.votes.delegate(account1, { from: account1 });
-          await this.votes.delegate(account2, { from: account2 });
-
-          const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 });
-          expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] });
-          expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousVotes: '1', newVotes: '0' });
-          expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousVotes: '0', newVotes: '1' });
-
-          const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
-          expect(
-            receipt.logs
-              .filter(({ event }) => event == 'DelegateVotesChanged')
-              .every(({ logIndex }) => transferLogIndex < logIndex),
-          ).to.be.equal(true);
-
-          this.account1Votes = '0';
-          this.account2Votes = '1';
+          await this.votes.connect(this.holder).delegate(this.holder);
+          await this.votes.connect(this.recipient).delegate(this.recipient);
+
+          const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
+          await expect(tx)
+            .to.emit(this.token, 'Transfer')
+            .withArgs(this.holder.address, this.recipient.address, tokens[0])
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.holder.address, 1n, 0n)
+            .to.emit(this.token, 'DelegateVotesChanged')
+            .withArgs(this.recipient.address, 0n, 1n);
+
+          const { logs } = await tx.wait();
+          const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
+          for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
+            expect(event.index).to.lt(index);
+          }
+
+          this.holderVotes = 0;
+          this.recipientVotes = 1n;
         });
 
         it('returns the same total supply on transfers', async function () {
-          await this.votes.delegate(account1, { from: account1 });
+          await this.votes.connect(this.holder).delegate(this.holder);
 
-          const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 });
-          const timepoint = await clockFromReceipt[mode](receipt);
+          const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
+          const timepoint = await time.clockFromReceipt[mode](tx);
 
-          await time.advanceBlock();
-          await time.advanceBlock();
+          await mine(2);
 
-          expect(await this.votes.getPastTotalSupply(timepoint - 1)).to.be.bignumber.equal('1');
-          expect(await this.votes.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal('1');
+          expect(await this.votes.getPastTotalSupply(timepoint - 1n)).to.equal(1n);
+          expect(await this.votes.getPastTotalSupply(timepoint + 1n)).to.equal(1n);
 
-          this.account1Votes = '0';
-          this.account2Votes = '0';
+          this.holderVotes = 0n;
+          this.recipientVotes = 0n;
         });
 
         it('generally returns the voting balance at the appropriate checkpoint', async function () {
-          await this.votes.$_mint(account1, tokens[1]);
-          await this.votes.$_mint(account1, tokens[2]);
-          await this.votes.$_mint(account1, tokens[3]);
-
-          const total = await this.votes.balanceOf(account1);
-
-          const t1 = await this.votes.delegate(other1, { from: account1 });
-          await time.advanceBlock();
-          await time.advanceBlock();
-          const t2 = await this.votes.transferFrom(account1, other2, tokens[0], { from: account1 });
-          await time.advanceBlock();
-          await time.advanceBlock();
-          const t3 = await this.votes.transferFrom(account1, other2, tokens[2], { from: account1 });
-          await time.advanceBlock();
-          await time.advanceBlock();
-          const t4 = await this.votes.transferFrom(other2, account1, tokens[2], { from: other2 });
-          await time.advanceBlock();
-          await time.advanceBlock();
-
-          t1.timepoint = await clockFromReceipt[mode](t1.receipt);
-          t2.timepoint = await clockFromReceipt[mode](t2.receipt);
-          t3.timepoint = await clockFromReceipt[mode](t3.receipt);
-          t4.timepoint = await clockFromReceipt[mode](t4.receipt);
-
-          expect(await this.votes.getPastVotes(other1, t1.timepoint - 1)).to.be.bignumber.equal('0');
-          expect(await this.votes.getPastVotes(other1, t1.timepoint)).to.be.bignumber.equal(total);
-          expect(await this.votes.getPastVotes(other1, t1.timepoint + 1)).to.be.bignumber.equal(total);
-          expect(await this.votes.getPastVotes(other1, t2.timepoint)).to.be.bignumber.equal('3');
-          expect(await this.votes.getPastVotes(other1, t2.timepoint + 1)).to.be.bignumber.equal('3');
-          expect(await this.votes.getPastVotes(other1, t3.timepoint)).to.be.bignumber.equal('2');
-          expect(await this.votes.getPastVotes(other1, t3.timepoint + 1)).to.be.bignumber.equal('2');
-          expect(await this.votes.getPastVotes(other1, t4.timepoint)).to.be.bignumber.equal('3');
-          expect(await this.votes.getPastVotes(other1, t4.timepoint + 1)).to.be.bignumber.equal('3');
-
-          this.account1Votes = '0';
-          this.account2Votes = '0';
+          await this.votes.$_mint(this.holder, tokens[1]);
+          await this.votes.$_mint(this.holder, tokens[2]);
+          await this.votes.$_mint(this.holder, tokens[3]);
+
+          const total = await this.votes.balanceOf(this.holder);
+
+          const t1 = await this.votes.connect(this.holder).delegate(this.other1);
+          await mine(2);
+          const t2 = await this.votes.connect(this.holder).transferFrom(this.holder, this.other2, tokens[0]);
+          await mine(2);
+          const t3 = await this.votes.connect(this.holder).transferFrom(this.holder, this.other2, tokens[2]);
+          await mine(2);
+          const t4 = await this.votes.connect(this.other2).transferFrom(this.other2, this.holder, tokens[2]);
+          await mine(2);
+
+          t1.timepoint = await time.clockFromReceipt[mode](t1);
+          t2.timepoint = await time.clockFromReceipt[mode](t2);
+          t3.timepoint = await time.clockFromReceipt[mode](t3);
+          t4.timepoint = await time.clockFromReceipt[mode](t4);
+
+          expect(await this.votes.getPastVotes(this.other1, t1.timepoint - 1n)).to.equal(0n);
+          expect(await this.votes.getPastVotes(this.other1, t1.timepoint)).to.equal(total);
+          expect(await this.votes.getPastVotes(this.other1, t1.timepoint + 1n)).to.equal(total);
+          expect(await this.votes.getPastVotes(this.other1, t2.timepoint)).to.equal(3n);
+          expect(await this.votes.getPastVotes(this.other1, t2.timepoint + 1n)).to.equal(3n);
+          expect(await this.votes.getPastVotes(this.other1, t3.timepoint)).to.equal(2n);
+          expect(await this.votes.getPastVotes(this.other1, t3.timepoint + 1n)).to.equal(2n);
+          expect(await this.votes.getPastVotes(this.other1, t4.timepoint)).to.equal('3');
+          expect(await this.votes.getPastVotes(this.other1, t4.timepoint + 1n)).to.equal(3n);
+
+          this.holderVotes = 0n;
+          this.recipientVotes = 0n;
         });
 
         afterEach(async function () {
-          expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(this.account1Votes);
-          expect(await this.votes.getVotes(account2)).to.be.bignumber.equal(this.account2Votes);
+          expect(await this.votes.getVotes(this.holder)).to.equal(this.holderVotes);
+          expect(await this.votes.getVotes(this.recipient)).to.equal(this.recipientVotes);
 
           // need to advance 2 blocks to see the effect of a transfer on "getPastVotes"
-          const timepoint = await clock[mode]();
-          await time.advanceBlock();
-          expect(await this.votes.getPastVotes(account1, timepoint)).to.be.bignumber.equal(this.account1Votes);
-          expect(await this.votes.getPastVotes(account2, timepoint)).to.be.bignumber.equal(this.account2Votes);
+          const timepoint = await time.clock[mode]();
+          await mine();
+          expect(await this.votes.getPastVotes(this.holder, timepoint)).to.equal(this.holderVotes);
+          expect(await this.votes.getPastVotes(this.recipient, timepoint)).to.equal(this.recipientVotes);
         });
       });
     });

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.