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

feat: add wonderGovernor and wonderVotes contract examples #5

Merged
merged 2 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ out-via-ir

# Config files
.env
.vscode

# Avoid ignoring gitkeep
!/**/.gitkeep
Expand Down
4 changes: 4 additions & 0 deletions solidity/contracts/governance/WonderGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -889,4 +889,8 @@ abstract contract WonderGovernor is
function proposalTypes() public view virtual override returns (uint8[] memory) {
return _proposalTypes();
}

function _getProposal(uint256 _proposalId) internal view virtual returns (ProposalCore memory) {
return _proposals[_proposalId];
}
}
6 changes: 5 additions & 1 deletion solidity/contracts/governance/utils/WonderVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,11 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
if (_weightSum != _weightNormalizer()) revert InvalidWeightSum(_weightSum);

Delegate[] memory _oldDelegates = delegates(account, proposalType);
_delegatees[account][proposalType] = delegatees;

delete _delegatees[account][proposalType];
for (uint256 i = 0; i < delegatees.length; i++) {
_delegatees[account][proposalType].push(delegatees[i]);
}

emit DelegateChanged(account, proposalType, _oldDelegates, delegatees);
_moveDelegateVotes(proposalType, _oldDelegates, delegatees, _getVotingUnits(account));
Expand Down
134 changes: 134 additions & 0 deletions solidity/examples/AliceGovernor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import 'contracts/governance/WonderGovernor.sol';
import 'contracts/governance/utils/WonderVotes.sol';

contract AliceGovernor is WonderGovernor {
WonderVotes public votes;
string internal _countingMode = 'support=bravo&quorum=bravo';
uint8[] internal __proposalTypes = [1, 2, 3];

mapping(uint256 proposalId => mapping(address => Receipt)) public receipts;
mapping(uint256 proposalId => ProposalTrack) public proposalTracks;

/// @notice Ballot receipt record for a voter
struct Receipt {
/// @notice Whether or not a vote has been cast
bool hasVoted;
/// @notice 0 = Against, 1 = For, 2 = Abstain
uint8 support;
/// @notice The number of votes the voter had, which were cast
uint256 votes;
}

struct ProposalTrack {
uint256 proposalId;
uint256 votes;
uint256 forVotes;
uint256 againstVotes;
uint256 abstainVotes;
}

constructor(address _wonderToken) WonderGovernor('AliceGovernor') {
votes = WonderVotes(_wonderToken);
}

function CLOCK_MODE() public view override returns (string memory _clockMode) {
return votes.CLOCK_MODE();
}

function COUNTING_MODE() external view override returns (string memory) {
return _countingMode;
}

function _getVotes(
address _account,
uint8 _proposalType,
uint256 _timepoint,
bytes memory _params
) internal view virtual override returns (uint256) {
return votes.getPastVotes(_account, _proposalType, _timepoint);
}

function clock() public view override returns (uint48) {
return votes.clock();
}

function votingPeriod() public view override returns (uint256) {
// ~3 days in blocks (assuming 15s blocks)
return 17_280;
}

function votingDelay() public view override returns (uint256) {
// 1 block
return 1;
}

function quorum(uint256 _timepoint, uint8 _proposalType) public view override returns (uint256) {
// same quorum for all proposals types and timepoints
return 400_000e18;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be cool to see some example where this has logic, like a percentage of IERC20(VoteToken).totalSupply()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For another instance, ofc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, this could be calling votes.getPastTotalSupply(_proposalType, _timepoint) and calculate a percentage of that, or it could also combine with the total supply to ensure that a minimum threshold of voting power is being tracked for new proposal types.

}

function _proposalTypes() internal view override returns (uint8[] memory) {
return __proposalTypes;
}

function _isValidProposalType(uint8 _proposalType) internal view virtual override returns (bool) {
return _proposalType < __proposalTypes.length;
}

function _countVote(
uint256 _proposalId,
address _account,
uint8 _support,
uint256 _weight,
bytes memory _params
) internal virtual override {
if (_support == 0) {
proposalTracks[_proposalId].againstVotes += _weight;
} else if (_support == 1) {
proposalTracks[_proposalId].forVotes += _weight;
} else if (_support == 2) {
proposalTracks[_proposalId].abstainVotes += _weight;
} else {
revert InvalidVoteType(_support);
}

Receipt storage receipt = receipts[_proposalId][_account];

receipt.hasVoted = true;
receipt.support = _support;
receipt.votes = _weight;
}

function hasVoted(uint256 _proposalId, address _account) external view override returns (bool) {
return receipts[_proposalId][_account].hasVoted;
}

function _quorumReached(uint256 _proposalId) internal view virtual override returns (bool) {
ProposalTrack memory _proposalTrack = proposalTracks[_proposalId];
ProposalCore memory _proposal = _getProposal(_proposalId);

uint256 _totalVotes = _proposalTrack.forVotes + _proposalTrack.againstVotes + _proposalTrack.abstainVotes;
return _totalVotes >= quorum(_proposal.voteStart, _proposal.proposalType);
}

function _voteSucceeded(uint256 _proposalId) internal view virtual override returns (bool) {
ProposalTrack memory _proposalTrack = proposalTracks[_proposalId];

bool _succeded = _quorumReached(_proposalId) && _proposalTrack.forVotes > _proposalTrack.againstVotes;
return _succeded;
}

function proposalThreshold(uint8 _proposalType) public view override returns (uint256) {
// same threshold for all proposals types
return 100_000e18;
}

function isValidProposalType(uint8 _proposalType) external view returns (bool) {
return _isValidProposalType(_proposalType);
}

error InvalidVoteType(uint8 voteType);
}
35 changes: 35 additions & 0 deletions solidity/examples/RabbitToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {WonderERC20Votes} from 'contracts/token/ERC20/extensions/WonderERC20Votes.sol';
import {AliceGovernor} from './AliceGovernor.sol';
import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol';
import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';

contract RabbitToken is WonderERC20Votes {
AliceGovernor public governor;

constructor(AliceGovernor _governor) EIP712('RabbitToken', '1') ERC20('RabbitToken', 'RBT') {
governor = _governor;
}

function _getProposalTypes() internal view virtual override returns (uint8[] memory) {
return governor.proposalTypes();
}

function _maxDelegates() internal view virtual override returns (uint8) {
return 4;
}

function _validProposalType(uint8 _proposalType) internal view virtual override returns (bool) {
return governor.isValidProposalType(_proposalType);
}

function _weightNormalizer() internal view virtual override returns (uint256) {
return 100;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hehe <3

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we just use a state variable instead of a function for this? use the getter

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we could. But that will require us to change the WonderVotes parent contract and use a variable we would override or change in the child contract instead a function. I think a function ensures that the child will override it by not having it implemented in the parent contract. We can change this one if we consider that is more convenient constant in a next pr.

}

function proposalTypes() external view returns (uint8[] memory) {
return governor.proposalTypes();
}
}
1 change: 1 addition & 0 deletions solidity/interfaces/governance/utils/IWonderVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ interface IWonderVotes {
*/
function delegateBySig(
Delegate[] memory delegates,
uint8 proposalType,
uint256 nonce,
uint256 expiry,
uint8 v,
Expand Down