forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ERC721Votes for NFT-based governance (OpenZeppelin#2944)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com> Co-authored-by: Hadrien Croubois <hadrien@openzeppelin.com>
- Loading branch information
1 parent
9a7e4a0
commit b42b053
Showing
31 changed files
with
1,346 additions
and
36 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// SPDX-License-Identifier: MIT | ||
// OpenZeppelin Contracts v4.4.0 (interfaces/IVotes.sol) | ||
pragma solidity ^0.8.0; | ||
|
||
/** | ||
* @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. | ||
* | ||
* _Available since v4.5._ | ||
*/ | ||
interface IVotes { | ||
/** | ||
* @dev Emitted when an account changes their delegate. | ||
*/ | ||
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); | ||
|
||
/** | ||
* @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. | ||
*/ | ||
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); | ||
|
||
/** | ||
* @dev Returns the current amount of votes that `account` has. | ||
*/ | ||
function getVotes(address account) external view returns (uint256); | ||
|
||
/** | ||
* @dev Returns the amount of votes that `account` had at the end of a past block (`blockNumber`). | ||
*/ | ||
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256); | ||
|
||
/** | ||
* @dev Returns the total supply of votes available at the end of a past block (`blockNumber`). | ||
* | ||
* 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. | ||
*/ | ||
function getPastTotalSupply(uint256 blockNumber) external view returns (uint256); | ||
|
||
/** | ||
* @dev Returns the delegate that `account` has chosen. | ||
*/ | ||
function delegates(address account) external view returns (address); | ||
|
||
/** | ||
* @dev Delegates votes from the sender to `delegatee`. | ||
*/ | ||
function delegate(address delegatee) external; | ||
|
||
/** | ||
* @dev Delegates votes from signer to `delegatee`. | ||
*/ | ||
function delegateBySig( | ||
address delegatee, | ||
uint256 nonce, | ||
uint256 expiry, | ||
uint8 v, | ||
bytes32 r, | ||
bytes32 s | ||
) external; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "../../utils/Context.sol"; | ||
import "../../utils/Counters.sol"; | ||
import "../../utils/Checkpoints.sol"; | ||
import "../../utils/cryptography/draft-EIP712.sol"; | ||
import "./IVotes.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 IVotes, Context, EIP712 { | ||
using Checkpoints for Checkpoints.History; | ||
using Counters for Counters.Counter; | ||
|
||
bytes32 private constant _DELEGATION_TYPEHASH = | ||
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); | ||
|
||
mapping(address => address) private _delegation; | ||
mapping(address => Checkpoints.History) private _delegateCheckpoints; | ||
Checkpoints.History private _totalCheckpoints; | ||
|
||
mapping(address => Counters.Counter) private _nonces; | ||
|
||
/** | ||
* @dev Returns the current amount of votes that `account` has. | ||
*/ | ||
function getVotes(address account) public view virtual override returns (uint256) { | ||
return _delegateCheckpoints[account].latest(); | ||
} | ||
|
||
/** | ||
* @dev Returns the amount of votes that `account` had at the end of a past block (`blockNumber`). | ||
* | ||
* Requirements: | ||
* | ||
* - `blockNumber` must have been already mined | ||
*/ | ||
function getPastVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) { | ||
return _delegateCheckpoints[account].getAtBlock(blockNumber); | ||
} | ||
|
||
/** | ||
* @dev Returns the total supply of votes available at the end of a past block (`blockNumber`). | ||
* | ||
* 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: | ||
* | ||
* - `blockNumber` must have been already mined | ||
*/ | ||
function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) { | ||
require(blockNumber < block.number, "Votes: block not yet mined"); | ||
return _totalCheckpoints.getAtBlock(blockNumber); | ||
} | ||
|
||
/** | ||
* @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 override returns (address) { | ||
return _delegation[account]; | ||
} | ||
|
||
/** | ||
* @dev Delegates votes from the sender to `delegatee`. | ||
*/ | ||
function delegate(address delegatee) public virtual override { | ||
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 override { | ||
require(block.timestamp <= expiry, "Votes: signature expired"); | ||
address signer = ECDSA.recover( | ||
_hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), | ||
v, | ||
r, | ||
s | ||
); | ||
require(nonce == _useNonce(signer), "Votes: invalid nonce"); | ||
_delegate(signer, delegatee); | ||
} | ||
|
||
/** | ||
* @dev Delegate all of `account`'s voting units to `delegatee`. | ||
* | ||
* Emits events {DelegateChanged} and {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)) { | ||
_totalCheckpoints.push(_add, amount); | ||
} | ||
if (to == address(0)) { | ||
_totalCheckpoints.push(_subtract, 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) = _delegateCheckpoints[from].push(_subtract, amount); | ||
emit DelegateVotesChanged(from, oldValue, newValue); | ||
} | ||
if (to != address(0)) { | ||
(uint256 oldValue, uint256 newValue) = _delegateCheckpoints[to].push(_add, amount); | ||
emit DelegateVotesChanged(to, oldValue, newValue); | ||
} | ||
} | ||
} | ||
|
||
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 Consumes a nonce. | ||
* | ||
* Returns the current value and increments nonce. | ||
*/ | ||
function _useNonce(address owner) internal virtual returns (uint256 current) { | ||
Counters.Counter storage nonce = _nonces[owner]; | ||
current = nonce.current(); | ||
nonce.increment(); | ||
} | ||
|
||
/** | ||
* @dev Returns an address nonce. | ||
*/ | ||
function nonces(address owner) public view virtual returns (uint256) { | ||
return _nonces[owner].current(); | ||
} | ||
|
||
/** | ||
* @dev Returns the contract's {EIP712} domain separator. | ||
*/ | ||
// solhint-disable-next-line func-name-mixedcase | ||
function DOMAIN_SEPARATOR() external view returns (bytes32) { | ||
return _domainSeparatorV4(); | ||
} | ||
|
||
/** | ||
* @dev Must return the voting units held by an account. | ||
*/ | ||
function _getVotingUnits(address) internal virtual returns (uint256); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../utils/Checkpoints.sol"; | ||
|
||
contract CheckpointsImpl { | ||
using Checkpoints for Checkpoints.History; | ||
|
||
Checkpoints.History private _totalCheckpoints; | ||
|
||
function latest() public view returns (uint256) { | ||
return _totalCheckpoints.latest(); | ||
} | ||
|
||
function getAtBlock(uint256 blockNumber) public view returns (uint256) { | ||
return _totalCheckpoints.getAtBlock(blockNumber); | ||
} | ||
|
||
function push(uint256 value) public returns (uint256, uint256) { | ||
return _totalCheckpoints.push(value); | ||
} | ||
} |
This file contains 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
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/ERC721/extensions/draft-ERC721Votes.sol"; | ||
|
||
contract ERC721VotesMock is ERC721Votes { | ||
constructor(string memory name, string memory symbol) ERC721(name, symbol) EIP712(name, "1") {} | ||
|
||
function getTotalSupply() public view returns (uint256) { | ||
return _getTotalSupply(); | ||
} | ||
|
||
function mint(address account, uint256 tokenId) public { | ||
_mint(account, tokenId); | ||
} | ||
|
||
function burn(uint256 tokenId) public { | ||
_burn(tokenId); | ||
} | ||
|
||
function getChainId() external view returns (uint256) { | ||
return block.chainid; | ||
} | ||
} |
This file contains 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
This file contains 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
Oops, something went wrong.