Created
July 4, 2023 17:13
-
-
Save ernestognw/11639e6320b73d10a4d354043f652f2d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
// OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/Votes.sol) | |
pragma solidity ^0.8.19; | |
import {IERC5805} from "../../interfaces/IERC5805.sol"; | |
import {Context} from "../../utils/Context.sol"; | |
import {Nonces} from "../../utils/Nonces.sol"; | |
import {EIP712} from "../../utils/cryptography/EIP712.sol"; | |
import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol"; | |
import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; | |
import {SafeCast} from "../../utils/math/SafeCast.sol"; | |
import {ECDSA} from "../../utils/cryptography/ECDSA.sol"; | |
/** | |
* @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be | |
* transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of | |
* "representative" that will pool delegated voting units from different accounts and can then use it to vote in | |
* decisions. In fact, voting units _must_ be delegated in order to count as actual votes, and an account has to | |
* delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. | |
* | |
* This contract is often combined with a token contract such that voting units correspond to token units. For an | |
* example, see {ERC721Votes}. | |
* | |
* The full history of delegate votes is tracked on-chain so that governance protocols can consider votes as distributed | |
* at a particular block number to protect against flash loans and double voting. The opt-in delegate system makes the | |
* cost of this history tracking optional. | |
* | |
* When using this module the derived contract must implement {_getVotingUnits} (for example, make it return | |
* {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-_beforeTokenTransfer}). | |
* | |
* _Available since v4.5._ | |
*/ | |
abstract contract Votes is Context, EIP712, Nonces, IERC5805 { | |
using Checkpoints for Checkpoints.Trace224; | |
bytes32 private constant _DELEGATION_TYPEHASH = | |
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); | |
mapping(address => address) private _delegation; | |
/// @custom:oz-retyped-from mapping(address => Checkpoints.History) | |
mapping(address => Checkpoints.Trace224) private _delegateCheckpoints; | |
/// @custom:oz-retyped-from Checkpoints.History | |
Checkpoints.Trace224 private _totalCheckpoints; | |
/** | |
* @dev The clock was incorrectly modified. | |
*/ | |
error ERC6372InconsistentClock(); | |
/** | |
* @dev Lookup to future votes is not available. | |
*/ | |
error ERC5805FutureLookup(uint256 timepoint, uint48 clock); | |
/** | |
* @dev Clock used for flagging checkpoints. Can be overridden to implement timestamp based | |
* checkpoints (and voting), in which case {CLOCK_MODE} should be overridden as well to match. | |
*/ | |
function clock() public view virtual returns (uint48) { | |
return SafeCast.toUint48(block.number); | |
} | |
/** | |
* @dev Machine-readable description of the clock as specified in EIP-6372. | |
*/ | |
// solhint-disable-next-line func-name-mixedcase | |
function CLOCK_MODE() public view virtual returns (string memory) { | |
// Check that the clock was not modified | |
if (clock() != block.number) { | |
revert ERC6372InconsistentClock(); | |
} | |
return "mode=blocknumber&from=default"; | |
} | |
/** | |
* @dev Returns the current amount of votes that `account` has. | |
*/ | |
function getVotes(address account) public view virtual returns (uint256) { | |
return _delegateCheckpoints[account].latest(); | |
} | |
/** | |
* @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. | |
* | |
* 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) { | |
uint48 currentTimepoint = clock(); | |
if (timepoint >= currentTimepoint) { | |
revert ERC5805FutureLookup(timepoint, currentTimepoint); | |
} | |
return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint32(timepoint)); | |
} | |
/** | |
* @dev Returns 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. | |
* Votes that have not been delegated are still part of total supply, even though they would not participate in a | |
* vote. | |
* | |
* Requirements: | |
* | |
* - `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) { | |
uint48 currentTimepoint = clock(); | |
if (timepoint >= currentTimepoint) { | |
revert ERC5805FutureLookup(timepoint, currentTimepoint); | |
} | |
return _totalCheckpoints.upperLookupRecent(SafeCast.toUint32(timepoint)); | |
} | |
/** | |
* @dev Returns the current total supply of votes. | |
*/ | |
function _getTotalSupply() internal view virtual returns (uint256) { | |
return _totalCheckpoints.latest(); | |
} | |
/** | |
* @dev Returns the delegate that `account` has chosen. | |
*/ | |
function delegates(address account) public view virtual returns (address) { | |
return _delegation[account]; | |
} | |
/** | |
* @dev Delegates votes from the sender to `delegatee`. | |
*/ | |
function delegate(address delegatee) public virtual { | |
address account = _msgSender(); | |
_delegate(account, delegatee); | |
} | |
/** | |
* @dev Delegates votes from signer to `delegatee`. | |
*/ | |
function delegateBySig( | |
address delegatee, | |
uint256 nonce, | |
uint256 expiry, | |
uint8 v, | |
bytes32 r, | |
bytes32 s | |
) public virtual { | |
if (block.timestamp > expiry) { | |
revert VotesExpiredSignature(expiry); | |
} | |
address signer = ECDSA.recover( | |
_hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), | |
v, | |
r, | |
s | |
); | |
_useCheckedNonce(signer, nonce); | |
_delegate(signer, delegatee); | |
} | |
/** | |
* @dev Delegates votes from signer to `delegatee`. | |
*/ | |
function delegateBySig(address delegatee, address signer, uint256 expiry, bytes memory signature) public virtual { | |
if (block.timestamp > expiry) { | |
revert VotesExpiredSignature(expiry); | |
} | |
bool valid = SignatureChecker.isValidSignatureNow( | |
signer, | |
_hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, _useNonce(signer), expiry))), | |
signature | |
); | |
if (!valid) { | |
revert VotesInvalidSignature(signer); | |
} | |
_delegate(signer, delegatee); | |
} | |
/** | |
* @dev Delegate all of `account`'s voting units to `delegatee`. | |
* | |
* Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. | |
*/ | |
function _delegate(address account, address delegatee) internal virtual { | |
address oldDelegate = delegates(account); | |
_delegation[account] = delegatee; | |
emit DelegateChanged(account, oldDelegate, delegatee); | |
_moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account)); | |
} | |
/** | |
* @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 { | |
if (from == address(0)) { | |
_push(_totalCheckpoints, _add, SafeCast.toUint224(amount)); | |
} | |
if (to == address(0)) { | |
_push(_totalCheckpoints, _subtract, SafeCast.toUint224(amount)); | |
} | |
_moveDelegateVotes(delegates(from), delegates(to), 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.toUint224(amount) | |
); | |
emit DelegateVotesChanged(from, oldValue, newValue); | |
} | |
if (to != address(0)) { | |
(uint256 oldValue, uint256 newValue) = _push( | |
_delegateCheckpoints[to], | |
_add, | |
SafeCast.toUint224(amount) | |
); | |
emit DelegateVotesChanged(to, oldValue, newValue); | |
} | |
} | |
} | |
/** | |
* @dev Get number of checkpoints for `account`. | |
*/ | |
function _numCheckpoints(address account) internal view virtual returns (uint32) { | |
return SafeCast.toUint32(_delegateCheckpoints[account].length()); | |
} | |
/** | |
* @dev Get the `pos`-th checkpoint for `account`. | |
*/ | |
function _checkpoints( | |
address account, | |
uint32 pos | |
) internal view virtual returns (Checkpoints.Checkpoint224 memory) { | |
return _delegateCheckpoints[account].at(pos); | |
} | |
function _push( | |
Checkpoints.Trace224 storage store, | |
function(uint224, uint224) view returns (uint224) op, | |
uint224 delta | |
) private returns (uint224, uint224) { | |
return store.push(SafeCast.toUint32(clock()), op(store.latest(), delta)); | |
} | |
function _add(uint224 a, uint224 b) private pure returns (uint224) { | |
return a + b; | |
} | |
function _subtract(uint224 a, uint224 b) private pure returns (uint224) { | |
return a - b; | |
} | |
/** | |
* @dev Must return the voting units held by an account. | |
*/ | |
function _getVotingUnits(address) internal view virtual returns (uint256); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment