Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 70 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
4312336
Add EIP-5805 clock to the Votes module
Amxx Jan 6, 2023
3d11319
Add EIP-5805 clock to ERC20Votes
Amxx Jan 6, 2023
cee1f9c
make governor clock dependant
Amxx Jan 6, 2023
f5b77d8
wording blockNumber → timepoint
Amxx Jan 6, 2023
cb1fa78
test ERC20Votes with timestamp
Amxx Jan 9, 2023
b1c155d
check clock return value
Amxx Jan 9, 2023
c6d3ef9
fix lint
Amxx Jan 9, 2023
c73f08f
test Votes.sol in timestamp mode
Amxx Jan 9, 2023
8a70c35
test governor with timestamp
Amxx Jan 11, 2023
0981e95
all governance test against blockNumber & timestamp modes
Amxx Jan 11, 2023
bcedb7e
add testing of new governor against legacy voting tokens
Amxx Jan 12, 2023
fdeabb7
spelling
Amxx Jan 12, 2023
5d2cd45
Merge branch 'master' into feature/timestamp-governor
Amxx Jan 17, 2023
fb0619c
revert IVotes and add IERC5805 that extends IVotes with clock functions
Amxx Jan 20, 2023
82ff76b
rewrite time helper
Amxx Jan 20, 2023
21588f0
add retyped from directive
Amxx Jan 25, 2023
3ae7f12
more retyped-from directives
Amxx Jan 25, 2023
490a379
add IERC6372.sol
Amxx Jan 25, 2023
c3d83d6
fix retype annotations, add missing
frangio Jan 27, 2023
b2bd811
Merge branch 'master'
Amxx Jan 27, 2023
d16a02e
Update README.adoc
Amxx Jan 27, 2023
2546026
Update ERC20VotesLegacyMock.sol
Amxx Jan 27, 2023
8348311
compact the new ProposalCore struct
Amxx Jan 30, 2023
7c9d08f
fix
Amxx Jan 30, 2023
cfd7d79
refactor retypes
Amxx Jan 30, 2023
b7e46ed
cleanup
Amxx Jan 31, 2023
37d5a3d
Apply suggestions from code review
Amxx Jan 31, 2023
7550395
Merge branch 'master'
Amxx Jan 31, 2023
21483ad
Update contracts/governance/README.adoc
Amxx Feb 1, 2023
9359d68
add upperLookupRecent to Checkpoints
Amxx Feb 1, 2023
97a34fd
use upperLookupRecent in Votes.sol
Amxx Feb 1, 2023
5cae0e6
Merge branch 'master' into feature/timestamp-governor
Amxx Feb 1, 2023
0822651
fix lint
Amxx Feb 1, 2023
d49ab18
fix lint
Amxx Feb 1, 2023
ecf0bde
reorder for reentrancy
Amxx Feb 1, 2023
0c05bc7
fix inheritance ordering
Amxx Feb 1, 2023
662c96e
put storage error directly in the output
Amxx Feb 1, 2023
0fe6bb5
simplify output
Amxx Feb 1, 2023
3a2882d
use block solhint-disable/solhint-enable to improve readability
Amxx Feb 1, 2023
b5f0b63
add a proposalProposer accessor
Amxx Feb 1, 2023
250ba20
avoid duplicate sload in GovernorCompatibilityBravo.cancel
Amxx Feb 1, 2023
a851552
Merge branch 'master' into feature/timestamp-governor
Amxx Feb 1, 2023
a75ca18
Apply suggestions from code review
Amxx Feb 2, 2023
07b60a7
Apply suggestions from code review
Amxx Feb 2, 2023
dab340d
safecast
Amxx Feb 2, 2023
f27149b
Merge branch 'feature/timestamp-governor' of github.com:Amxx/openzepp…
Amxx Feb 2, 2023
54632b8
lint
Amxx Feb 2, 2023
32e0ebf
changeset
Amxx Feb 2, 2023
18174a9
relax gas compare requierement to still produce an output when tests are
Amxx Feb 2, 2023
e1be9a2
improve comments
Amxx Feb 2, 2023
6f7f346
use safecast in mocks
Amxx Feb 2, 2023
b173774
do recent lookup for _quorumNumeratorHistory
Amxx Feb 2, 2023
3d15872
Merge branch 'master' into feature/timestamp-governor
Amxx Feb 3, 2023
759bbd8
change casts
Amxx Feb 3, 2023
cf548e3
Merge branch 'master' into feature/timestamp-governor
Amxx Feb 3, 2023
155ee75
Update ninety-hornets-kick.md
frangio Feb 3, 2023
b192f8c
Update four-bats-sniff.md
frangio Feb 3, 2023
c772388
overriden -> overridden
frangio Feb 3, 2023
8cec0b6
Merge branch 'master' into feature/timestamp-governor
frangio Feb 4, 2023
e4fb558
Merge branch 'master' into feature/timestamp-governor
Amxx Feb 6, 2023
773c4c0
Update contracts/governance/extensions/GovernorVotesQuorumFraction.sol
Amxx Feb 6, 2023
df59b36
add EIP6372.behavior.js
Amxx Feb 6, 2023
3c6c942
improve coverage
Amxx Feb 6, 2023
9a42c99
rename event params
Amxx Feb 6, 2023
11ac9da
Update contracts/governance/extensions/GovernorTimelockCompound.sol
Amxx Feb 9, 2023
cc0f45f
Merge branch 'master'
Amxx Feb 9, 2023
e2eb048
Update governance.js
Amxx Feb 9, 2023
5e54e88
Update ERC721Votes.test.js
Amxx Feb 9, 2023
60992b6
improve coverage
Amxx Feb 9, 2023
8397ce7
remove full IGovernor from ERC165
Amxx Feb 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion contracts/governance/Governor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
this.getVotesWithParams.selector) ||
// Previous interface for backwards compatibility
interfaceId == (type(IGovernor).interfaceId ^ type(IERC6372).interfaceId ^ this.cancel.selector) ||
interfaceId == type(IGovernor).interfaceId ||
frangio marked this conversation as resolved.
Show resolved Hide resolved
interfaceId == type(IERC1155Receiver).interfaceId ||
super.supportsInterface(interfaceId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor {

ICompoundTimelock private _timelock;

/// @custom:oz-retyped-from mapping(uint256 => Timers.Timestamp)
/// @custom:oz-retyped-from mapping(uint256 => GovernorTimelockCompound.ProposalTimelock)
mapping(uint256 => uint64) private _proposalTimelocks;

/**
Expand Down
32 changes: 15 additions & 17 deletions test/governance/Governor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,24 +161,22 @@ contract('Governor', function (accounts) {
const voterBySig = Wallet.generate();
const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString());

const signature = async message => {
return fromRpcSig(
ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), {
data: {
types: {
EIP712Domain,
Ballot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
],
},
domain: { name, version, chainId: this.chainId, verifyingContract: this.mock.address },
primaryType: 'Ballot',
message,
const signature = (contract, message) =>
getDomain(contract)
.then(domain => ({
primaryType: 'Ballot',
types: {
EIP712Domain: domainType(domain),
Ballot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
],
},
}),
);
};
domain,
message,
}))
.then(data => ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), { data }))
.then(fromRpcSig);

await this.token.delegate(voterBySigAddress, { from: voter1 });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ contract('GovernorTimelockCompound', function (accounts) {
expect(await this.token.balanceOf(this.mock.address), 0);
expect(await this.token.balanceOf(other), 1);

expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', {
await expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', {
from: this.mock.address,
to: other,
value: '1',
Expand Down
2 changes: 1 addition & 1 deletion test/governance/extensions/GovernorTimelockControl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ contract('GovernorTimelockControl', function (accounts) {
expect(await this.token.balanceOf(this.mock.address), 0);
expect(await this.token.balanceOf(other), 1);

expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', {
await expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', {
from: this.mock.address,
to: other,
value: '1',
Expand Down
36 changes: 17 additions & 19 deletions test/governance/extensions/GovernorWithParams.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,24 @@ contract('GovernorWithParams', function (accounts) {
const voterBySig = Wallet.generate();
const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString());

const signature = async message => {
return fromRpcSig(
ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), {
data: {
types: {
EIP712Domain,
ExtendedBallot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
{ name: 'reason', type: 'string' },
{ name: 'params', type: 'bytes' },
],
},
domain: { name, version, chainId: this.chainId, verifyingContract: this.mock.address },
primaryType: 'ExtendedBallot',
message,
const signature = (contract, message) =>
getDomain(contract)
.then(domain => ({
primaryType: 'ExtendedBallot',
types: {
EIP712Domain: domainType(domain),
ExtendedBallot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
{ name: 'reason', type: 'string' },
{ name: 'params', type: 'bytes' },
],
},
}),
);
};
domain,
message,
}))
.then(data => ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), { data }))
.then(fromRpcSig);

await this.token.delegate(voterBySigAddress, { from: voter2 });

Expand Down
4 changes: 1 addition & 3 deletions test/governance/utils/Votes.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Wallet = require('ethereumjs-wallet').default;

const { shouldBehaveLikeEIP6372 } = require('./EIP6372.behavior');

const { EIP712Domain, domainSeparator } = require('../../helpers/eip712');
const { getDomain, domainType, domainSeparator } = require('../../helpers/eip712');
const { clockFromReceipt } = require('../../helpers/time');

const Delegation = [
Expand All @@ -17,8 +17,6 @@ const Delegation = [
{ name: 'expiry', type: 'uint256' },
];

const version = '1';

function shouldBehaveLikeVotes(mode = 'blocknumber') {
shouldBehaveLikeEIP6372(mode);

Expand Down
2 changes: 1 addition & 1 deletion test/helpers/governance.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function concatOpts(args, opts = null) {
}

class GovernorHelper {
constructor(governor, mode = 'blockNumber') {
constructor(governor, mode = 'blocknumber') {
this.governor = governor;
this.mode = mode;
}
Expand Down
94 changes: 32 additions & 62 deletions test/token/ERC20/extensions/ERC20Votes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const Wallet = require('ethereumjs-wallet').default;

const { batchInBlock } = require('../../../helpers/txpool');
const { getDomain, domainType, domainSeparator } = require('../../../helpers/eip712');
const { getChainId } = require('../../../helpers/chainid');
const { clock, clockFromReceipt } = require('../../../helpers/time');

const { shouldBehaveLikeEIP6372 } = require('../../../governance/utils/EIP6372.behavior');
Expand All @@ -31,13 +30,11 @@ contract('ERC20Votes', function (accounts) {

const name = 'My Token';
const symbol = 'MTKN';
const version = '1';
const supply = new BN('10000000000000000000000000');

for (const [mode, artifact] of Object.entries(MODES)) {
describe(`vote with ${mode}`, function () {
beforeEach(async function () {
this.chainId = await getChainId();
this.token = await artifact.new(name, symbol, name);
});

Expand All @@ -48,9 +45,7 @@ contract('ERC20Votes', function (accounts) {
});

it('domain separator', async function () {
expect(await this.token.DOMAIN_SEPARATOR()).to.equal(
await domainSeparator({ name, version, chainId: this.chainId, verifyingContract: this.token.address }),
);
expect(await this.token.DOMAIN_SEPARATOR()).to.equal(await getDomain(this.token).then(domainSeparator));
});

it('minting restriction', async function () {
Expand Down Expand Up @@ -119,30 +114,24 @@ contract('ERC20Votes', function (accounts) {
const delegatorAddress = web3.utils.toChecksumAddress(delegator.getAddressString());
const nonce = 0;

const buildData = (chainId, verifyingContract, message) => ({
data: {
const buildData = (contract, message) =>
getDomain(contract).then(domain => ({
primaryType: 'Delegation',
types: { EIP712Domain, Delegation },
domain: { name, version, chainId, verifyingContract },
types: { EIP712Domain: domainType(domain), Delegation },
domain,
message,
},
});
}));

beforeEach(async function () {
await this.token.$_mint(delegatorAddress, supply);
});

it('accept signed delegation', async function () {
const { v, r, s } = fromRpcSig(
ethSigUtil.signTypedMessage(
delegator.getPrivateKey(),
buildData(this.chainId, this.token.address, {
delegatee: delegatorAddress,
nonce,
expiry: MAX_UINT256,
}),
),
);
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);

Expand All @@ -169,16 +158,11 @@ contract('ERC20Votes', function (accounts) {
});

it('rejects reused signature', async function () {
const { v, r, s } = fromRpcSig(
ethSigUtil.signTypedMessage(
delegator.getPrivateKey(),
buildData(this.chainId, this.token.address, {
delegatee: delegatorAddress,
nonce,
expiry: MAX_UINT256,
}),
),
);
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);

Expand All @@ -189,16 +173,11 @@ contract('ERC20Votes', function (accounts) {
});

it('rejects bad delegatee', async function () {
const { v, r, s } = fromRpcSig(
ethSigUtil.signTypedMessage(
delegator.getPrivateKey(),
buildData(this.chainId, this.token.address, {
delegatee: delegatorAddress,
nonce,
expiry: MAX_UINT256,
}),
),
);
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');
Expand All @@ -208,16 +187,12 @@ contract('ERC20Votes', function (accounts) {
});

it('rejects bad nonce', async function () {
const { v, r, s } = fromRpcSig(
ethSigUtil.signTypedMessage(
delegator.getPrivateKey(),
buildData(this.chainId, this.token.address, {
delegatee: delegatorAddress,
nonce,
expiry: MAX_UINT256,
}),
),
);
const { v, r, s } = await buildData(this.token, {
delegatee: delegatorAddress,
nonce,
expiry: MAX_UINT256,
}).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data })));

await expectRevert(
this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s),
'ERC20Votes: invalid nonce',
Expand All @@ -226,16 +201,11 @@ contract('ERC20Votes', function (accounts) {

it('rejects expired permit', async function () {
const expiry = (await time.latest()) - time.duration.weeks(1);
const { v, r, s } = fromRpcSig(
ethSigUtil.signTypedMessage(
delegator.getPrivateKey(),
buildData(this.chainId, this.token.address, {
delegatee: delegatorAddress,
nonce,
expiry,
}),
),
);
const { v, r, s } = await buildData(this.token, {
delegatee: delegatorAddress,
nonce,
expiry,
}).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data })));

await expectRevert(
this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s),
Expand Down
Loading