From a7719cd10f57d3df8cc59d77a3d653673c42b2e7 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Mon, 18 Dec 2023 13:36:43 -0300 Subject: [PATCH 1/5] feat: add smart and partial delegation Signed-off-by: 0xRaccoon --- .../governance/utils/WonderVotes.sol | 230 ++++++++++++++---- .../governance/utils/IWonderVotes.sol | 99 ++++++-- 2 files changed, 265 insertions(+), 64 deletions(-) diff --git a/solidity/contracts/governance/utils/WonderVotes.sol b/solidity/contracts/governance/utils/WonderVotes.sol index 7249611..c48dcd1 100644 --- a/solidity/contracts/governance/utils/WonderVotes.sol +++ b/solidity/contracts/governance/utils/WonderVotes.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v5.0.0) (governance/utils/Votes.sol) pragma solidity ^0.8.20; -import {IERC5805} from '@openzeppelin/contracts/interfaces/IERC5805.sol'; +import {IERC6372} from '@openzeppelin/contracts/interfaces/IERC6372.sol'; import {Context} from '@openzeppelin/contracts/utils/Context.sol'; import {Nonces} from '@openzeppelin/contracts/utils/Nonces.sol'; import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; @@ -10,6 +10,7 @@ import {Checkpoints} from '@openzeppelin/contracts/utils/structs/Checkpoints.sol import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; import {ECDSA} from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; import {Time} from '@openzeppelin/contracts/utils/types/Time.sol'; +import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol'; /** * @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be @@ -29,16 +30,17 @@ import {Time} from '@openzeppelin/contracts/utils/types/Time.sol'; * {ERC721-balanceOf}), and can use {_transferVotingUnits} to track a change in the distribution of those units (in the * previous example, it would be included in {ERC721-_update}). */ -abstract contract WonderVotes is Context, EIP712, Nonces, IERC5805 { +abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes { using Checkpoints for Checkpoints.Trace208; - bytes32 private constant DELEGATION_TYPEHASH = keccak256('Delegation(address delegatee,uint256 nonce,uint256 expiry)'); + bytes32 private constant DELEGATION_TYPEHASH = + keccak256('Delegation(uint8 proposalType, Delegate[] delegatees,uint256 nonce,uint256 expiry)'); - mapping(address account => address) private _delegatee; + mapping(address account => mapping(uint8 proposalType => Delegate[])) private _delegatees; - mapping(address delegatee => Checkpoints.Trace208) private _delegateCheckpoints; + mapping(address delegatee => mapping(uint8 proposalType => Checkpoints.Trace208)) private _delegateCheckpoints; - Checkpoints.Trace208 private _totalCheckpoints; + mapping(uint8 proposalType => Checkpoints.Trace208) private _totalCheckpoints; /** * @dev The clock was incorrectly modified. @@ -71,30 +73,30 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC5805 { } /** - * @dev Returns the current amount of votes that `account` has. + * @dev Returns the current amount of votes that `account` has for the given `proposalType`. */ - function getVotes(address account) public view virtual returns (uint256) { - return _delegateCheckpoints[account].latest(); + function getVotes(address account, uint8 proposalType) public view virtual returns (uint256) { + return _delegateCheckpoints[account][proposalType].latest(); } /** - * @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is + * @dev Returns the amount of votes that `account` had at a specific moment in the past for a given proposalType. If the `clock()` is * configured to use block numbers, this will return the value at the end of the corresponding block. * * Requirements: * * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ - function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) { + function getPastVotes(address account, uint8 proposalType, uint256 timepoint) public view virtual returns (uint256) { uint48 currentTimepoint = clock(); if (timepoint >= currentTimepoint) { revert ERC5805FutureLookup(timepoint, currentTimepoint); } - return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint48(timepoint)); + return _delegateCheckpoints[account][proposalType].upperLookupRecent(SafeCast.toUint48(timepoint)); } /** - * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is + * @dev Returns for a given `proposalType` the total supply of votes available at a specific moment in the past. If the `clock()` is * configured to use block numbers, this will return the value at the end of the corresponding block. * * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. @@ -105,41 +107,81 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC5805 { * * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ - function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) { + function getPastTotalSupply(uint8 proposalType, uint256 timepoint) public view virtual returns (uint256) { uint48 currentTimepoint = clock(); if (timepoint >= currentTimepoint) { revert ERC5805FutureLookup(timepoint, currentTimepoint); } - return _totalCheckpoints.upperLookupRecent(SafeCast.toUint48(timepoint)); + return _totalCheckpoints[proposalType].upperLookupRecent(SafeCast.toUint48(timepoint)); } /** - * @dev Returns the current total supply of votes. + * @dev Returns the current total supply of votes for a given `proposalType`. */ - function _getTotalSupply() internal view virtual returns (uint256) { - return _totalCheckpoints.latest(); + function _getTotalSupply(uint8 proposalType) internal view virtual returns (uint256) { + return _totalCheckpoints[proposalType].latest(); } /** - * @dev Returns the delegate that `account` has chosen. + * @dev Returns the delegates that `account` has chosen. */ - function delegates(address account) public view virtual returns (address) { - return _delegatee[account]; + function delegates(address account, uint8 proposalType) public view virtual returns (Delegate[] memory) { + return _delegatees[account][proposalType]; } /** * @dev Delegates votes from the sender to `delegatee`. */ + function delegate(Delegate[] calldata delegatees, uint8 proposalType) public virtual { + address account = _msgSender(); + _delegate(account, proposalType, delegatees); + } + + /** + * @dev See {IWonderVotes-delegate}. + */ + function delegate(address delegatee, uint8 proposalType) public virtual { + address account = _msgSender(); + Delegate[] memory _singleDelegate = new Delegate[](1); + _singleDelegate[0] = Delegate({account: delegatee, weight: _totalWeight()}); + _delegate(account, proposalType, _singleDelegate); + } + + /** + * @dev See {IWonderVotes-delegate}. + */ function delegate(address delegatee) public virtual { address account = _msgSender(); - _delegate(account, delegatee); + Delegate[] memory _singleDelegate = new Delegate[](1); + _singleDelegate[0] = Delegate({account: delegatee, weight: _totalWeight()}); + + uint8[] memory proposalTypes = _getProposalTypes(); + + for (uint256 i = 0; i < proposalTypes.length; i++) { + _delegate(account, proposalTypes[i], _singleDelegate); + } + } + + /** + * @dev See {IWonderVotes-totalWeight}. + */ + function totalWeight() external view virtual returns (uint8) { + return _totalWeight(); + } + + /** + * @dev See {IWonderVotes-maxDelegates}. + */ + function maxDelegates() external view returns (uint8) { + return _maxDelegates(); } /** * @dev Delegates votes from signer to `delegatee`. */ function delegateBySig( - address delegatee, + Delegate[] memory delegatees, + uint8 proposalType, uint256 nonce, uint256 expiry, uint8 v, @@ -149,10 +191,49 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC5805 { if (block.timestamp > expiry) { revert VotesExpiredSignature(expiry); } - address signer = - ECDSA.recover(_hashTypedDataV4(keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))), v, r, s); + address signer = ECDSA.recover( + _hashTypedDataV4(keccak256(abi.encode(DELEGATION_TYPEHASH, proposalType, delegatees, nonce, expiry))), v, r, s + ); _useCheckedNonce(signer, nonce); - _delegate(signer, delegatee); + _delegate(signer, proposalType, delegatees); + } + + /** + * @dev See {IWonderVotes-delegateBySig}. + */ + function delegateBySig( + address delegatee, + uint8 proposalType, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + Delegate[] memory _singleDelegate = new Delegate[](1); + _singleDelegate[0] = Delegate({account: delegatee, weight: _totalWeight()}); + delegateBySig(_singleDelegate, proposalType, nonce, expiry, v, r, s); + } + + /** + * @dev See {IWonderVotes-delegateBySig}. + */ + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + Delegate[] memory _singleDelegate = new Delegate[](1); + _singleDelegate[0] = Delegate({account: delegatee, weight: _totalWeight()}); + + uint8[] memory proposalTypes = _getProposalTypes(); + + for (uint256 i = 0; i < proposalTypes.length; i++) { + delegateBySig(_singleDelegate, proposalTypes[i], nonce, expiry, v, r, s); + } } /** @@ -160,56 +241,92 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC5805 { * * Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. */ - function _delegate(address account, address delegatee) internal virtual { - address oldDelegate = delegates(account); - _delegatee[account] = delegatee; + function _delegate(address account, uint8 proposalType, Delegate[] memory delegatees) internal virtual { + if (delegatees.length > _maxDelegates()) revert DelegatesMaxNumberExceeded(delegatees.length); + + uint8 _weightSum; + for (uint256 i = 0; i < delegatees.length; i++) { + if (delegatees[i].weight == 0) revert ZeroWeight(); + _weightSum += delegatees[i].weight; + } + if (_weightSum != _totalWeight()) revert InvalidWeightSum(_weightSum); + + Delegate[] memory _oldDelegates = delegates(account, proposalType); + _delegatees[account][proposalType] = delegatees; + + emit DelegateChanged(account, proposalType, _oldDelegates, delegatees); + _moveDelegateVotes(proposalType, _oldDelegates, delegatees, _getVotingUnits(account)); + } + + /** + * @dev Loops the proposalTypes implemented and calls the `_transferVotingUnits` helper method. + */ + function _transferVotingUnits(address from, address to, uint256 amount) internal virtual { + uint8[] memory _proposalTypes = _getProposalTypes(); - emit DelegateChanged(account, oldDelegate, delegatee); - _moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account)); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _transferVotingUnits(_proposalTypes[i], from, to, amount); + } } /** * @dev Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` * should be zero. Total supply of voting units will be adjusted with mints and burns. */ - function _transferVotingUnits(address from, address to, uint256 amount) internal virtual { + function _transferVotingUnits(uint8 proposalType, address from, address to, uint256 amount) private { if (from == address(0)) { - _push(_totalCheckpoints, _add, SafeCast.toUint208(amount)); + _push(_totalCheckpoints[proposalType], _add, SafeCast.toUint208(amount)); } if (to == address(0)) { - _push(_totalCheckpoints, _subtract, SafeCast.toUint208(amount)); + _push(_totalCheckpoints[proposalType], _subtract, SafeCast.toUint208(amount)); } - _moveDelegateVotes(delegates(from), delegates(to), amount); + _moveDelegateVotes(proposalType, delegates(from, proposalType), delegates(to, proposalType), amount); } /** * @dev Moves delegated votes from one delegate to another. */ - function _moveDelegateVotes(address from, address to, uint256 amount) private { - if (from != to && amount > 0) { - if (from != address(0)) { - (uint256 oldValue, uint256 newValue) = _push(_delegateCheckpoints[from], _subtract, SafeCast.toUint208(amount)); - emit DelegateVotesChanged(from, oldValue, newValue); + function _moveDelegateVotes(uint8 proposalType, Delegate[] memory from, Delegate[] memory to, uint256 amount) private { + uint8 _weightSum = _totalWeight(); + uint8 _weight; + + for (uint256 i = 0; i < from.length; i++) { + if (from[i].account != address(0)) { + _weight = from[i].weight; + uint256 _votingUnits = amount * _weight / _weightSum; + (uint256 oldValue, uint256 newValue) = + _push(_delegateCheckpoints[from[i].account][proposalType], _subtract, SafeCast.toUint208(_votingUnits)); + emit DelegateVotesChanged(from[i].account, proposalType, oldValue, newValue); } - if (to != address(0)) { - (uint256 oldValue, uint256 newValue) = _push(_delegateCheckpoints[to], _add, SafeCast.toUint208(amount)); - emit DelegateVotesChanged(to, oldValue, newValue); + } + + for (uint256 i = 0; i < to.length; i++) { + if (to[i].account != address(0)) { + _weight = to[i].weight; + uint256 _votingUnits = amount * _weight / _weightSum; + (uint256 oldValue, uint256 newValue) = + _push(_delegateCheckpoints[to[i].account][proposalType], _add, SafeCast.toUint208(_votingUnits)); + emit DelegateVotesChanged(to[i].account, proposalType, oldValue, newValue); } } } /** - * @dev Get number of checkpoints for `account`. + * @dev Get number of checkpoints for `account` given a `proposalType`. */ - function _numCheckpoints(address account) internal view virtual returns (uint32) { - return SafeCast.toUint32(_delegateCheckpoints[account].length()); + function _numCheckpoints(address account, uint8 proposalType) internal view virtual returns (uint32) { + return SafeCast.toUint32(_delegateCheckpoints[account][proposalType].length()); } /** - * @dev Get the `pos`-th checkpoint for `account`. + * @dev Get the `pos`-th checkpoint for `account` given a `proposalType`. */ - function _checkpoints(address account, uint32 pos) internal view virtual returns (Checkpoints.Checkpoint208 memory) { - return _delegateCheckpoints[account].at(pos); + function _checkpoints( + address account, + uint8 proposalType, + uint32 pos + ) internal view virtual returns (Checkpoints.Checkpoint208 memory) { + return _delegateCheckpoints[account][proposalType].at(pos); } function _push( @@ -232,4 +349,19 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC5805 { * @dev Must return the voting units held by an account. */ function _getVotingUnits(address) internal view virtual returns (uint256); + + /** + * @dev Returns the total weight that each delegation should sum. + */ + function _totalWeight() internal view virtual returns (uint8); + + /** + * @dev Returns the types of proposals that are supported by the implementation. + */ + function _getProposalTypes() internal view virtual returns (uint8[] memory); + + /** + * @dev Returns the maximum number of delegates that `proposalType` can delegate to. + */ + function _maxDelegates() internal view virtual returns (uint8); } diff --git a/solidity/interfaces/governance/utils/IWonderVotes.sol b/solidity/interfaces/governance/utils/IWonderVotes.sol index 28459a3..3483db9 100644 --- a/solidity/interfaces/governance/utils/IWonderVotes.sol +++ b/solidity/interfaces/governance/utils/IWonderVotes.sol @@ -6,54 +6,123 @@ pragma solidity ^0.8.20; * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. */ interface IWonderVotes { + struct Delegate { + address account; + uint8 weight; + } + /** * @dev The signature used has expired. */ error VotesExpiredSignature(uint256 expiry); /** - * @dev Emitted when an account changes their delegate. + * @dev The weight delegation sum is different from totalWeight. + */ + error InvalidWeightSum(uint8 weightSum); + + /** + * @dev The weight set for a delegate is zero. */ - event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); + error ZeroWeight(); + + /** + * @dev The delegates number for a `proposalType` exceeds the maximum number of delegates. + */ + error DelegatesMaxNumberExceeded(uint256 delegateesNumber); + + /** + * @dev Emitted when an account changes their delegates. + */ + event DelegateChanged( + address indexed delegator, uint8 indexed proposalType, Delegate[] fromDelegates, Delegate[] toDelegates + ); // TODO: check if we should index the rest of arguments /** * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units. */ - event DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes); + event DelegateVotesChanged(address indexed delegate, uint8 proposalType, uint256 previousVotes, uint256 newVotes); /** - * @dev Returns the current amount of votes that `account` has. + * @dev Returns the current amount of votes that `account` has for a `proposalType`. */ - function getVotes(address account) external view returns (uint256); + function getVotes(address account, uint8 proposalType) external view returns (uint256); /** - * @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is - * configured to use block numbers, this will return the value at the end of the corresponding block. + * @dev Returns the amount of votes that `account` had at a specific moment in the past for a given `proposalType`. + * If the `clock()` is configured to use block numbers, this will return the value at the end of the corresponding block. */ - function getPastVotes(address account, uint256 timepoint) external view returns (uint256); + function getPastVotes(address account, uint8 proposalType, uint256 timepoint) external view returns (uint256); /** - * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is + * @dev Returns for a given `proposalType` the total supply of votes available at a specific moment in the past. If the `clock()` is * configured to use block numbers, this will return the value at the end of the corresponding block. * - * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. + * NOTE: This value is the sum of all available votes for a `proposalType`, which is not necessarily the sum of all delegated votes. * Votes that have not been delegated are still part of total supply, even though they would not participate in a * vote. */ - function getPastTotalSupply(uint256 timepoint) external view returns (uint256); + function getPastTotalSupply(uint8 proposalType, uint256 timepoint) external view returns (uint256); /** - * @dev Returns the delegate that `account` has chosen. + * @dev Returns the delegates that `account` has chosen for a given `proposalType`. */ - function delegates(address account) external view returns (address); + function delegates(address account, uint8 proposalType) external view returns (Delegate[] memory); /** - * @dev Delegates votes from the sender to `delegatee`. + * @dev Delegates 100% of the votes for all proposalTypes from the sender to `delegatee`. */ function delegate(address delegatee) external; /** - * @dev Delegates votes from signer to `delegatee`. + * @dev Delegates 100% of the votes for all proposalTypes from the sender to `delegatee` for a given `proposalType`. + */ + function delegate(address delegatee, uint8 proposalType) external; + + /** + * @dev Delegates a specific amount of votes according to the weight from the sender to the `delegates` for a given proposalType. + */ + function delegate(Delegate[] memory delegates, uint8 proposalType) external; + + /** + * @dev Delegates 100% of the votes for all proposalTypes votes from signer to `delegatee`. */ function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external; + + /** + * @dev Delegates 100% of the votes for a specific `proposalType` votes from signer to `delegatee`. + */ + function delegateBySig( + address delegatee, + uint8 proposalType, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Delegates a specific amount of votes according to the weight for a given `proposalType` votes from signer to `delegatee`. + */ + function delegateBySig( + Delegate[] memory delegates, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the amount that represents 100% of the weight sum for every delegation + * used to calculate the amount of votes when partial delegating to more than 1 delegate. + * Example: 100% = 10000 - beware of precision loss from division and overflows from multiplications + */ + function totalWeight() external view returns (uint8); + + /** + * @dev Returns the maximum amount of delegates that a `proposalType` can be delegated to. + */ + function maxDelegates() external view returns (uint8); } From d7d9aa0a476e5a4664cb90e3fb747cec470a3920 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Mon, 18 Dec 2023 16:03:47 -0300 Subject: [PATCH 2/5] fix: add proposalType validity check Signed-off-by: 0xRaccoon --- .../governance/utils/WonderVotes.sol | 21 +++++++++++++++---- .../governance/utils/IWonderVotes.sol | 5 +++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/solidity/contracts/governance/utils/WonderVotes.sol b/solidity/contracts/governance/utils/WonderVotes.sol index c48dcd1..6d85965 100644 --- a/solidity/contracts/governance/utils/WonderVotes.sol +++ b/solidity/contracts/governance/utils/WonderVotes.sol @@ -132,7 +132,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes /** * @dev Delegates votes from the sender to `delegatee`. */ - function delegate(Delegate[] calldata delegatees, uint8 proposalType) public virtual { + function delegate(Delegate[] calldata delegatees, uint8 proposalType) public virtual validProposalType(proposalType) { address account = _msgSender(); _delegate(account, proposalType, delegatees); } @@ -140,7 +140,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes /** * @dev See {IWonderVotes-delegate}. */ - function delegate(address delegatee, uint8 proposalType) public virtual { + function delegate(address delegatee, uint8 proposalType) public virtual validProposalType(proposalType) { address account = _msgSender(); Delegate[] memory _singleDelegate = new Delegate[](1); _singleDelegate[0] = Delegate({account: delegatee, weight: _totalWeight()}); @@ -187,7 +187,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes uint8 v, bytes32 r, bytes32 s - ) public virtual { + ) public virtual validProposalType(proposalType) { if (block.timestamp > expiry) { revert VotesExpiredSignature(expiry); } @@ -209,7 +209,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes uint8 v, bytes32 r, bytes32 s - ) public virtual { + ) public virtual validProposalType(proposalType) { Delegate[] memory _singleDelegate = new Delegate[](1); _singleDelegate[0] = Delegate({account: delegatee, weight: _totalWeight()}); delegateBySig(_singleDelegate, proposalType, nonce, expiry, v, r, s); @@ -364,4 +364,17 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes * @dev Returns the maximum number of delegates that `proposalType` can delegate to. */ function _maxDelegates() internal view virtual returns (uint8); + + /** + * @dev Returns true if the `proposalType` is valid, false otherwise. + */ + function _validProposalType(uint8 proposalType) internal view virtual returns (bool); + + /** + * @dev checks the `proposalType` validity + */ + modifier validProposalType(uint8 proposalType) { + if (!_validProposalType(proposalType)) revert InvalidProposalType(proposalType); + _; + } } diff --git a/solidity/interfaces/governance/utils/IWonderVotes.sol b/solidity/interfaces/governance/utils/IWonderVotes.sol index 3483db9..3989e0d 100644 --- a/solidity/interfaces/governance/utils/IWonderVotes.sol +++ b/solidity/interfaces/governance/utils/IWonderVotes.sol @@ -26,6 +26,11 @@ interface IWonderVotes { */ error ZeroWeight(); + /** + * @dev The proposal type is invalid. + */ + error InvalidProposalType(uint8 proposalType); + /** * @dev The delegates number for a `proposalType` exceeds the maximum number of delegates. */ From a041f0c5a85369069d4926114d719b5bda48ae36 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Tue, 19 Dec 2023 10:25:54 -0300 Subject: [PATCH 3/5] fix: use uint256 type for weights Signed-off-by: 0xRaccoon --- solidity/contracts/governance/utils/WonderVotes.sol | 10 +++++----- solidity/interfaces/governance/utils/IWonderVotes.sol | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/solidity/contracts/governance/utils/WonderVotes.sol b/solidity/contracts/governance/utils/WonderVotes.sol index 6d85965..e4b22fa 100644 --- a/solidity/contracts/governance/utils/WonderVotes.sol +++ b/solidity/contracts/governance/utils/WonderVotes.sol @@ -165,7 +165,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes /** * @dev See {IWonderVotes-totalWeight}. */ - function totalWeight() external view virtual returns (uint8) { + function totalWeight() external view virtual returns (uint256) { return _totalWeight(); } @@ -244,7 +244,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes function _delegate(address account, uint8 proposalType, Delegate[] memory delegatees) internal virtual { if (delegatees.length > _maxDelegates()) revert DelegatesMaxNumberExceeded(delegatees.length); - uint8 _weightSum; + uint256 _weightSum; for (uint256 i = 0; i < delegatees.length; i++) { if (delegatees[i].weight == 0) revert ZeroWeight(); _weightSum += delegatees[i].weight; @@ -287,8 +287,8 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes * @dev Moves delegated votes from one delegate to another. */ function _moveDelegateVotes(uint8 proposalType, Delegate[] memory from, Delegate[] memory to, uint256 amount) private { - uint8 _weightSum = _totalWeight(); - uint8 _weight; + uint256 _weightSum = _totalWeight(); + uint256 _weight; for (uint256 i = 0; i < from.length; i++) { if (from[i].account != address(0)) { @@ -353,7 +353,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes /** * @dev Returns the total weight that each delegation should sum. */ - function _totalWeight() internal view virtual returns (uint8); + function _totalWeight() internal view virtual returns (uint256); /** * @dev Returns the types of proposals that are supported by the implementation. diff --git a/solidity/interfaces/governance/utils/IWonderVotes.sol b/solidity/interfaces/governance/utils/IWonderVotes.sol index 3989e0d..2f6c8af 100644 --- a/solidity/interfaces/governance/utils/IWonderVotes.sol +++ b/solidity/interfaces/governance/utils/IWonderVotes.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.20; interface IWonderVotes { struct Delegate { address account; - uint8 weight; + uint256 weight; } /** @@ -19,7 +19,7 @@ interface IWonderVotes { /** * @dev The weight delegation sum is different from totalWeight. */ - error InvalidWeightSum(uint8 weightSum); + error InvalidWeightSum(uint256 weightSum); /** * @dev The weight set for a delegate is zero. @@ -124,7 +124,7 @@ interface IWonderVotes { * used to calculate the amount of votes when partial delegating to more than 1 delegate. * Example: 100% = 10000 - beware of precision loss from division and overflows from multiplications */ - function totalWeight() external view returns (uint8); + function totalWeight() external view returns (uint256); /** * @dev Returns the maximum amount of delegates that a `proposalType` can be delegated to. From 674f2f27be264e05d54208654080bf0621af063a Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Tue, 19 Dec 2023 10:55:48 -0300 Subject: [PATCH 4/5] feat: implement WonderVotes in abstract WonderERC20Votes Signed-off-by: 0xRaccoon --- .../ERC20/extensions/WonderERC20Votes.sol | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/solidity/contracts/token/ERC20/extensions/WonderERC20Votes.sol b/solidity/contracts/token/ERC20/extensions/WonderERC20Votes.sol index e0b8a35..0cafdd0 100644 --- a/solidity/contracts/token/ERC20/extensions/WonderERC20Votes.sol +++ b/solidity/contracts/token/ERC20/extensions/WonderERC20Votes.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.20; import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; -import {Votes} from '@openzeppelin/contracts/governance/utils/Votes.sol'; import {Checkpoints} from '@openzeppelin/contracts/utils/structs/Checkpoints.sol'; +import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol'; /** * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, @@ -20,7 +20,7 @@ import {Checkpoints} from '@openzeppelin/contracts/utils/structs/Checkpoints.sol * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. */ -abstract contract WonderERC20Votes is ERC20, Votes { +abstract contract WonderERC20Votes is ERC20, WonderVotes { /** * @dev Total supply cap has been exceeded, introducing a risk of votes overflowing. */ @@ -68,16 +68,20 @@ abstract contract WonderERC20Votes is ERC20, Votes { } /** - * @dev Get number of checkpoints for `account`. + * @dev Get number of checkpoints for `account` given a `proposalType`. */ - function numCheckpoints(address account) public view virtual returns (uint32) { - return _numCheckpoints(account); + function numCheckpoints(address account, uint8 proposalType) public view virtual returns (uint32) { + return _numCheckpoints(account, proposalType); } /** - * @dev Get the `pos`-th checkpoint for `account`. + * @dev Get the `pos`-th checkpoint for `account` given a `proposalType`. */ - function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint208 memory) { - return _checkpoints(account, pos); + function checkpoints( + address account, + uint8 proposalType, + uint32 pos + ) public view virtual returns (Checkpoints.Checkpoint208 memory) { + return _checkpoints(account, proposalType, pos); } } From 7cbc6fd5e22a3d53ff5d12f13de263660d01c8c6 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Wed, 20 Dec 2023 11:34:45 -0300 Subject: [PATCH 5/5] fix: docs Signed-off-by: 0xRaccoon --- solidity/interfaces/governance/utils/IWonderVotes.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/solidity/interfaces/governance/utils/IWonderVotes.sol b/solidity/interfaces/governance/utils/IWonderVotes.sol index f8944eb..c595243 100644 --- a/solidity/interfaces/governance/utils/IWonderVotes.sol +++ b/solidity/interfaces/governance/utils/IWonderVotes.sol @@ -17,11 +17,7 @@ interface IWonderVotes { error VotesExpiredSignature(uint256 expiry); /** - * <<<<<<< HEAD - * @dev The weight delegation sum is different from totalWeight. - * ======= * @dev The weight delegation sum is different from weightNormalizer. - * >>>>>>> c4a138a767bdf917be05f63ff44a3bb810d8b9db */ error InvalidWeightSum(uint256 weightSum);