From 5bfd84bd7291f34f034aeb71bcab059547047d39 Mon Sep 17 00:00:00 2001 From: 0xRaccoon <112493530+0xRaccoon@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:18:32 -0300 Subject: [PATCH] feat: add wonderGovernor and wonderVotes contract examples Signed-off-by: 0xRaccoon --- .gitignore | 1 + .../contracts/governance/WonderGovernor.sol | 4 + .../governance/utils/WonderVotes.sol | 6 +- solidity/examples/AliceGovernor.sol | 134 ++++++++++++++++++ solidity/examples/RabbitToken.sol | 35 +++++ .../governance/utils/IWonderVotes.sol | 1 + 6 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 solidity/examples/AliceGovernor.sol create mode 100644 solidity/examples/RabbitToken.sol diff --git a/.gitignore b/.gitignore index 9be8a61..96330bd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ out-via-ir # Config files .env +.vscode # Avoid ignoring gitkeep !/**/.gitkeep diff --git a/solidity/contracts/governance/WonderGovernor.sol b/solidity/contracts/governance/WonderGovernor.sol index 67d2f87..f564ded 100644 --- a/solidity/contracts/governance/WonderGovernor.sol +++ b/solidity/contracts/governance/WonderGovernor.sol @@ -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]; + } } diff --git a/solidity/contracts/governance/utils/WonderVotes.sol b/solidity/contracts/governance/utils/WonderVotes.sol index f2903f2..1b3db47 100644 --- a/solidity/contracts/governance/utils/WonderVotes.sol +++ b/solidity/contracts/governance/utils/WonderVotes.sol @@ -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)); diff --git a/solidity/examples/AliceGovernor.sol b/solidity/examples/AliceGovernor.sol new file mode 100644 index 0000000..75b2930 --- /dev/null +++ b/solidity/examples/AliceGovernor.sol @@ -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; + } + + 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); +} diff --git a/solidity/examples/RabbitToken.sol b/solidity/examples/RabbitToken.sol new file mode 100644 index 0000000..f0112a8 --- /dev/null +++ b/solidity/examples/RabbitToken.sol @@ -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; + } + + function proposalTypes() external view returns (uint8[] memory) { + return governor.proposalTypes(); + } +} diff --git a/solidity/interfaces/governance/utils/IWonderVotes.sol b/solidity/interfaces/governance/utils/IWonderVotes.sol index c595243..db510b8 100644 --- a/solidity/interfaces/governance/utils/IWonderVotes.sol +++ b/solidity/interfaces/governance/utils/IWonderVotes.sol @@ -112,6 +112,7 @@ interface IWonderVotes { */ function delegateBySig( Delegate[] memory delegates, + uint8 proposalType, uint256 nonce, uint256 expiry, uint8 v,