ERC20Votes.test.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { getDomain, Delegation } = require('../../../helpers/eip712');
  5. const { batchInBlock } = require('../../../helpers/txpool');
  6. const time = require('../../../helpers/time');
  7. const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior');
  8. const TOKENS = [
  9. { Token: '$ERC20Votes', mode: 'blocknumber' },
  10. { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
  11. ];
  12. const name = 'My Token';
  13. const symbol = 'MTKN';
  14. const version = '1';
  15. const supply = ethers.parseEther('10000000');
  16. describe('ERC20Votes', function () {
  17. for (const { Token, mode } of TOKENS) {
  18. const fixture = async () => {
  19. // accounts is required by shouldBehaveLikeVotes
  20. const accounts = await ethers.getSigners();
  21. const [holder, recipient, delegatee, other1, other2] = accounts;
  22. const token = await ethers.deployContract(Token, [name, symbol, name, version]);
  23. const domain = await getDomain(token);
  24. return { accounts, holder, recipient, delegatee, other1, other2, token, domain };
  25. };
  26. describe(`vote with ${mode}`, function () {
  27. beforeEach(async function () {
  28. Object.assign(this, await loadFixture(fixture));
  29. this.votes = this.token;
  30. });
  31. // includes ERC6372 behavior check
  32. shouldBehaveLikeVotes([1, 17, 42], { mode, fungible: true });
  33. it('initial nonce is 0', async function () {
  34. expect(await this.token.nonces(this.holder)).to.equal(0n);
  35. });
  36. it('minting restriction', async function () {
  37. const value = 2n ** 208n;
  38. await expect(this.token.$_mint(this.holder, value))
  39. .to.be.revertedWithCustomError(this.token, 'ERC20ExceededSafeSupply')
  40. .withArgs(value, value - 1n);
  41. });
  42. it('recent checkpoints', async function () {
  43. await this.token.connect(this.holder).delegate(this.holder);
  44. for (let i = 0; i < 6; i++) {
  45. await this.token.$_mint(this.holder, 1n);
  46. }
  47. const timepoint = await time.clock[mode]();
  48. expect(await this.token.numCheckpoints(this.holder)).to.equal(6n);
  49. // recent
  50. expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(5n);
  51. // non-recent
  52. expect(await this.token.getPastVotes(this.holder, timepoint - 6n)).to.equal(0n);
  53. });
  54. describe('set delegation', function () {
  55. describe('call', function () {
  56. it('delegation with balance', async function () {
  57. await this.token.$_mint(this.holder, supply);
  58. expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress);
  59. const tx = await this.token.connect(this.holder).delegate(this.holder);
  60. const timepoint = await time.clockFromReceipt[mode](tx);
  61. await expect(tx)
  62. .to.emit(this.token, 'DelegateChanged')
  63. .withArgs(this.holder, ethers.ZeroAddress, this.holder)
  64. .to.emit(this.token, 'DelegateVotesChanged')
  65. .withArgs(this.holder, 0n, supply);
  66. expect(await this.token.delegates(this.holder)).to.equal(this.holder);
  67. expect(await this.token.getVotes(this.holder)).to.equal(supply);
  68. expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(0n);
  69. await mine();
  70. expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(supply);
  71. });
  72. it('delegation without balance', async function () {
  73. expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress);
  74. await expect(this.token.connect(this.holder).delegate(this.holder))
  75. .to.emit(this.token, 'DelegateChanged')
  76. .withArgs(this.holder, ethers.ZeroAddress, this.holder)
  77. .to.not.emit(this.token, 'DelegateVotesChanged');
  78. expect(await this.token.delegates(this.holder)).to.equal(this.holder);
  79. });
  80. });
  81. describe('with signature', function () {
  82. const nonce = 0n;
  83. beforeEach(async function () {
  84. await this.token.$_mint(this.holder, supply);
  85. });
  86. it('accept signed delegation', async function () {
  87. const { r, s, v } = await this.holder
  88. .signTypedData(
  89. this.domain,
  90. { Delegation },
  91. {
  92. delegatee: this.holder.address,
  93. nonce,
  94. expiry: ethers.MaxUint256,
  95. },
  96. )
  97. .then(ethers.Signature.from);
  98. expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress);
  99. const tx = await this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s);
  100. const timepoint = await time.clockFromReceipt[mode](tx);
  101. await expect(tx)
  102. .to.emit(this.token, 'DelegateChanged')
  103. .withArgs(this.holder, ethers.ZeroAddress, this.holder)
  104. .to.emit(this.token, 'DelegateVotesChanged')
  105. .withArgs(this.holder, 0n, supply);
  106. expect(await this.token.delegates(this.holder)).to.equal(this.holder);
  107. expect(await this.token.getVotes(this.holder)).to.equal(supply);
  108. expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(0n);
  109. await mine();
  110. expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(supply);
  111. });
  112. it('rejects reused signature', async function () {
  113. const { r, s, v } = await this.holder
  114. .signTypedData(
  115. this.domain,
  116. { Delegation },
  117. {
  118. delegatee: this.holder.address,
  119. nonce,
  120. expiry: ethers.MaxUint256,
  121. },
  122. )
  123. .then(ethers.Signature.from);
  124. await this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s);
  125. await expect(this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s))
  126. .to.be.revertedWithCustomError(this.token, 'InvalidAccountNonce')
  127. .withArgs(this.holder, nonce + 1n);
  128. });
  129. it('rejects bad delegatee', async function () {
  130. const { r, s, v } = await this.holder
  131. .signTypedData(
  132. this.domain,
  133. { Delegation },
  134. {
  135. delegatee: this.holder.address,
  136. nonce,
  137. expiry: ethers.MaxUint256,
  138. },
  139. )
  140. .then(ethers.Signature.from);
  141. const tx = await this.token.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s);
  142. const { args } = await tx
  143. .wait()
  144. .then(receipt => receipt.logs.find(event => event.fragment.name == 'DelegateChanged'));
  145. expect(args[0]).to.not.equal(this.holder);
  146. expect(args[1]).to.equal(ethers.ZeroAddress);
  147. expect(args[2]).to.equal(this.delegatee);
  148. });
  149. it('rejects bad nonce', async function () {
  150. const { r, s, v, serialized } = await this.holder
  151. .signTypedData(
  152. this.domain,
  153. { Delegation },
  154. {
  155. delegatee: this.holder.address,
  156. nonce,
  157. expiry: ethers.MaxUint256,
  158. },
  159. )
  160. .then(ethers.Signature.from);
  161. const recovered = ethers.verifyTypedData(
  162. this.domain,
  163. { Delegation },
  164. {
  165. delegatee: this.holder.address,
  166. nonce: nonce + 1n,
  167. expiry: ethers.MaxUint256,
  168. },
  169. serialized,
  170. );
  171. await expect(this.token.delegateBySig(this.holder, nonce + 1n, ethers.MaxUint256, v, r, s))
  172. .to.be.revertedWithCustomError(this.token, 'InvalidAccountNonce')
  173. .withArgs(recovered, nonce);
  174. });
  175. it('rejects expired permit', async function () {
  176. const expiry = (await time.clock.timestamp()) - time.duration.weeks(1);
  177. const { r, s, v } = await this.holder
  178. .signTypedData(
  179. this.domain,
  180. { Delegation },
  181. {
  182. delegatee: this.holder.address,
  183. nonce,
  184. expiry,
  185. },
  186. )
  187. .then(ethers.Signature.from);
  188. await expect(this.token.delegateBySig(this.holder, nonce, expiry, v, r, s))
  189. .to.be.revertedWithCustomError(this.token, 'VotesExpiredSignature')
  190. .withArgs(expiry);
  191. });
  192. });
  193. });
  194. describe('change delegation', function () {
  195. beforeEach(async function () {
  196. await this.token.$_mint(this.holder, supply);
  197. await this.token.connect(this.holder).delegate(this.holder);
  198. });
  199. it('call', async function () {
  200. expect(await this.token.delegates(this.holder)).to.equal(this.holder);
  201. const tx = await this.token.connect(this.holder).delegate(this.delegatee);
  202. const timepoint = await time.clockFromReceipt[mode](tx);
  203. await expect(tx)
  204. .to.emit(this.token, 'DelegateChanged')
  205. .withArgs(this.holder, this.holder, this.delegatee)
  206. .to.emit(this.token, 'DelegateVotesChanged')
  207. .withArgs(this.holder, supply, 0n)
  208. .to.emit(this.token, 'DelegateVotesChanged')
  209. .withArgs(this.delegatee, 0n, supply);
  210. expect(await this.token.delegates(this.holder)).to.equal(this.delegatee);
  211. expect(await this.token.getVotes(this.holder)).to.equal(0n);
  212. expect(await this.token.getVotes(this.delegatee)).to.equal(supply);
  213. expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(supply);
  214. expect(await this.token.getPastVotes(this.delegatee, timepoint - 1n)).to.equal(0n);
  215. await mine();
  216. expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(0n);
  217. expect(await this.token.getPastVotes(this.delegatee, timepoint)).to.equal(supply);
  218. });
  219. });
  220. describe('transfers', function () {
  221. beforeEach(async function () {
  222. await this.token.$_mint(this.holder, supply);
  223. });
  224. it('no delegation', async function () {
  225. await expect(this.token.connect(this.holder).transfer(this.recipient, 1n))
  226. .to.emit(this.token, 'Transfer')
  227. .withArgs(this.holder, this.recipient, 1n)
  228. .to.not.emit(this.token, 'DelegateVotesChanged');
  229. this.holderVotes = 0n;
  230. this.recipientVotes = 0n;
  231. });
  232. it('sender delegation', async function () {
  233. await this.token.connect(this.holder).delegate(this.holder);
  234. const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n);
  235. await expect(tx)
  236. .to.emit(this.token, 'Transfer')
  237. .withArgs(this.holder, this.recipient, 1n)
  238. .to.emit(this.token, 'DelegateVotesChanged')
  239. .withArgs(this.holder, supply, supply - 1n);
  240. const { logs } = await tx.wait();
  241. const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
  242. for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
  243. expect(event.index).to.lt(index);
  244. }
  245. this.holderVotes = supply - 1n;
  246. this.recipientVotes = 0n;
  247. });
  248. it('receiver delegation', async function () {
  249. await this.token.connect(this.recipient).delegate(this.recipient);
  250. const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n);
  251. await expect(tx)
  252. .to.emit(this.token, 'Transfer')
  253. .withArgs(this.holder, this.recipient, 1n)
  254. .to.emit(this.token, 'DelegateVotesChanged')
  255. .withArgs(this.recipient, 0n, 1n);
  256. const { logs } = await tx.wait();
  257. const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
  258. for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
  259. expect(event.index).to.lt(index);
  260. }
  261. this.holderVotes = 0n;
  262. this.recipientVotes = 1n;
  263. });
  264. it('full delegation', async function () {
  265. await this.token.connect(this.holder).delegate(this.holder);
  266. await this.token.connect(this.recipient).delegate(this.recipient);
  267. const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n);
  268. await expect(tx)
  269. .to.emit(this.token, 'Transfer')
  270. .withArgs(this.holder, this.recipient, 1n)
  271. .to.emit(this.token, 'DelegateVotesChanged')
  272. .withArgs(this.holder, supply, supply - 1n)
  273. .to.emit(this.token, 'DelegateVotesChanged')
  274. .withArgs(this.recipient, 0n, 1n);
  275. const { logs } = await tx.wait();
  276. const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged');
  277. for (const event of logs.filter(event => event.fragment.name == 'Transfer')) {
  278. expect(event.index).to.lt(index);
  279. }
  280. this.holderVotes = supply - 1n;
  281. this.recipientVotes = 1n;
  282. });
  283. afterEach(async function () {
  284. expect(await this.token.getVotes(this.holder)).to.equal(this.holderVotes);
  285. expect(await this.token.getVotes(this.recipient)).to.equal(this.recipientVotes);
  286. // need to advance 2 blocks to see the effect of a transfer on "getPastVotes"
  287. const timepoint = await time.clock[mode]();
  288. await mine();
  289. expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(this.holderVotes);
  290. expect(await this.token.getPastVotes(this.recipient, timepoint)).to.equal(this.recipientVotes);
  291. });
  292. });
  293. // The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js.
  294. describe('Compound test suite', function () {
  295. beforeEach(async function () {
  296. await this.token.$_mint(this.holder, supply);
  297. });
  298. describe('balanceOf', function () {
  299. it('grants to initial account', async function () {
  300. expect(await this.token.balanceOf(this.holder)).to.equal(supply);
  301. });
  302. });
  303. describe('numCheckpoints', function () {
  304. it('returns the number of checkpoints for a delegate', async function () {
  305. await this.token.connect(this.holder).transfer(this.recipient, 100n); //give an account a few tokens for readability
  306. expect(await this.token.numCheckpoints(this.other1)).to.equal(0n);
  307. const t1 = await this.token.connect(this.recipient).delegate(this.other1);
  308. t1.timepoint = await time.clockFromReceipt[mode](t1);
  309. expect(await this.token.numCheckpoints(this.other1)).to.equal(1n);
  310. const t2 = await this.token.connect(this.recipient).transfer(this.other2, 10);
  311. t2.timepoint = await time.clockFromReceipt[mode](t2);
  312. expect(await this.token.numCheckpoints(this.other1)).to.equal(2n);
  313. const t3 = await this.token.connect(this.recipient).transfer(this.other2, 10);
  314. t3.timepoint = await time.clockFromReceipt[mode](t3);
  315. expect(await this.token.numCheckpoints(this.other1)).to.equal(3n);
  316. const t4 = await this.token.connect(this.holder).transfer(this.recipient, 20);
  317. t4.timepoint = await time.clockFromReceipt[mode](t4);
  318. expect(await this.token.numCheckpoints(this.other1)).to.equal(4n);
  319. expect(await this.token.checkpoints(this.other1, 0n)).to.deep.equal([t1.timepoint, 100n]);
  320. expect(await this.token.checkpoints(this.other1, 1n)).to.deep.equal([t2.timepoint, 90n]);
  321. expect(await this.token.checkpoints(this.other1, 2n)).to.deep.equal([t3.timepoint, 80n]);
  322. expect(await this.token.checkpoints(this.other1, 3n)).to.deep.equal([t4.timepoint, 100n]);
  323. await mine();
  324. expect(await this.token.getPastVotes(this.other1, t1.timepoint)).to.equal(100n);
  325. expect(await this.token.getPastVotes(this.other1, t2.timepoint)).to.equal(90n);
  326. expect(await this.token.getPastVotes(this.other1, t3.timepoint)).to.equal(80n);
  327. expect(await this.token.getPastVotes(this.other1, t4.timepoint)).to.equal(100n);
  328. });
  329. it('does not add more than one checkpoint in a block', async function () {
  330. await this.token.connect(this.holder).transfer(this.recipient, 100n);
  331. expect(await this.token.numCheckpoints(this.other1)).to.equal(0n);
  332. const [t1, t2, t3] = await batchInBlock([
  333. () => this.token.connect(this.recipient).delegate(this.other1, { gasLimit: 200000 }),
  334. () => this.token.connect(this.recipient).transfer(this.other2, 10n, { gasLimit: 200000 }),
  335. () => this.token.connect(this.recipient).transfer(this.other2, 10n, { gasLimit: 200000 }),
  336. ]);
  337. t1.timepoint = await time.clockFromReceipt[mode](t1);
  338. t2.timepoint = await time.clockFromReceipt[mode](t2);
  339. t3.timepoint = await time.clockFromReceipt[mode](t3);
  340. expect(await this.token.numCheckpoints(this.other1)).to.equal(1);
  341. expect(await this.token.checkpoints(this.other1, 0n)).to.be.deep.equal([t1.timepoint, 80n]);
  342. const t4 = await this.token.connect(this.holder).transfer(this.recipient, 20n);
  343. t4.timepoint = await time.clockFromReceipt[mode](t4);
  344. expect(await this.token.numCheckpoints(this.other1)).to.equal(2n);
  345. expect(await this.token.checkpoints(this.other1, 1n)).to.be.deep.equal([t4.timepoint, 100n]);
  346. });
  347. });
  348. describe('getPastVotes', function () {
  349. it('reverts if block number >= current block', async function () {
  350. const clock = await this.token.clock();
  351. await expect(this.token.getPastVotes(this.other1, 50_000_000_000n))
  352. .to.be.revertedWithCustomError(this.token, 'ERC5805FutureLookup')
  353. .withArgs(50_000_000_000n, clock);
  354. });
  355. it('returns 0 if there are no checkpoints', async function () {
  356. expect(await this.token.getPastVotes(this.other1, 0n)).to.equal(0n);
  357. });
  358. it('returns the latest block if >= last checkpoint block', async function () {
  359. const tx = await this.token.connect(this.holder).delegate(this.other1);
  360. const timepoint = await time.clockFromReceipt[mode](tx);
  361. await mine(2);
  362. expect(await this.token.getPastVotes(this.other1, timepoint)).to.equal(supply);
  363. expect(await this.token.getPastVotes(this.other1, timepoint + 1n)).to.equal(supply);
  364. });
  365. it('returns zero if < first checkpoint block', async function () {
  366. await mine();
  367. const tx = await this.token.connect(this.holder).delegate(this.other1);
  368. const timepoint = await time.clockFromReceipt[mode](tx);
  369. await mine(2);
  370. expect(await this.token.getPastVotes(this.other1, timepoint - 1n)).to.equal(0n);
  371. expect(await this.token.getPastVotes(this.other1, timepoint + 1n)).to.equal(supply);
  372. });
  373. it('generally returns the voting balance at the appropriate checkpoint', async function () {
  374. const t1 = await this.token.connect(this.holder).delegate(this.other1);
  375. await mine(2);
  376. const t2 = await this.token.connect(this.holder).transfer(this.other2, 10);
  377. await mine(2);
  378. const t3 = await this.token.connect(this.holder).transfer(this.other2, 10);
  379. await mine(2);
  380. const t4 = await this.token.connect(this.other2).transfer(this.holder, 20);
  381. await mine(2);
  382. t1.timepoint = await time.clockFromReceipt[mode](t1);
  383. t2.timepoint = await time.clockFromReceipt[mode](t2);
  384. t3.timepoint = await time.clockFromReceipt[mode](t3);
  385. t4.timepoint = await time.clockFromReceipt[mode](t4);
  386. expect(await this.token.getPastVotes(this.other1, t1.timepoint - 1n)).to.equal(0n);
  387. expect(await this.token.getPastVotes(this.other1, t1.timepoint)).to.equal(supply);
  388. expect(await this.token.getPastVotes(this.other1, t1.timepoint + 1n)).to.equal(supply);
  389. expect(await this.token.getPastVotes(this.other1, t2.timepoint)).to.equal(supply - 10n);
  390. expect(await this.token.getPastVotes(this.other1, t2.timepoint + 1n)).to.equal(supply - 10n);
  391. expect(await this.token.getPastVotes(this.other1, t3.timepoint)).to.equal(supply - 20n);
  392. expect(await this.token.getPastVotes(this.other1, t3.timepoint + 1n)).to.equal(supply - 20n);
  393. expect(await this.token.getPastVotes(this.other1, t4.timepoint)).to.equal(supply);
  394. expect(await this.token.getPastVotes(this.other1, t4.timepoint + 1n)).to.equal(supply);
  395. });
  396. });
  397. });
  398. describe('getPastTotalSupply', function () {
  399. beforeEach(async function () {
  400. await this.token.connect(this.holder).delegate(this.holder);
  401. });
  402. it('reverts if block number >= current block', async function () {
  403. const clock = await this.token.clock();
  404. await expect(this.token.getPastTotalSupply(50_000_000_000n))
  405. .to.be.revertedWithCustomError(this.token, 'ERC5805FutureLookup')
  406. .withArgs(50_000_000_000n, clock);
  407. });
  408. it('returns 0 if there are no checkpoints', async function () {
  409. expect(await this.token.getPastTotalSupply(0n)).to.equal(0n);
  410. });
  411. it('returns the latest block if >= last checkpoint block', async function () {
  412. const tx = await this.token.$_mint(this.holder, supply);
  413. const timepoint = await time.clockFromReceipt[mode](tx);
  414. await mine(2);
  415. expect(await this.token.getPastTotalSupply(timepoint)).to.equal(supply);
  416. expect(await this.token.getPastTotalSupply(timepoint + 1n)).to.equal(supply);
  417. });
  418. it('returns zero if < first checkpoint block', async function () {
  419. await mine();
  420. const tx = await this.token.$_mint(this.holder, supply);
  421. const timepoint = await time.clockFromReceipt[mode](tx);
  422. await mine(2);
  423. expect(await this.token.getPastTotalSupply(timepoint - 1n)).to.equal(0n);
  424. expect(await this.token.getPastTotalSupply(timepoint + 1n)).to.equal(supply);
  425. });
  426. it('generally returns the voting balance at the appropriate checkpoint', async function () {
  427. const t1 = await this.token.$_mint(this.holder, supply);
  428. await mine(2);
  429. const t2 = await this.token.$_burn(this.holder, 10n);
  430. await mine(2);
  431. const t3 = await this.token.$_burn(this.holder, 10n);
  432. await mine(2);
  433. const t4 = await this.token.$_mint(this.holder, 20n);
  434. await mine(2);
  435. t1.timepoint = await time.clockFromReceipt[mode](t1);
  436. t2.timepoint = await time.clockFromReceipt[mode](t2);
  437. t3.timepoint = await time.clockFromReceipt[mode](t3);
  438. t4.timepoint = await time.clockFromReceipt[mode](t4);
  439. expect(await this.token.getPastTotalSupply(t1.timepoint - 1n)).to.equal(0n);
  440. expect(await this.token.getPastTotalSupply(t1.timepoint)).to.equal(supply);
  441. expect(await this.token.getPastTotalSupply(t1.timepoint + 1n)).to.equal(supply);
  442. expect(await this.token.getPastTotalSupply(t2.timepoint)).to.equal(supply - 10n);
  443. expect(await this.token.getPastTotalSupply(t2.timepoint + 1n)).to.equal(supply - 10n);
  444. expect(await this.token.getPastTotalSupply(t3.timepoint)).to.equal(supply - 20n);
  445. expect(await this.token.getPastTotalSupply(t3.timepoint + 1n)).to.equal(supply - 20n);
  446. expect(await this.token.getPastTotalSupply(t4.timepoint)).to.equal(supply);
  447. expect(await this.token.getPastTotalSupply(t4.timepoint + 1n)).to.equal(supply);
  448. });
  449. });
  450. });
  451. }
  452. });