123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- const { ethers } = require('hardhat');
- const { expect } = require('chai');
- const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers');
- const time = require('../../../helpers/time');
- const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior');
- 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;
- const token = await ethers.deployContract(Token, [name, symbol, name, version]);
- return { accounts, holder, recipient, other1, other2, token };
- };
- describe(`vote with ${mode}`, function () {
- beforeEach(async function () {
- Object.assign(this, await loadFixture(fixture));
- this.votes = this.token;
- });
- // includes ERC6372 behavior check
- shouldBehaveLikeVotes(tokens, { mode, fungible: false });
- describe('balanceOf', function () {
- beforeEach(async function () {
- 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(this.holder)).to.equal(4n);
- });
- });
- describe('transfers', function () {
- beforeEach(async function () {
- await this.votes.$_mint(this.holder, tokens[0]);
- });
- it('no delegation', async function () {
- await expect(this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]))
- .to.emit(this.token, 'Transfer')
- .withArgs(this.holder, this.recipient, tokens[0])
- .to.not.emit(this.token, 'DelegateVotesChanged');
- this.holderVotes = 0n;
- this.recipientVotes = 0n;
- });
- it('sender delegation', async function () {
- 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, this.recipient, tokens[0])
- .to.emit(this.token, 'DelegateVotesChanged')
- .withArgs(this.holder, 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.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, this.recipient, tokens[0])
- .to.emit(this.token, 'DelegateVotesChanged')
- .withArgs(this.recipient, 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.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, this.recipient, tokens[0])
- .to.emit(this.token, 'DelegateVotesChanged')
- .withArgs(this.holder, 1n, 0n)
- .to.emit(this.token, 'DelegateVotesChanged')
- .withArgs(this.recipient, 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.connect(this.holder).delegate(this.holder);
- const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
- const timepoint = await time.clockFromReceipt[mode](tx);
- await mine(2);
- expect(await this.votes.getPastTotalSupply(timepoint - 1n)).to.equal(1n);
- expect(await this.votes.getPastTotalSupply(timepoint + 1n)).to.equal(1n);
- this.holderVotes = 0n;
- this.recipientVotes = 0n;
- });
- it('generally returns the voting balance at the appropriate checkpoint', async function () {
- 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(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 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);
- });
- });
- });
- }
- });
|