You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
10 KiB
Solidity
276 lines
10 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC20Votes.sol)
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "./ERC20Permit.sol";
|
|
import "../../../utils/math/Math.sol";
|
|
import "../../../governance/utils/IVotes.sol";
|
|
import "../../../utils/math/SafeCast.sol";
|
|
import "../../../utils/cryptography/ECDSA.sol";
|
|
|
|
/**
|
|
* @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's,
|
|
* and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1.
|
|
*
|
|
* NOTE: If exact COMP compatibility is required, use the {ERC20VotesComp} variant of this module.
|
|
*
|
|
* This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either
|
|
* by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting
|
|
* power can be queried through the public accessors {getVotes} and {getPastVotes}.
|
|
*
|
|
* 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.
|
|
*
|
|
* _Available since v4.2._
|
|
*/
|
|
abstract contract ERC20Votes is IVotes, ERC20Permit {
|
|
struct Checkpoint {
|
|
uint32 fromBlock;
|
|
uint224 votes;
|
|
}
|
|
|
|
bytes32 private constant _DELEGATION_TYPEHASH =
|
|
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
|
|
|
|
mapping(address => address) private _delegates;
|
|
mapping(address => Checkpoint[]) private _checkpoints;
|
|
Checkpoint[] private _totalSupplyCheckpoints;
|
|
|
|
/**
|
|
* @dev Get the `pos`-th checkpoint for `account`.
|
|
*/
|
|
function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) {
|
|
return _checkpoints[account][pos];
|
|
}
|
|
|
|
/**
|
|
* @dev Get number of checkpoints for `account`.
|
|
*/
|
|
function numCheckpoints(address account) public view virtual returns (uint32) {
|
|
return SafeCast.toUint32(_checkpoints[account].length);
|
|
}
|
|
|
|
/**
|
|
* @dev Get the address `account` is currently delegating to.
|
|
*/
|
|
function delegates(address account) public view virtual override returns (address) {
|
|
return _delegates[account];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the current votes balance for `account`
|
|
*/
|
|
function getVotes(address account) public view virtual override returns (uint256) {
|
|
uint256 pos = _checkpoints[account].length;
|
|
unchecked {
|
|
return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Retrieve the number of votes for `account` at the end of `blockNumber`.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - `blockNumber` must have been already mined
|
|
*/
|
|
function getPastVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) {
|
|
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
|
|
return _checkpointsLookup(_checkpoints[account], blockNumber);
|
|
}
|
|
|
|
/**
|
|
* @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances.
|
|
* It is NOT the sum of all the delegated votes!
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - `blockNumber` must have been already mined
|
|
*/
|
|
function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) {
|
|
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
|
|
return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
|
|
}
|
|
|
|
/**
|
|
* @dev Lookup a value in a list of (sorted) checkpoints.
|
|
*/
|
|
function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256) {
|
|
// We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
|
|
//
|
|
// Initially we check if the block is recent to narrow the search range.
|
|
// During the loop, the index of the wanted checkpoint remains in the range [low-1, high).
|
|
// With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant.
|
|
// - If the middle checkpoint is after `blockNumber`, we look in [low, mid)
|
|
// - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high)
|
|
// Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not
|
|
// out of bounds (in which case we're looking too far in the past and the result is 0).
|
|
// Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is
|
|
// past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out
|
|
// the same.
|
|
uint256 length = ckpts.length;
|
|
|
|
uint256 low = 0;
|
|
uint256 high = length;
|
|
|
|
if (length > 5) {
|
|
uint256 mid = length - Math.sqrt(length);
|
|
if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) {
|
|
high = mid;
|
|
} else {
|
|
low = mid + 1;
|
|
}
|
|
}
|
|
|
|
while (low < high) {
|
|
uint256 mid = Math.average(low, high);
|
|
if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) {
|
|
high = mid;
|
|
} else {
|
|
low = mid + 1;
|
|
}
|
|
}
|
|
|
|
unchecked {
|
|
return high == 0 ? 0 : _unsafeAccess(ckpts, high - 1).votes;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Delegate votes from the sender to `delegatee`.
|
|
*/
|
|
function delegate(address delegatee) public virtual override {
|
|
_delegate(_msgSender(), delegatee);
|
|
}
|
|
|
|
/**
|
|
* @dev Delegates votes from signer to `delegatee`
|
|
*/
|
|
function delegateBySig(
|
|
address delegatee,
|
|
uint256 nonce,
|
|
uint256 expiry,
|
|
uint8 v,
|
|
bytes32 r,
|
|
bytes32 s
|
|
) public virtual override {
|
|
require(block.timestamp <= expiry, "ERC20Votes: signature expired");
|
|
address signer = ECDSA.recover(
|
|
_hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))),
|
|
v,
|
|
r,
|
|
s
|
|
);
|
|
require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
|
|
_delegate(signer, delegatee);
|
|
}
|
|
|
|
/**
|
|
* @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1).
|
|
*/
|
|
function _maxSupply() internal view virtual returns (uint224) {
|
|
return type(uint224).max;
|
|
}
|
|
|
|
/**
|
|
* @dev Snapshots the totalSupply after it has been increased.
|
|
*/
|
|
function _mint(address account, uint256 amount) internal virtual override {
|
|
super._mint(account, amount);
|
|
require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes");
|
|
|
|
_writeCheckpoint(_totalSupplyCheckpoints, _add, amount);
|
|
}
|
|
|
|
/**
|
|
* @dev Snapshots the totalSupply after it has been decreased.
|
|
*/
|
|
function _burn(address account, uint256 amount) internal virtual override {
|
|
super._burn(account, amount);
|
|
|
|
_writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount);
|
|
}
|
|
|
|
/**
|
|
* @dev Move voting power when tokens are transferred.
|
|
*
|
|
* Emits a {IVotes-DelegateVotesChanged} event.
|
|
*/
|
|
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual override {
|
|
super._afterTokenTransfer(from, to, amount);
|
|
|
|
_moveVotingPower(delegates(from), delegates(to), amount);
|
|
}
|
|
|
|
/**
|
|
* @dev Change delegation for `delegator` to `delegatee`.
|
|
*
|
|
* Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}.
|
|
*/
|
|
function _delegate(address delegator, address delegatee) internal virtual {
|
|
address currentDelegate = delegates(delegator);
|
|
uint256 delegatorBalance = balanceOf(delegator);
|
|
_delegates[delegator] = delegatee;
|
|
|
|
emit DelegateChanged(delegator, currentDelegate, delegatee);
|
|
|
|
_moveVotingPower(currentDelegate, delegatee, delegatorBalance);
|
|
}
|
|
|
|
function _moveVotingPower(address src, address dst, uint256 amount) private {
|
|
if (src != dst && amount > 0) {
|
|
if (src != address(0)) {
|
|
(uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount);
|
|
emit DelegateVotesChanged(src, oldWeight, newWeight);
|
|
}
|
|
|
|
if (dst != address(0)) {
|
|
(uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], _add, amount);
|
|
emit DelegateVotesChanged(dst, oldWeight, newWeight);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _writeCheckpoint(
|
|
Checkpoint[] storage ckpts,
|
|
function(uint256, uint256) view returns (uint256) op,
|
|
uint256 delta
|
|
) private returns (uint256 oldWeight, uint256 newWeight) {
|
|
uint256 pos = ckpts.length;
|
|
|
|
unchecked {
|
|
Checkpoint memory oldCkpt = pos == 0 ? Checkpoint(0, 0) : _unsafeAccess(ckpts, pos - 1);
|
|
|
|
oldWeight = oldCkpt.votes;
|
|
newWeight = op(oldWeight, delta);
|
|
|
|
if (pos > 0 && oldCkpt.fromBlock == block.number) {
|
|
_unsafeAccess(ckpts, pos - 1).votes = SafeCast.toUint224(newWeight);
|
|
} else {
|
|
ckpts.push(
|
|
Checkpoint({fromBlock: SafeCast.toUint32(block.number), votes: SafeCast.toUint224(newWeight)})
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _add(uint256 a, uint256 b) private pure returns (uint256) {
|
|
return a + b;
|
|
}
|
|
|
|
function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
|
|
return a - b;
|
|
}
|
|
|
|
/**
|
|
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
|
|
*/
|
|
function _unsafeAccess(Checkpoint[] storage ckpts, uint256 pos) private pure returns (Checkpoint storage result) {
|
|
assembly {
|
|
mstore(0, ckpts.slot)
|
|
result.slot := add(keccak256(0, 0x20), pos)
|
|
}
|
|
}
|
|
}
|