Skip to content

Commit 6e40f62

Browse files
author
Sean McCaffery
authored
refactor: big ole proposal metadata, tests, rewards, and ranking refactor (#254)
* enhancement: add metadatas and refactor proposals * feat: add target metadata to rewards * bugfix: set _lowestRanking when only one prop * test: add Rewards tests * chore: remove solhint * refactor: move GovernorMerkleVotes * refactor: move sorting logic into Governor from Rewards * chore: update bytecode * chore: add in SUB_AND_VOTING_MERKLE_ROOT constant * bugfix: ts-ignore ABI too many signatures issues * chore: add TooManyMetadatas error, docs, rest of TODOs
1 parent 3fb5a35 commit 6e40f62

File tree

18 files changed

+14330
-17562
lines changed

18 files changed

+14330
-17562
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
"react-app-revamp:test": "yarn workspace @scaffold-eth/react-app-revamp test",
1919
"dev": "yarn workspace @scaffold-eth/react-app-revamp dev",
2020
"format": "yarn workspace @scaffold-eth/forge forgeformat",
21-
"lint": "yarn workspace @scaffold-eth/forge forgelint",
2221
"test": "yarn workspace @scaffold-eth/forge forgetest",
23-
"deploy": "yarn workspace @scaffold-eth/forge forgebuild",
22+
"forgebuild": "yarn workspace @scaffold-eth/forge forgebuild",
23+
"deploy": "yarn workspace @scaffold-eth/forge forgebuildandcopy",
2424
"coverage": "yarn workspace @scaffold-eth/forge forgecoverage",
25-
"smartcheck": "yarn format && yarn lint && yarn test && yarn deploy",
25+
"smartcheck": "yarn format && yarn test && yarn deploy",
2626
"postinstall": "husky install"
2727
},
2828
"workspaces": [

packages/forge/.solhint.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/forge/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77
"copyfiles": "^2.4.1"
88
},
99
"dependencies": {
10-
"copyfiles": "^2.4.1",
11-
"solhint": "^3.4.1"
10+
"copyfiles": "^2.4.1"
1211
},
1312
"scripts": {
1413
"forgeformat": "forge fmt",
15-
"forgelint": "yarn solhint 'src/**/*.sol'",
1614
"forgetest": "forge test",
1715
"forgecoverage": "forge coverage",
18-
"forgebuild": "forge build && copyfiles -u 1 out/Contest.sol/*.json ../react-app-revamp/contracts/bytecodeAndAbi && copyfiles -u 1 out/RewardsModule.sol/*.json ../react-app-revamp/contracts/bytecodeAndAbi/modules/"
16+
"forgebuild": "forge build",
17+
"forgebuildandcopy": "forge build && copyfiles -u 1 out/Contest.sol/*.json ../react-app-revamp/contracts/bytecodeAndAbi && copyfiles -u 1 out/RewardsModule.sol/*.json ../react-app-revamp/contracts/bytecodeAndAbi/modules/"
1918
}
2019
}

packages/forge/src/Contest.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "./governance/extensions/GovernorSettings.sol";
66
import "./governance/extensions/GovernorCountingSimple.sol";
77
import "./governance/extensions/GovernorModuleRegistry.sol";
88

9-
contract Contest is Governor, GovernorSettings, GovernorCountingSimple, GovernorModuleRegistry {
9+
contract Contest is Governor, GovernorSettings, GovernorSorting, GovernorModuleRegistry {
1010
constructor(
1111
string memory _name,
1212
string memory _prompt,

packages/forge/src/governance/Governor.sol

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import "@openzeppelin/utils/math/SafeCast.sol";
1010
import "@openzeppelin/utils/Address.sol";
1111
import "@openzeppelin/utils/Context.sol";
1212
import "./IGovernor.sol";
13-
import "./extensions/GovernorMerkleVotes.sol";
13+
import "./GovernorMerkleVotes.sol";
1414

1515
/**
1616
* @dev Core of the governance system, designed to be extended though various modules.
@@ -29,14 +29,8 @@ abstract contract Governor is Context, ERC165, EIP712, GovernorMerkleVotes, IGov
2929
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");
3030
uint64 public constant AMOUNT_FOR_SUMBITTER_PROOF = 10000000000000000000;
3131
mapping(address => uint256) public addressTotalVotes;
32-
mapping(address => bool) private _addressTotalVotesVerified;
33-
mapping(address => bool) private _addressSubmitterVerified;
34-
35-
struct ProposalCore {
36-
address author;
37-
string description;
38-
bool exists;
39-
}
32+
mapping(address => bool) public addressTotalVotesVerified;
33+
mapping(address => bool) public addressSubmitterVerified;
4034

4135
uint256[] private _proposalIds;
4236
mapping(uint256 => uint256) private _deletedProposalIds;
@@ -46,6 +40,9 @@ abstract contract Governor is Context, ERC165, EIP712, GovernorMerkleVotes, IGov
4640
mapping(uint256 => ProposalCore) private _proposals;
4741
mapping(address => uint256) private _numSubmissions;
4842

43+
/// @notice Thrown if there is metadata included in a proposal that isn't covered in data validation
44+
error TooManyMetadatas();
45+
4946
/**
5047
* @dev Restrict access of functions to the governance executor, which may be the Governor itself or a timelock
5148
* contract, as specified by {_executor}. This generally means that function with this modifier must be voted on and
@@ -116,8 +113,8 @@ abstract contract Governor is Context, ERC165, EIP712, GovernorMerkleVotes, IGov
116113
* accross multiple networks. This also means that in order to execute the same operation twice (on the same
117114
* governor) the proposer will have to change the description in order to avoid proposal id conflicts.
118115
*/
119-
function hashProposal(string memory proposalDescription) public pure virtual override returns (uint256) {
120-
return uint256(keccak256(abi.encode(proposalDescription)));
116+
function hashProposal(ProposalCore memory proposal) public pure virtual override returns (uint256) {
117+
return uint256(keccak256(abi.encode(proposal)));
121118
}
122119

123120
/**
@@ -225,56 +222,78 @@ abstract contract Governor is Context, ERC165, EIP712, GovernorMerkleVotes, IGov
225222
* @dev See {IGovernor-verifySubmitter}.
226223
*/
227224
function verifySubmitter(address account, bytes32[] calldata proof) public override returns (bool verified) {
228-
if (!_addressSubmitterVerified[account]) {
225+
if (!addressSubmitterVerified[account]) {
229226
checkProof(account, AMOUNT_FOR_SUMBITTER_PROOF, proof, false); // will revert with NotInMerkle if not valid
230-
_addressSubmitterVerified[account] = true;
227+
addressSubmitterVerified[account] = true;
231228
}
232229
return true;
233230
}
234231

235232
/**
236-
* @dev See {IGovernor-verifyTotalVotes}.
233+
* @dev See {IGovernor-validateProposalData}.
237234
*/
238-
function verifyTotalVotes(address account, uint256 totalVotes, bytes32[] calldata proof)
239-
public
240-
override
241-
returns (bool verified)
242-
{
243-
if (!_addressTotalVotesVerified[account]) {
244-
checkProof(account, totalVotes, proof, false); // will revert with NotInMerkle if not valid
245-
addressTotalVotes[account] = totalVotes;
246-
_addressTotalVotesVerified[account] = true;
235+
function validateProposalData(ProposalCore memory proposal) public virtual override returns (bool dataValidated) {
236+
for (uint256 index = 0; index < METADATAS_COUNT; index++) {
237+
Metadatas currentMetadata = Metadatas(index);
238+
if (currentMetadata == Metadatas.Target) {
239+
continue; // Nothing to check here since strictly typed to address
240+
} else if (currentMetadata == Metadatas.Safe) {
241+
require(
242+
proposal.safeMetadata.signers.length != 0,
243+
"GovernorMetadataValidation: there cannot be zero signers in safeMetadata"
244+
);
245+
require(
246+
proposal.safeMetadata.threshold != 0,
247+
"GovernorMetadataValidation: threshold cannot be zero in safeMetadata"
248+
);
249+
require(proposal.safeMetadata.signers.length != 0);
250+
} else {
251+
revert TooManyMetadatas();
252+
}
247253
}
254+
require(bytes(proposal.description).length != 0, "Governor: empty proposal");
248255
return true;
249256
}
250257

251258
/**
252259
* @dev See {IGovernor-propose}.
253260
*/
254-
function propose(string memory proposalDescription, bytes32[] calldata proof)
261+
function propose(ProposalCore memory proposal, bytes32[] calldata proof)
255262
public
256263
virtual
257264
override
258265
returns (uint256)
259266
{
267+
require(verifySubmitter(msg.sender, proof), "Governor: address is not permissioned to submit");
268+
validateProposalData(proposal);
269+
return _castProposal(proposal);
270+
}
271+
272+
/**
273+
* @dev See {IGovernor-proposeWithoutProof}.
274+
*/
275+
function proposeWithoutProof(ProposalCore memory proposal) public virtual override returns (uint256) {
276+
require(addressSubmitterVerified[msg.sender], "Governor: address is not permissioned to submit");
277+
validateProposalData(proposal);
278+
return _castProposal(proposal);
279+
}
280+
281+
function _castProposal(ProposalCore memory proposal) internal virtual returns (uint256) {
260282
require(state() == ContestState.Queued, "Governor: contest must be queued for proposals to be submitted");
261283
require(
262284
_numSubmissions[msg.sender] < numAllowedProposalSubmissions(),
263285
"Governor: the same cannot submit more than the numAllowedProposalSubmissions for this contest"
264286
);
265287
require(_proposalIds.length < maxProposalCount(), "Governor: the max number of proposals have been submitted");
266-
require(verifySubmitter(msg.sender, proof), "Governor: address is not permissioned to submit");
267-
268-
require(bytes(proposalDescription).length != 0, "Governor: empty proposal");
269288

270-
uint256 proposalId = hashProposal(proposalDescription);
289+
uint256 proposalId = hashProposal(proposal);
271290
require(!_proposals[proposalId].exists, "Governor: duplicate proposals not allowed");
272291

273292
_proposalIds.push(proposalId);
274-
_proposals[proposalId] = ProposalCore({author: msg.sender, description: proposalDescription, exists: true});
293+
_proposals[proposalId] = proposal;
275294
_numSubmissions[msg.sender] += 1;
276295

277-
emit ProposalCreated(proposalId, proposalDescription, _msgSender());
296+
emit ProposalCreated(proposalId, _msgSender());
278297

279298
return proposalId;
280299
}
@@ -318,6 +337,22 @@ abstract contract Governor is Context, ERC165, EIP712, GovernorMerkleVotes, IGov
318337
emit ContestCanceled();
319338
}
320339

340+
/**
341+
* @dev See {IGovernor-verifyTotalVotes}.
342+
*/
343+
function verifyTotalVotes(address account, uint256 totalVotes, bytes32[] calldata proof)
344+
public
345+
override
346+
returns (bool verified)
347+
{
348+
if (!addressTotalVotesVerified[account]) {
349+
checkProof(account, totalVotes, proof, false); // will revert with NotInMerkle if not valid
350+
addressTotalVotes[account] = totalVotes;
351+
addressTotalVotesVerified[account] = true;
352+
}
353+
return true;
354+
}
355+
321356
/**
322357
* @dev See {IGovernor-castVote}.
323358
*/
@@ -343,7 +378,7 @@ abstract contract Governor is Context, ERC165, EIP712, GovernorMerkleVotes, IGov
343378
{
344379
address voter = _msgSender();
345380
require(
346-
_addressTotalVotesVerified[voter],
381+
addressTotalVotesVerified[voter],
347382
"Governor: you need to cast a vote with the proof at least once and you haven't yet"
348383
);
349384
return _castVote(proposalId, voter, support, numVotes, "");
@@ -364,7 +399,7 @@ abstract contract Governor is Context, ERC165, EIP712, GovernorMerkleVotes, IGov
364399
require(numVotes > 0, "Governor: cannot vote with 0 or fewer votes");
365400

366401
require(
367-
_addressTotalVotesVerified[account],
402+
addressTotalVotesVerified[account],
368403
"Governor: you need to verify your number of votes against the merkle root first"
369404
);
370405
_countVote(proposalId, account, support, numVotes, addressTotalVotes[account]);

packages/forge/src/governance/IGovernor.sol

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,34 @@ abstract contract IGovernor is IERC165 {
1919
Completed
2020
}
2121

22+
uint8 public constant METADATAS_COUNT = 2;
23+
24+
enum Metadatas {
25+
Target,
26+
Safe
27+
}
28+
29+
struct TargetMetadata {
30+
address targetAddress;
31+
}
32+
33+
struct SafeMetadata {
34+
address[] signers;
35+
uint256 threshold;
36+
}
37+
38+
struct ProposalCore {
39+
address author;
40+
string description;
41+
bool exists;
42+
TargetMetadata targetMetadata;
43+
SafeMetadata safeMetadata;
44+
}
45+
2246
/**
2347
* @dev Emitted when a proposal is created.
2448
*/
25-
event ProposalCreated(uint256 proposalId, string description, address proposer);
49+
event ProposalCreated(uint256 proposalId, address proposer);
2650

2751
/**
2852
* @dev Emitted when proposals are deleted.
@@ -83,7 +107,7 @@ abstract contract IGovernor is IERC165 {
83107
* @notice module:core
84108
* @dev Hashing function used to (re)build the proposal id from the proposal details..
85109
*/
86-
function hashProposal(string memory proposalDescription) public pure virtual returns (uint256);
110+
function hashProposal(ProposalCore memory proposal) public pure virtual returns (uint256);
87111

88112
/**
89113
* @notice module:core
@@ -140,23 +164,36 @@ abstract contract IGovernor is IERC165 {
140164
function verifySubmitter(address account, bytes32[] calldata proof) public virtual returns (bool);
141165

142166
/**
143-
* @dev Verifies that `account` is permissioned to vote with `totalVotes` via merkle proof.
167+
* @dev Verifies that all of the metadata in the proposal is valid.
144168
*/
145-
function verifyTotalVotes(address account, uint256 totalVotes, bytes32[] calldata proof)
169+
function validateProposalData(ProposalCore memory proposal) public virtual returns (bool);
170+
171+
/**
172+
* @dev Create a new proposal. Vote start {IGovernor-votingDelay} blocks after the proposal is created and ends
173+
* {IGovernor-votingPeriod} blocks after the voting starts.
174+
*
175+
* Emits a {ProposalCreated} event.
176+
*/
177+
function propose(ProposalCore memory proposal, bytes32[] calldata proof)
146178
public
147179
virtual
148-
returns (bool);
180+
returns (uint256 proposalId);
149181

150182
/**
151183
* @dev Create a new proposal. Vote start {IGovernor-votingDelay} blocks after the proposal is created and ends
152184
* {IGovernor-votingPeriod} blocks after the voting starts.
153185
*
154186
* Emits a {ProposalCreated} event.
155187
*/
156-
function propose(string memory proposalDescription, bytes32[] calldata proof)
188+
function proposeWithoutProof(ProposalCore memory proposal) public virtual returns (uint256 proposalId);
189+
190+
/**
191+
* @dev Verifies that `account` is permissioned to vote with `totalVotes` via merkle proof.
192+
*/
193+
function verifyTotalVotes(address account, uint256 totalVotes, bytes32[] calldata proof)
157194
public
158195
virtual
159-
returns (uint256 proposalId);
196+
returns (bool);
160197

161198
/**
162199
* @dev Cast a vote with a merkle proof.

0 commit comments

Comments
 (0)