Add timestamp based governor with EIP-6372 and EIP-5805 (#3934)

Co-authored-by: Francisco Giordano <fg@frang.io>
Co-authored-by: Ernesto García <ernestognw@gmail.com>
Co-authored-by: Francisco <frangio.1@gmail.com>
This commit is contained in:
Hadrien Croubois
2023-02-09 22:33:55 +01:00
committed by GitHub
parent 94cd8ef12e
commit 790cc5b65a
42 changed files with 4081 additions and 3209 deletions

View File

@ -6,7 +6,10 @@ const { fromRpcSig } = require('ethereumjs-util');
const ethSigUtil = require('eth-sig-util');
const Wallet = require('ethereumjs-wallet').default;
const { shouldBehaveLikeEIP6372 } = require('./EIP6372.behavior');
const { getDomain, domainType, domainSeparator } = require('../../helpers/eip712');
const { clockFromReceipt } = require('../../helpers/time');
const Delegation = [
{ name: 'delegatee', type: 'address' },
@ -14,7 +17,9 @@ const Delegation = [
{ name: 'expiry', type: 'uint256' },
];
function shouldBehaveLikeVotes() {
function shouldBehaveLikeVotes(mode = 'blocknumber') {
shouldBehaveLikeEIP6372(mode);
describe('run votes workflow', function () {
it('initial nonce is 0', async function () {
expect(await this.votes.nonces(this.account1)).to.be.bignumber.equal('0');
@ -57,6 +62,8 @@ function shouldBehaveLikeVotes() {
expect(await this.votes.delegates(delegatorAddress)).to.be.equal(ZERO_ADDRESS);
const { receipt } = await this.votes.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s);
const timepoint = await clockFromReceipt[mode](receipt);
expectEvent(receipt, 'DelegateChanged', {
delegator: delegatorAddress,
fromDelegate: ZERO_ADDRESS,
@ -71,9 +78,9 @@ function shouldBehaveLikeVotes() {
expect(await this.votes.delegates(delegatorAddress)).to.be.equal(delegatorAddress);
expect(await this.votes.getVotes(delegatorAddress)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(delegatorAddress, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastVotes(delegatorAddress, timepoint - 1)).to.be.bignumber.equal('0');
await time.advanceBlock();
expect(await this.votes.getPastVotes(delegatorAddress, receipt.blockNumber)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(delegatorAddress, timepoint)).to.be.bignumber.equal('1');
});
it('rejects reused signature', async function () {
@ -157,6 +164,8 @@ function shouldBehaveLikeVotes() {
expect(await this.votes.delegates(this.account1)).to.be.equal(ZERO_ADDRESS);
const { receipt } = await this.votes.delegate(this.account1, { from: this.account1 });
const timepoint = await clockFromReceipt[mode](receipt);
expectEvent(receipt, 'DelegateChanged', {
delegator: this.account1,
fromDelegate: ZERO_ADDRESS,
@ -171,9 +180,9 @@ function shouldBehaveLikeVotes() {
expect(await this.votes.delegates(this.account1)).to.be.equal(this.account1);
expect(await this.votes.getVotes(this.account1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastVotes(this.account1, timepoint - 1)).to.be.bignumber.equal('0');
await time.advanceBlock();
expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(this.account1, timepoint)).to.be.bignumber.equal('1');
});
it('delegation without tokens', async function () {
@ -202,6 +211,8 @@ function shouldBehaveLikeVotes() {
expect(await this.votes.delegates(this.account1)).to.be.equal(this.account1);
const { receipt } = await this.votes.delegate(this.account1Delegatee, { from: this.account1 });
const timepoint = await clockFromReceipt[mode](receipt);
expectEvent(receipt, 'DelegateChanged', {
delegator: this.account1,
fromDelegate: this.account1,
@ -217,16 +228,16 @@ function shouldBehaveLikeVotes() {
previousBalance: '0',
newBalance: '1',
});
const prevBlock = receipt.blockNumber - 1;
expect(await this.votes.delegates(this.account1)).to.be.equal(this.account1Delegatee);
expect(await this.votes.getVotes(this.account1)).to.be.bignumber.equal('0');
expect(await this.votes.getVotes(this.account1Delegatee)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber - 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(this.account1Delegatee, prevBlock)).to.be.bignumber.equal('0');
expect(await this.votes.getPastVotes(this.account1, timepoint - 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(this.account1Delegatee, timepoint - 1)).to.be.bignumber.equal('0');
await time.advanceBlock();
expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber)).to.be.bignumber.equal('0');
expect(await this.votes.getPastVotes(this.account1Delegatee, receipt.blockNumber)).to.be.bignumber.equal('1');
expect(await this.votes.getPastVotes(this.account1, timepoint)).to.be.bignumber.equal('0');
expect(await this.votes.getPastVotes(this.account1Delegatee, timepoint)).to.be.bignumber.equal('1');
});
});
@ -236,7 +247,7 @@ function shouldBehaveLikeVotes() {
});
it('reverts if block number >= current block', async function () {
await expectRevert(this.votes.getPastTotalSupply(5e10), 'block not yet mined');
await expectRevert(this.votes.getPastTotalSupply(5e10), 'future lookup');
});
it('returns 0 if there are no checkpoints', async function () {
@ -244,22 +255,24 @@ function shouldBehaveLikeVotes() {
});
it('returns the latest block if >= last checkpoint block', async function () {
const t1 = await this.votes.$_mint(this.account1, this.NFT0);
const { receipt } = await this.votes.$_mint(this.account1, this.NFT0);
const timepoint = await clockFromReceipt[mode](receipt);
await time.advanceBlock();
await time.advanceBlock();
expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(timepoint - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal('1');
});
it('returns zero if < first checkpoint block', async function () {
await time.advanceBlock();
const t2 = await this.votes.$_mint(this.account1, this.NFT1);
const { receipt } = await this.votes.$_mint(this.account1, this.NFT1);
const timepoint = await clockFromReceipt[mode](receipt);
await time.advanceBlock();
await time.advanceBlock();
expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber + 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(timepoint - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal('1');
});
it('generally returns the voting balance at the appropriate checkpoint', async function () {
@ -279,17 +292,23 @@ function shouldBehaveLikeVotes() {
await time.advanceBlock();
await time.advanceBlock();
expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber + 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t3.receipt.blockNumber)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t3.receipt.blockNumber + 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t4.receipt.blockNumber)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t4.receipt.blockNumber + 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t5.receipt.blockNumber)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t5.receipt.blockNumber + 1)).to.be.bignumber.equal('1');
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(t1.timepoint - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t1.timepoint)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t1.timepoint + 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t2.timepoint)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t2.timepoint + 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t3.timepoint)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t3.timepoint + 1)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t4.timepoint)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t4.timepoint + 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastTotalSupply(t5.timepoint)).to.be.bignumber.equal('1');
expect(await this.votes.getPastTotalSupply(t5.timepoint + 1)).to.be.bignumber.equal('1');
});
});
@ -305,7 +324,7 @@ function shouldBehaveLikeVotes() {
describe('getPastVotes', function () {
it('reverts if block number >= current block', async function () {
await expectRevert(this.votes.getPastVotes(this.account2, 5e10), 'block not yet mined');
await expectRevert(this.votes.getPastVotes(this.account2, 5e10), 'future lookup');
});
it('returns 0 if there are no checkpoints', async function () {
@ -313,22 +332,24 @@ function shouldBehaveLikeVotes() {
});
it('returns the latest block if >= last checkpoint block', async function () {
const t1 = await this.votes.delegate(this.account2, { from: this.account1 });
const { receipt } = await this.votes.delegate(this.account2, { from: this.account1 });
const timepoint = await clockFromReceipt[mode](receipt);
await time.advanceBlock();
await time.advanceBlock();
const latest = await this.votes.getVotes(this.account2);
const nextBlock = t1.receipt.blockNumber + 1;
expect(await this.votes.getPastVotes(this.account2, t1.receipt.blockNumber)).to.be.bignumber.equal(latest);
expect(await this.votes.getPastVotes(this.account2, nextBlock)).to.be.bignumber.equal(latest);
expect(await this.votes.getPastVotes(this.account2, timepoint)).to.be.bignumber.equal(latest);
expect(await this.votes.getPastVotes(this.account2, timepoint + 1)).to.be.bignumber.equal(latest);
});
it('returns zero if < first checkpoint block', async function () {
await time.advanceBlock();
const t1 = await this.votes.delegate(this.account2, { from: this.account1 });
const { receipt } = await this.votes.delegate(this.account2, { from: this.account1 });
const timepoint = await clockFromReceipt[mode](receipt);
await time.advanceBlock();
await time.advanceBlock();
expect(await this.votes.getPastVotes(this.account2, t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
expect(await this.votes.getPastVotes(this.account2, timepoint - 1)).to.be.bignumber.equal('0');
});
});
});