|
@@ -3,11 +3,9 @@
|
|
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
|
|
-import "./ERC20Permit.sol";
|
|
|
-import "../../../utils/math/Math.sol";
|
|
|
-import "../../../governance/utils/IVotes.sol";
|
|
|
+import "../ERC20.sol";
|
|
|
+import "../../../governance/utils/Votes.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,
|
|
@@ -24,148 +22,7 @@ import "../../../utils/cryptography/ECDSA.sol";
|
|
|
*
|
|
|
* _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);
|
|
|
- }
|
|
|
-
|
|
|
+abstract contract ERC20Votes is ERC20, Votes {
|
|
|
/**
|
|
|
* @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1).
|
|
|
*/
|
|
@@ -173,25 +30,6 @@ abstract contract ERC20Votes is IVotes, ERC20Permit {
|
|
|
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.
|
|
|
*
|
|
@@ -202,79 +40,36 @@ abstract contract ERC20Votes is IVotes, ERC20Permit {
|
|
|
address to,
|
|
|
uint256 amount
|
|
|
) internal virtual override {
|
|
|
+ _transferVotingUnits(from, to, amount);
|
|
|
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}.
|
|
|
+ * @dev Get number of checkpoints for `account`.
|
|
|
*/
|
|
|
- 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 numCheckpoints(address account) public view virtual returns (uint32) {
|
|
|
+ return _numCheckpoints(account);
|
|
|
}
|
|
|
|
|
|
- function _add(uint256 a, uint256 b) private pure returns (uint256) {
|
|
|
- return a + b;
|
|
|
+ /**
|
|
|
+ * @dev Get the `pos`-th checkpoint for `account`.
|
|
|
+ */
|
|
|
+ function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint memory) {
|
|
|
+ return _checkpoints(account, pos);
|
|
|
}
|
|
|
|
|
|
- function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
|
|
|
- return a - b;
|
|
|
+ /**
|
|
|
+ * @dev Returns the balance of `account`.
|
|
|
+ */
|
|
|
+ function _getVotingUnits(address account) internal view virtual override returns (uint256) {
|
|
|
+ return balanceOf(account);
|
|
|
}
|
|
|
|
|
|
- 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)
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * @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");
|
|
|
}
|
|
|
}
|