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

Split ERC20Votes and ERC20VotesComp #2706

Merged
merged 11 commits into from
Jun 4, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

## Unreleased

* `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. This extension is compatible with Compound's `Comp` token interface. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632))
* `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632))
* `ERC20VotesComp`: Variant of `ERC20Votes` that is compatible with Compound's `Comp` token interface but restricts supply to `uint96`. ([#2706](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2706))
* Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`.
* Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`.
* `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678))
* Tokens: Wrap definitely safe subtractions in `unchecked` blocks.
* Tokens: Wrap definitely safe subtractions in `unchecked` blocks.
* `Math`: Add a `ceilDiv` method for performing ceiling division.
* `ERC1155Supply`: add a new `ERC1155` extension that keeps track of the totalSupply of each tokenId. ([#2593](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2593))

### Breaking Changes

* `ERC20FlashMint` is no longer a Draft ERC. ([#2673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2673)))

**How to update:** Change your import paths by removing the `draft-` prefix from `@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20FlashMint.sol`.

> See [Releases and Stability: Drafts](https://docs.openzeppelin.com/contracts/4.x/releases-stability#drafts).
Expand Down
25 changes: 25 additions & 0 deletions contracts/mocks/ERC20VotesCompMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;


import "../token/ERC20/extensions/ERC20VotesComp.sol";

contract ERC20VotesCompMock is ERC20VotesComp {
constructor (string memory name, string memory symbol)
ERC20(name, symbol)
ERC20Permit(name)
{}

function mint(address account, uint256 amount) public {
_mint(account, amount);
}

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

function getChainId() external view returns (uint256) {
return block.chainid;
}
}
12 changes: 10 additions & 2 deletions contracts/mocks/SafeCastMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ contract SafeCastMock {
return a.toUint256();
}

function toInt256(uint a) public pure returns (int256) {
return a.toInt256();
function toUint224(uint a) public pure returns (uint224) {
return a.toUint224();
}

function toUint128(uint a) public pure returns (uint128) {
return a.toUint128();
}

function toUint96(uint a) public pure returns (uint96) {
return a.toUint96();
}

function toUint64(uint a) public pure returns (uint64) {
return a.toUint64();
}
Expand All @@ -36,6 +40,10 @@ contract SafeCastMock {
return a.toUint8();
}

function toInt256(uint a) public pure returns (int256) {
return a.toInt256();
}

function toInt128(int a) public pure returns (int128) {
return a.toInt128();
}
Expand Down
3 changes: 2 additions & 1 deletion contracts/token/ERC20/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ Additionally there are multiple custom extensions, including:
* {ERC20Snapshot}: efficient storage of past token balances to be later queried at any point in time.
* {ERC20Permit}: gasless approval of tokens (standardized as ERC2612).
* {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156).
* {ERC20Votes}: support for voting and vote delegation (compatible with Compound's token).
* {ERC20Votes}: support for voting and vote delegation.
* {ERC20VotesComp}: support for voting and vote delegation (compatible with Compound's tokenn, with uint96 restrictions).

Finally, there are some utilities to interact with ERC20 contracts in various ways.

Expand Down
40 changes: 28 additions & 12 deletions contracts/token/ERC20/extensions/ERC20Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
pragma solidity ^0.8.0;

import "./draft-ERC20Permit.sol";
import "./IERC20Votes.sol";
import "../../../utils/math/Math.sol";
import "../../../utils/math/SafeCast.sol";
import "../../../utils/cryptography/ECDSA.sol";

/**
* @dev Extension of the ERC20 token contract to support Compound's voting and delegation.
* @dev Extension of the ERC20 token contract to support Compound like voting and delegation. This version is more
* generic then Compound's. It support token supply up to 2**224, while comp is limited to 2**96. If exact Compound
* compatibility is requiered, please use the {ERC20VotesComp} variant of this module.
*
* This extensions 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
Expand All @@ -22,38 +23,46 @@ import "../../../utils/cryptography/ECDSA.sol";
*
* _Available since v4.2._
*/
abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
abstract contract ERC20Votes is 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;

event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);

/**
* @dev Get the `pos`-th checkpoint for `account`.
*/
function checkpoints(address account, uint32 pos) external view virtual override returns (Checkpoint memory) {
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) external view virtual override returns (uint32) {
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) {
function delegates(address account) public view virtual returns (address) {
return _delegates[account];
}

/**
* @dev Gets the current votes balance for `account`
*/
function getCurrentVotes(address account) external view override returns (uint256) {
function getVotes(address account) public view returns (uint256) {
uint256 pos = _checkpoints[account].length;
return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
}
Expand All @@ -65,7 +74,7 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
*
* - `blockNumber` must have been already mined
*/
function getPriorVotes(address account, uint256 blockNumber) external view override returns (uint256) {
function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
return _checkpointsLookup(_checkpoints[account], blockNumber);
}
Expand All @@ -78,7 +87,7 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
*
* - `blockNumber` must have been already mined
*/
function getPriorTotalSupply(uint256 blockNumber) external view override returns (uint256) {
function getPastTotalSupply(uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
}
Expand Down Expand Up @@ -115,15 +124,15 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
/**
* @dev Delegate votes from the sender to `delegatee`.
*/
function delegate(address delegatee) public virtual override {
function delegate(address delegatee) public virtual {
return _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
public virtual
{
require(block.timestamp <= expiry, "ERC20Votes: signature expired");
address signer = ECDSA.recover(
Expand All @@ -139,12 +148,19 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
return _delegate(signer, delegatee);
}

/**
* @dev Maximum supported values.
*/
function _maxVotes() internal view virtual returns (uint256) {
return type(uint224).max;
}

/**
* @dev snapshot the totalSupply after it has been increassed.
*/
function _mint(address account, uint256 amount) internal virtual override {
super._mint(account, amount);
require(totalSupply() <= type(uint224).max, "ERC20Votes: total supply exceeds 2**224");
require(totalSupply() <= _maxVotes(), "ERC20Votes: total supply risk overflowing votes");

_writeCheckpoint(_totalSupplyCheckpoints, add, amount);
}
Expand Down
44 changes: 44 additions & 0 deletions contracts/token/ERC20/extensions/ERC20VotesComp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./ERC20Votes.sol";

/**
* @dev Extension of the ERC20 token contract to support Compound's voting and delegation. This version exactly matches
* Compound's interface, which the drawback of only supporting 2**96 token. You should use this domain if you need
* exact compatibility (for example in other to use your token with Governor Alpha or Bravo) or if you are sure the
* supply cap of 2**96 is enough for you. Otherwize, use the {ERC20Votes} variant of this module.
*
* This extensions 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 {getCurrentVotes} and {getPriorVotes}.
*
* 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.
* Enabling self-delegation can easily be done by overriding the {delegates} function. Keep in mind however that this
* will significantly increase the base gas cost of transfers.
*
* _Available since v4.2._
*/
abstract contract ERC20VotesComp is ERC20Votes {
/**
* @dev Comp version of the {getVotes} accessor, with `uint96` return type.
*/
function getCurrentVotes(address account) external view returns (uint96) {
return SafeCast.toUint96(getVotes(account));
}
/**
* @dev Comp version of the {getPastVotes} accessor, with `uint96` return type.
*/
function getPriorVotes(address account, uint256 blockNumber) external view returns (uint96) {
return SafeCast.toUint96(getPastVotes(account, blockNumber));
}

/**
* @dev Maximum supported values.
*/
function _maxVotes() internal view virtual override returns (uint256) {
return type(uint96).max;
}
}
24 changes: 0 additions & 24 deletions contracts/token/ERC20/extensions/IERC20Votes.sol

This file was deleted.

Loading