ERC721Votes.test.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers');
  4. const time = require('../../../helpers/time');
  5. const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior');
  6. const TOKENS = [
  7. { Token: '$ERC721Votes', mode: 'blocknumber' },
  8. // no timestamp mode for ERC721Votes yet
  9. ];
  10. const name = 'My Vote';
  11. const symbol = 'MTKN';
  12. const version = '1';
  13. const tokens = [ethers.parseEther('10000000'), 10n, 20n, 30n];
  14. describe('ERC721Votes', function () {
  15. for (const { Token, mode } of TOKENS) {
  16. const fixture = async () => {
  17. // accounts is required by shouldBehaveLikeVotes
  18. const accounts = await ethers.getSigners();
  19. const [holder, recipient, other1, other2] = accounts;
  20. const token = await ethers.deployContract(Token, [name, symbol, name, version]);
  21. return { accounts, holder, recipient, other1, other2, token };
  22. };
  23. describe(`vote with ${mode}`, function () {
  24. beforeEach(async function () {
  25. Object.assign(this, await loadFixture(fixture));
  26. this.votes = this.token;
  27. });
  28. // includes ERC6372 behavior check
  29. shouldBehaveLikeVotes(tokens, { mode, fungible: false });
  30. describe('balanceOf', function () {
  31. beforeEach(async function () {
  32. await this.votes.$_mint(this.holder, tokens[0]);
  33. await this.votes.$_mint(this.holder, tokens[1]);
  34. await this.votes.$_mint(this.holder, tokens[2]);
  35. await this.votes.$_mint(this.holder, tokens[3]);
  36. });
  37. it('grants to initial account', async function () {
  38. expect(await this.votes.balanceOf(this.holder)).to.equal(4n);
  39. });
  40. });
  41. describe('transfers', function () {
  42. beforeEach(async function () {
  43. await this.votes.$_mint(this.holder, tokens[0]);
  44. });
  45. it('no delegation', async function () {
  46. await expect(this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]))
  47. .to.emit(this.token, 'Transfer')
  48. .withArgs(this.holder, this.recipient, tokens[0])
  49. .to.not.emit(this.token, 'DelegateVotesChanged');
  50. this.holderVotes = 0n;
  51. this.recipientVotes = 0n;
  52. });
  53. it('sender delegation', async function () {
  54. await this.votes.connect(this.holder).delegate(this.holder);
  55. const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
  56. await expect(tx)
  57. .to.emit(this.token, 'Transfer')
  58. .withArgs(this.holder, this.recipient, tokens[0])
  59. .to.emit(this.token, 'DelegateVotesChanged')
  60. .withArgs(this.holder, 1n, 0n);
  61. const { logs } = await tx.wait();
  62. const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
  63. for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
  64. expect(event.index).to.lt(index);
  65. }
  66. this.holderVotes = 0n;
  67. this.recipientVotes = 0n;
  68. });
  69. it('receiver delegation', async function () {
  70. await this.votes.connect(this.recipient).delegate(this.recipient);
  71. const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
  72. await expect(tx)
  73. .to.emit(this.token, 'Transfer')
  74. .withArgs(this.holder, this.recipient, tokens[0])
  75. .to.emit(this.token, 'DelegateVotesChanged')
  76. .withArgs(this.recipient, 0n, 1n);
  77. const { logs } = await tx.wait();
  78. const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
  79. for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
  80. expect(event.index).to.lt(index);
  81. }
  82. this.holderVotes = 0n;
  83. this.recipientVotes = 1n;
  84. });
  85. it('full delegation', async function () {
  86. await this.votes.connect(this.holder).delegate(this.holder);
  87. await this.votes.connect(this.recipient).delegate(this.recipient);
  88. const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
  89. await expect(tx)
  90. .to.emit(this.token, 'Transfer')
  91. .withArgs(this.holder, this.recipient, tokens[0])
  92. .to.emit(this.token, 'DelegateVotesChanged')
  93. .withArgs(this.holder, 1n, 0n)
  94. .to.emit(this.token, 'DelegateVotesChanged')
  95. .withArgs(this.recipient, 0n, 1n);
  96. const { logs } = await tx.wait();
  97. const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
  98. for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
  99. expect(event.index).to.lt(index);
  100. }
  101. this.holderVotes = 0;
  102. this.recipientVotes = 1n;
  103. });
  104. it('returns the same total supply on transfers', async function () {
  105. await this.votes.connect(this.holder).delegate(this.holder);
  106. const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]);
  107. const timepoint = await time.clockFromReceipt[mode](tx);
  108. await mine(2);
  109. expect(await this.votes.getPastTotalSupply(timepoint - 1n)).to.equal(1n);
  110. expect(await this.votes.getPastTotalSupply(timepoint + 1n)).to.equal(1n);
  111. this.holderVotes = 0n;
  112. this.recipientVotes = 0n;
  113. });
  114. it('generally returns the voting balance at the appropriate checkpoint', async function () {
  115. await this.votes.$_mint(this.holder, tokens[1]);
  116. await this.votes.$_mint(this.holder, tokens[2]);
  117. await this.votes.$_mint(this.holder, tokens[3]);
  118. const total = await this.votes.balanceOf(this.holder);
  119. const t1 = await this.votes.connect(this.holder).delegate(this.other1);
  120. await mine(2);
  121. const t2 = await this.votes.connect(this.holder).transferFrom(this.holder, this.other2, tokens[0]);
  122. await mine(2);
  123. const t3 = await this.votes.connect(this.holder).transferFrom(this.holder, this.other2, tokens[2]);
  124. await mine(2);
  125. const t4 = await this.votes.connect(this.other2).transferFrom(this.other2, this.holder, tokens[2]);
  126. await mine(2);
  127. t1.timepoint = await time.clockFromReceipt[mode](t1);
  128. t2.timepoint = await time.clockFromReceipt[mode](t2);
  129. t3.timepoint = await time.clockFromReceipt[mode](t3);
  130. t4.timepoint = await time.clockFromReceipt[mode](t4);
  131. expect(await this.votes.getPastVotes(this.other1, t1.timepoint - 1n)).to.equal(0n);
  132. expect(await this.votes.getPastVotes(this.other1, t1.timepoint)).to.equal(total);
  133. expect(await this.votes.getPastVotes(this.other1, t1.timepoint + 1n)).to.equal(total);
  134. expect(await this.votes.getPastVotes(this.other1, t2.timepoint)).to.equal(3n);
  135. expect(await this.votes.getPastVotes(this.other1, t2.timepoint + 1n)).to.equal(3n);
  136. expect(await this.votes.getPastVotes(this.other1, t3.timepoint)).to.equal(2n);
  137. expect(await this.votes.getPastVotes(this.other1, t3.timepoint + 1n)).to.equal(2n);
  138. expect(await this.votes.getPastVotes(this.other1, t4.timepoint)).to.equal('3');
  139. expect(await this.votes.getPastVotes(this.other1, t4.timepoint + 1n)).to.equal(3n);
  140. this.holderVotes = 0n;
  141. this.recipientVotes = 0n;
  142. });
  143. afterEach(async function () {
  144. expect(await this.votes.getVotes(this.holder)).to.equal(this.holderVotes);
  145. expect(await this.votes.getVotes(this.recipient)).to.equal(this.recipientVotes);
  146. // need to advance 2 blocks to see the effect of a transfer on "getPastVotes"
  147. const timepoint = await time.clock[mode]();
  148. await mine();
  149. expect(await this.votes.getPastVotes(this.holder, timepoint)).to.equal(this.holderVotes);
  150. expect(await this.votes.getPastVotes(this.recipient, timepoint)).to.equal(this.recipientVotes);
  151. });
  152. });
  153. });
  154. }
  155. });