Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
O
openzeppelin-contracts-upgradeable
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
俞永鹏
openzeppelin-contracts-upgradeable
Commits
f6efd8ac
Unverified
Commit
f6efd8ac
authored
May 27, 2021
by
Hadrien Croubois
Committed by
GitHub
May 27, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add totalSupply checkpoints to ER20Votes (#2695)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
parent
ad3c18eb
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
155 additions
and
36 deletions
+155
-36
ERC20VotesMock.sol
contracts/mocks/ERC20VotesMock.sol
+11
-7
ERC20Votes.sol
contracts/token/ERC20/extensions/ERC20Votes.sol
+63
-20
IERC20Votes.sol
contracts/token/ERC20/extensions/IERC20Votes.sol
+1
-0
ERC20FlashMint.test.js
test/token/ERC20/extensions/ERC20FlashMint.test.js
+0
-0
ERC20Votes.test.js
test/token/ERC20/extensions/ERC20Votes.test.js
+80
-9
No files found.
contracts/mocks/ERC20VotesMock.sol
View file @
f6efd8ac
...
@@ -6,13 +6,17 @@ pragma solidity ^0.8.0;
...
@@ -6,13 +6,17 @@ pragma solidity ^0.8.0;
import "../token/ERC20/extensions/ERC20Votes.sol";
import "../token/ERC20/extensions/ERC20Votes.sol";
contract ERC20VotesMock is ERC20Votes {
contract ERC20VotesMock is ERC20Votes {
constructor (
constructor (string memory name, string memory symbol)
string memory name,
ERC20(name, symbol)
string memory symbol,
ERC20Permit(name)
address initialAccount,
{}
uint256 initialBalance
) payable ERC20(name, symbol) ERC20Permit(name) {
function mint(address account, uint256 amount) public {
_mint(initialAccount, initialBalance);
_mint(account, amount);
}
function burn(address account, uint256 amount) public {
_burn(account, amount);
}
}
function getChainId() external view returns (uint256) {
function getChainId() external view returns (uint256) {
...
...
contracts/token/ERC20/extensions/ERC20Votes.sol
View file @
f6efd8ac
...
@@ -27,6 +27,7 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
...
@@ -27,6 +27,7 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
mapping (address => address) private _delegates;
mapping (address => address) private _delegates;
mapping (address => Checkpoint[]) private _checkpoints;
mapping (address => Checkpoint[]) private _checkpoints;
Checkpoint[] private _totalSupplyCheckpoints;
/**
/**
* @dev Get the `pos`-th checkpoint for `account`.
* @dev Get the `pos`-th checkpoint for `account`.
...
@@ -62,9 +63,22 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
...
@@ -62,9 +63,22 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
*/
*/
function getPriorVotes(address account, uint256 blockNumber) external view override returns (uint256) {
function getPriorVotes(address account, uint256 blockNumber) external view override returns (uint256) {
require(blockNumber < block.number, "ERC20Votes::getPriorVotes: not yet determined");
require(blockNumber < block.number, "ERC20Votes::getPriorVotes: not yet determined");
return _checkpointsLookup(_checkpoints[account], blockNumber);
}
Checkpoint[] storage ckpts = _checkpoints[account];
/**
* @dev Determine the totalSupply at the begining of `blockNumber`. Note, this value is the sum of all balances.
* It is but NOT the sum of all the delegated votes!
*/
function getPriorTotalSupply(uint256 blockNumber) external view override returns(uint256) {
require(blockNumber < block.number, "ERC20Votes::getPriorTotalSupply: not yet determined");
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`.
// We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
//
//
// During the loop, the index of the wanted checkpoint remains in the range [low, high).
// During the loop, the index of the wanted checkpoint remains in the range [low, high).
...
@@ -118,6 +132,32 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
...
@@ -118,6 +132,32 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
}
}
/**
/**
* @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");
_writeCheckpoint(_totalSupplyCheckpoints, add, amount);
}
/**
* @dev snapshot 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.
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
_moveVotingPower(delegates(from), delegates(to), amount);
}
/**
* @dev Change delegation for `delegator` to `delegatee`.
* @dev Change delegation for `delegator` to `delegatee`.
*/
*/
function _delegate(address delegator, address delegatee) internal virtual {
function _delegate(address delegator, address delegatee) internal virtual {
...
@@ -133,40 +173,43 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
...
@@ -133,40 +173,43 @@ abstract contract ERC20Votes is IERC20Votes, ERC20Permit {
function _moveVotingPower(address src, address dst, uint256 amount) private {
function _moveVotingPower(address src, address dst, uint256 amount) private {
if (src != dst && amount > 0) {
if (src != dst && amount > 0) {
if (src != address(0)) {
if (src != address(0)) {
uint256 srcCkptLen = _checkpoints[src].length;
(uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], subtract, amount);
uint256 srcCkptOld = srcCkptLen == 0 ? 0 : _checkpoints[src][srcCkptLen - 1].votes;
emit DelegateVotesChanged(src, oldWeight, newWeight);
uint256 srcCkptNew = srcCkptOld - amount;
_writeCheckpoint(src, srcCkptLen, srcCkptOld, srcCkptNew);
}
}
if (dst != address(0)) {
if (dst != address(0)) {
uint256 dstCkptLen = _checkpoints[dst].length;
(uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], add, amount);
uint256 dstCkptOld = dstCkptLen == 0 ? 0 : _checkpoints[dst][dstCkptLen - 1].votes;
emit DelegateVotesChanged(dst, oldWeight, newWeight);
uint256 dstCkptNew = dstCkptOld + amount;
_writeCheckpoint(dst, dstCkptLen, dstCkptOld, dstCkptNew);
}
}
}
}
}
}
function _writeCheckpoint(address delegatee, uint256 pos, uint256 oldWeight, uint256 newWeight) private {
function _writeCheckpoint(
if (pos > 0 && _checkpoints[delegatee][pos - 1].fromBlock == block.number) {
Checkpoint[] storage ckpts,
_checkpoints[delegatee][pos - 1].votes = SafeCast.toUint224(newWeight);
function (uint256, uint256) view returns (uint256) op,
uint256 delta
)
private returns (uint256 oldWeight, uint256 newWeight)
{
uint256 pos = ckpts.length;
oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
newWeight = op(oldWeight, delta);
if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
ckpts[pos - 1].votes = SafeCast.toUint224(newWeight);
} else {
} else {
_checkpoints[delegatee]
.push(Checkpoint({
ckpts
.push(Checkpoint({
fromBlock: SafeCast.toUint32(block.number),
fromBlock: SafeCast.toUint32(block.number),
votes: SafeCast.toUint224(newWeight)
votes: SafeCast.toUint224(newWeight)
}));
}));
}
}
emit DelegateVotesChanged(delegatee, oldWeight, newWeight);
}
}
function _mint(address account, uint256 amount) internal virtual override {
function add(uint256 a, uint256 b) private pure returns (uint256) {
super._mint(account, amount);
return a + b;
require(totalSupply() <= type(uint224).max, "ERC20Votes: total supply exceeds 2**224");
}
}
function
_beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override
{
function
subtract(uint256 a, uint256 b) private pure returns (uint256)
{
_moveVotingPower(delegates(from), delegates(to), amount)
;
return a - b
;
}
}
}
}
contracts/token/ERC20/extensions/IERC20Votes.sol
View file @
f6efd8ac
...
@@ -18,6 +18,7 @@ interface IERC20Votes is IERC20 {
...
@@ -18,6 +18,7 @@ interface IERC20Votes is IERC20 {
function numCheckpoints(address account) external view returns (uint32);
function numCheckpoints(address account) external view returns (uint32);
function getCurrentVotes(address account) external view returns (uint256);
function getCurrentVotes(address account) external view returns (uint256);
function getPriorVotes(address account, uint256 blockNumber) external view returns (uint256);
function getPriorVotes(address account, uint256 blockNumber) external view returns (uint256);
function getPriorTotalSupply(uint256 blockNumber) external view returns(uint256);
function delegate(address delegatee) external;
function delegate(address delegatee) external;
function delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) external;
function delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) external;
}
}
test/token/ERC20/extensions/
draft-
ERC20FlashMint.test.js
→
test/token/ERC20/extensions/ERC20FlashMint.test.js
View file @
f6efd8ac
File moved
test/token/ERC20/extensions/
draft-
ERC20Votes.test.js
→
test/token/ERC20/extensions/ERC20Votes.test.js
View file @
f6efd8ac
...
@@ -58,11 +58,10 @@ contract('ERC20Votes', function (accounts) {
...
@@ -58,11 +58,10 @@ contract('ERC20Votes', function (accounts) {
const
name
=
'My Token'
;
const
name
=
'My Token'
;
const
symbol
=
'MTKN'
;
const
symbol
=
'MTKN'
;
const
version
=
'1'
;
const
version
=
'1'
;
const
supply
=
new
BN
(
'10000000000000000000000000'
);
const
supply
=
new
BN
(
'10000000000000000000000000'
);
beforeEach
(
async
function
()
{
beforeEach
(
async
function
()
{
this
.
token
=
await
ERC20VotesMock
.
new
(
name
,
symbol
,
holder
,
supply
);
this
.
token
=
await
ERC20VotesMock
.
new
(
name
,
symbol
);
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
// from within the EVM as from the JSON RPC interface.
// from within the EVM as from the JSON RPC interface.
...
@@ -85,7 +84,7 @@ contract('ERC20Votes', function (accounts) {
...
@@ -85,7 +84,7 @@ contract('ERC20Votes', function (accounts) {
it
(
'minting restriction'
,
async
function
()
{
it
(
'minting restriction'
,
async
function
()
{
const
amount
=
new
BN
(
'2'
).
pow
(
new
BN
(
'224'
));
const
amount
=
new
BN
(
'2'
).
pow
(
new
BN
(
'224'
));
await
expectRevert
(
await
expectRevert
(
ERC20VotesMock
.
new
(
name
,
symbol
,
holder
,
amount
),
this
.
token
.
mint
(
holder
,
amount
),
'ERC20Votes: total supply exceeds 2**224'
,
'ERC20Votes: total supply exceeds 2**224'
,
);
);
});
});
...
@@ -93,6 +92,7 @@ contract('ERC20Votes', function (accounts) {
...
@@ -93,6 +92,7 @@ contract('ERC20Votes', function (accounts) {
describe
(
'set delegation'
,
function
()
{
describe
(
'set delegation'
,
function
()
{
describe
(
'call'
,
function
()
{
describe
(
'call'
,
function
()
{
it
(
'delegation with balance'
,
async
function
()
{
it
(
'delegation with balance'
,
async
function
()
{
await
this
.
token
.
mint
(
holder
,
supply
);
expect
(
await
this
.
token
.
delegates
(
holder
)).
to
.
be
.
equal
(
ZERO_ADDRESS
);
expect
(
await
this
.
token
.
delegates
(
holder
)).
to
.
be
.
equal
(
ZERO_ADDRESS
);
const
{
receipt
}
=
await
this
.
token
.
delegate
(
holder
,
{
from
:
holder
});
const
{
receipt
}
=
await
this
.
token
.
delegate
(
holder
,
{
from
:
holder
});
...
@@ -116,17 +116,17 @@ contract('ERC20Votes', function (accounts) {
...
@@ -116,17 +116,17 @@ contract('ERC20Votes', function (accounts) {
});
});
it
(
'delegation without balance'
,
async
function
()
{
it
(
'delegation without balance'
,
async
function
()
{
expect
(
await
this
.
token
.
delegates
(
recipient
)).
to
.
be
.
equal
(
ZERO_ADDRESS
);
expect
(
await
this
.
token
.
delegates
(
holder
)).
to
.
be
.
equal
(
ZERO_ADDRESS
);
const
{
receipt
}
=
await
this
.
token
.
delegate
(
recipient
,
{
from
:
recipient
});
const
{
receipt
}
=
await
this
.
token
.
delegate
(
holder
,
{
from
:
holder
});
expectEvent
(
receipt
,
'DelegateChanged'
,
{
expectEvent
(
receipt
,
'DelegateChanged'
,
{
delegator
:
recipient
,
delegator
:
holder
,
fromDelegate
:
ZERO_ADDRESS
,
fromDelegate
:
ZERO_ADDRESS
,
toDelegate
:
recipient
,
toDelegate
:
holder
,
});
});
expectEvent
.
notEmitted
(
receipt
,
'DelegateVotesChanged'
);
expectEvent
.
notEmitted
(
receipt
,
'DelegateVotesChanged'
);
expect
(
await
this
.
token
.
delegates
(
recipient
)).
to
.
be
.
equal
(
recipient
);
expect
(
await
this
.
token
.
delegates
(
holder
)).
to
.
be
.
equal
(
holder
);
});
});
});
});
...
@@ -143,7 +143,7 @@ contract('ERC20Votes', function (accounts) {
...
@@ -143,7 +143,7 @@ contract('ERC20Votes', function (accounts) {
}});
}});
beforeEach
(
async
function
()
{
beforeEach
(
async
function
()
{
await
this
.
token
.
transfer
(
delegatorAddress
,
supply
,
{
from
:
holder
}
);
await
this
.
token
.
mint
(
delegatorAddress
,
supply
);
});
});
it
(
'accept signed delegation'
,
async
function
()
{
it
(
'accept signed delegation'
,
async
function
()
{
...
@@ -249,6 +249,7 @@ contract('ERC20Votes', function (accounts) {
...
@@ -249,6 +249,7 @@ contract('ERC20Votes', function (accounts) {
describe
(
'change delegation'
,
function
()
{
describe
(
'change delegation'
,
function
()
{
beforeEach
(
async
function
()
{
beforeEach
(
async
function
()
{
await
this
.
token
.
mint
(
holder
,
supply
);
await
this
.
token
.
delegate
(
holder
,
{
from
:
holder
});
await
this
.
token
.
delegate
(
holder
,
{
from
:
holder
});
});
});
...
@@ -285,6 +286,10 @@ contract('ERC20Votes', function (accounts) {
...
@@ -285,6 +286,10 @@ contract('ERC20Votes', function (accounts) {
});
});
describe
(
'transfers'
,
function
()
{
describe
(
'transfers'
,
function
()
{
beforeEach
(
async
function
()
{
await
this
.
token
.
mint
(
holder
,
supply
);
});
it
(
'no delegation'
,
async
function
()
{
it
(
'no delegation'
,
async
function
()
{
const
{
receipt
}
=
await
this
.
token
.
transfer
(
recipient
,
1
,
{
from
:
holder
});
const
{
receipt
}
=
await
this
.
token
.
transfer
(
recipient
,
1
,
{
from
:
holder
});
expectEvent
(
receipt
,
'Transfer'
,
{
from
:
holder
,
to
:
recipient
,
value
:
'1'
});
expectEvent
(
receipt
,
'Transfer'
,
{
from
:
holder
,
to
:
recipient
,
value
:
'1'
});
...
@@ -343,6 +348,10 @@ contract('ERC20Votes', function (accounts) {
...
@@ -343,6 +348,10 @@ contract('ERC20Votes', function (accounts) {
// The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js.
// The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js.
describe
(
'Compound test suite'
,
function
()
{
describe
(
'Compound test suite'
,
function
()
{
beforeEach
(
async
function
()
{
await
this
.
token
.
mint
(
holder
,
supply
);
});
describe
(
'balanceOf'
,
function
()
{
describe
(
'balanceOf'
,
function
()
{
it
(
'grants to initial account'
,
async
function
()
{
it
(
'grants to initial account'
,
async
function
()
{
expect
(
await
this
.
token
.
balanceOf
(
holder
)).
to
.
be
.
bignumber
.
equal
(
'10000000000000000000000000'
);
expect
(
await
this
.
token
.
balanceOf
(
holder
)).
to
.
be
.
bignumber
.
equal
(
'10000000000000000000000000'
);
...
@@ -455,4 +464,66 @@ contract('ERC20Votes', function (accounts) {
...
@@ -455,4 +464,66 @@ contract('ERC20Votes', function (accounts) {
});
});
});
});
});
});
describe
(
'getPriorTotalSupply'
,
function
()
{
beforeEach
(
async
function
()
{
await
this
.
token
.
delegate
(
holder
,
{
from
:
holder
});
});
it
(
'reverts if block number >= current block'
,
async
function
()
{
await
expectRevert
(
this
.
token
.
getPriorTotalSupply
(
5
e10
),
'ERC20Votes::getPriorTotalSupply: not yet determined'
,
);
});
it
(
'returns 0 if there are no checkpoints'
,
async
function
()
{
expect
(
await
this
.
token
.
getPriorTotalSupply
(
0
)).
to
.
be
.
bignumber
.
equal
(
'0'
);
});
it
(
'returns the latest block if >= last checkpoint block'
,
async
function
()
{
t1
=
await
this
.
token
.
mint
(
holder
,
supply
);
await
time
.
advanceBlock
();
await
time
.
advanceBlock
();
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t1
.
receipt
.
blockNumber
)).
to
.
be
.
bignumber
.
equal
(
supply
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t1
.
receipt
.
blockNumber
+
1
)).
to
.
be
.
bignumber
.
equal
(
supply
);
});
it
(
'returns zero if < first checkpoint block'
,
async
function
()
{
await
time
.
advanceBlock
();
const
t1
=
await
this
.
token
.
mint
(
holder
,
supply
);
await
time
.
advanceBlock
();
await
time
.
advanceBlock
();
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t1
.
receipt
.
blockNumber
-
1
)).
to
.
be
.
bignumber
.
equal
(
'0'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t1
.
receipt
.
blockNumber
+
1
)).
to
.
be
.
bignumber
.
equal
(
'10000000000000000000000000'
);
});
it
(
'generally returns the voting balance at the appropriate checkpoint'
,
async
function
()
{
const
t1
=
await
this
.
token
.
mint
(
holder
,
supply
);
await
time
.
advanceBlock
();
await
time
.
advanceBlock
();
const
t2
=
await
this
.
token
.
burn
(
holder
,
10
);
await
time
.
advanceBlock
();
await
time
.
advanceBlock
();
const
t3
=
await
this
.
token
.
burn
(
holder
,
10
);
await
time
.
advanceBlock
();
await
time
.
advanceBlock
();
const
t4
=
await
this
.
token
.
mint
(
holder
,
20
);
await
time
.
advanceBlock
();
await
time
.
advanceBlock
();
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t1
.
receipt
.
blockNumber
-
1
)).
to
.
be
.
bignumber
.
equal
(
'0'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t1
.
receipt
.
blockNumber
)).
to
.
be
.
bignumber
.
equal
(
'10000000000000000000000000'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t1
.
receipt
.
blockNumber
+
1
)).
to
.
be
.
bignumber
.
equal
(
'10000000000000000000000000'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t2
.
receipt
.
blockNumber
)).
to
.
be
.
bignumber
.
equal
(
'9999999999999999999999990'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t2
.
receipt
.
blockNumber
+
1
)).
to
.
be
.
bignumber
.
equal
(
'9999999999999999999999990'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t3
.
receipt
.
blockNumber
)).
to
.
be
.
bignumber
.
equal
(
'9999999999999999999999980'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t3
.
receipt
.
blockNumber
+
1
)).
to
.
be
.
bignumber
.
equal
(
'9999999999999999999999980'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t4
.
receipt
.
blockNumber
)).
to
.
be
.
bignumber
.
equal
(
'10000000000000000000000000'
);
expect
(
await
this
.
token
.
getPriorTotalSupply
(
t4
.
receipt
.
blockNumber
+
1
)).
to
.
be
.
bignumber
.
equal
(
'10000000000000000000000000'
);
});
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment