Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ERC20Votes independent from ERC20Permit #3816

Merged
merged 24 commits into from
Nov 29, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f28ede6
Add Nonces contract
JulissaDantes Nov 14, 2022
1931d64
Remove permit from erc20votes
JulissaDantes Nov 15, 2022
cc25b3f
Update ERC20Permit contract
JulissaDantes Nov 15, 2022
39bd173
Update Changelog
JulissaDantes Nov 15, 2022
5df3792
Add Nonces dcumentation
JulissaDantes Nov 15, 2022
b9c425f
Update changelog with PR number
JulissaDantes Nov 16, 2022
485a322
Use Votes for ERC20Votes
JulissaDantes Nov 17, 2022
f40bf1c
Remove duplicate tests
JulissaDantes Nov 21, 2022
1c07a94
Fix Votes tests
JulissaDantes Nov 21, 2022
f4baabe
update changelog
JulissaDantes Nov 22, 2022
a7ec549
Update CHANGELOG.md
JulissaDantes Nov 25, 2022
8fab231
Merge branch 'next-v5.0' into refactor/erc20-votes
JulissaDantes Nov 25, 2022
5202e0d
Add test case
JulissaDantes Nov 25, 2022
0a2a857
Update test
JulissaDantes Nov 25, 2022
c140d72
Merge branch 'refactor/erc20-votes' of https://github.com/JulissaDant…
JulissaDantes Nov 25, 2022
cffa45c
Update tests
JulissaDantes Nov 25, 2022
f881aad
Merge branch 'next-v5.0' into refactor/erc20-votes
JulissaDantes Nov 25, 2022
390f04a
Add numCheckpoints
JulissaDantes Nov 28, 2022
b41574c
Add tests to ERC20VotesComp
JulissaDantes Nov 28, 2022
4cda6a0
Update template
JulissaDantes Nov 28, 2022
c2a9537
Update contracts/token/ERC20/extensions/ERC20Votes.sol
JulissaDantes Nov 29, 2022
6adfc96
Update contracts/token/ERC20/extensions/ERC20Votes.sol
JulissaDantes Nov 29, 2022
e31342b
Update contracts/utils/Checkpoints.sol
JulissaDantes Nov 29, 2022
3f001ad
update template
JulissaDantes Nov 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use Votes for ERC20Votes
  • Loading branch information
JulissaDantes committed Nov 17, 2022
commit 485a322a9db35c3aaf65c9367799775cf675b201
2 changes: 1 addition & 1 deletion contracts/mocks/ERC721VotesMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ contract ERC721VotesMock is ERC721Votes {
_mint(account, tokenId);
}

function burn(uint256 tokenId) public {
function burn(address, uint256 tokenId) public {
_burn(tokenId);
}

Expand Down
228 changes: 8 additions & 220 deletions contracts/token/ERC20/extensions/ERC20Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@
pragma solidity ^0.8.0;

import "../ERC20.sol";
import "../../../utils/math/Math.sol";
import "../../../utils/Nonces.sol";
import "../../../governance/utils/IVotes.sol";
import "../../../governance/utils/Votes.sol";
import "../../../utils/math/SafeCast.sol";
import "../../../utils/cryptography/EIP712.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,
Expand All @@ -26,148 +22,7 @@ import "../../../utils/cryptography/ECDSA.sol";
*
* _Available since v4.2._
*/
abstract contract ERC20Votes is ERC20, IVotes, Nonces, EIP712 {
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).
*/
Expand All @@ -181,17 +36,15 @@ abstract contract ERC20Votes is ERC20, IVotes, Nonces, EIP712 {
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);
_transferVotingUnits(address(0), account, 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);
_transferVotingUnits(account, address(0), amount);
}

/**
Expand All @@ -204,79 +57,14 @@ abstract contract ERC20Votes is ERC20, IVotes, Nonces, EIP712 {
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 Returns the balance of `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 _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;
}

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)
}
function _getVotingUnits(address account) internal view virtual override returns (uint256) {
return balanceOf(account);
}
}
Loading