Commit aeb86bf4 by github-actions

Transpile c4250c41

parent 2ef7dafa
...@@ -7,27 +7,23 @@ import "../token/ERC20/extensions/ERC20VotesUpgradeable.sol"; ...@@ -7,27 +7,23 @@ import "../token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "../proxy/utils/Initializable.sol"; import "../proxy/utils/Initializable.sol";
contract ERC20VotesMockUpgradeable is Initializable, ERC20VotesUpgradeable { contract ERC20VotesMockUpgradeable is Initializable, ERC20VotesUpgradeable {
function __ERC20VotesMock_init( function __ERC20VotesMock_init(string memory name, string memory symbol) internal initializer {
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC20_init_unchained(name, symbol); __ERC20_init_unchained(name, symbol);
__EIP712_init_unchained(name, "1"); __EIP712_init_unchained(name, "1");
__ERC20Permit_init_unchained(name); __ERC20Permit_init_unchained(name);
__ERC20Votes_init_unchained(); __ERC20Votes_init_unchained();
__ERC20VotesMock_init_unchained(name, symbol, initialAccount, initialBalance); __ERC20VotesMock_init_unchained(name, symbol);
} }
function __ERC20VotesMock_init_unchained( function __ERC20VotesMock_init_unchained(string memory name, string memory symbol) internal initializer {}
string memory name,
string memory symbol, function mint(address account, uint256 amount) public {
address initialAccount, _mint(account, amount);
uint256 initialBalance }
) internal initializer {
_mint(initialAccount, initialBalance); function burn(address account, uint256 amount) public {
_burn(account, amount);
} }
function getChainId() external view returns (uint256) { function getChainId() external view returns (uint256) {
......
...@@ -594,13 +594,8 @@ contract SafeCastMockUpgradeableWithInit is SafeCastMockUpgradeable { ...@@ -594,13 +594,8 @@ contract SafeCastMockUpgradeableWithInit is SafeCastMockUpgradeable {
import "./ERC20VotesMockUpgradeable.sol"; import "./ERC20VotesMockUpgradeable.sol";
contract ERC20VotesMockUpgradeableWithInit is ERC20VotesMockUpgradeable { contract ERC20VotesMockUpgradeableWithInit is ERC20VotesMockUpgradeable {
constructor( constructor(string memory name, string memory symbol) public payable {
string memory name, __ERC20VotesMock_init(name, symbol);
string memory symbol,
address initialAccount,
uint256 initialBalance
) public payable {
__ERC20VotesMock_init(name, symbol, initialAccount, initialBalance);
} }
} }
import "./ArraysImplUpgradeable.sol"; import "./ArraysImplUpgradeable.sol";
......
...@@ -30,6 +30,7 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable ...@@ -30,6 +30,7 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable
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`.
...@@ -61,13 +62,34 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable ...@@ -61,13 +62,34 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable
} }
/** /**
* @dev Determine the number of votes for `account` at the begining of `blockNumber`. * @dev Retrieve the number of votes for `account` at the end of `blockNumber`.
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/ */
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: block not yet mined");
return _checkpointsLookup(_checkpoints[account], blockNumber);
}
Checkpoint[] storage ckpts = _checkpoints[account]; /**
* @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances.
* It is but NOT the sum of all the delegated votes!
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/
function getPriorTotalSupply(uint256 blockNumber) external view 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`. // 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).
...@@ -106,7 +128,7 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable ...@@ -106,7 +128,7 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable
function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)
public virtual override public virtual override
{ {
require(block.timestamp <= expiry, "ERC20Votes::delegateBySig: signature expired"); require(block.timestamp <= expiry, "ERC20Votes: signature expired");
address signer = ECDSAUpgradeable.recover( address signer = ECDSAUpgradeable.recover(
_hashTypedDataV4(keccak256(abi.encode( _hashTypedDataV4(keccak256(abi.encode(
_DELEGATION_TYPEHASH, _DELEGATION_TYPEHASH,
...@@ -116,11 +138,37 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable ...@@ -116,11 +138,37 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable
))), ))),
v, r, s v, r, s
); );
require(nonce == _useNonce(signer), "ERC20Votes::delegateBySig: invalid nonce"); require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
return _delegate(signer, delegatee); return _delegate(signer, delegatee);
} }
/** /**
* @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 {
...@@ -136,41 +184,44 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable ...@@ -136,41 +184,44 @@ abstract contract ERC20VotesUpgradeable is Initializable, IERC20VotesUpgradeable
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 = SafeCastUpgradeable.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 = SafeCastUpgradeable.toUint224(newWeight);
} else { } else {
_checkpoints[delegatee].push(Checkpoint({ ckpts.push(Checkpoint({
fromBlock: SafeCastUpgradeable.toUint32(block.number), fromBlock: SafeCastUpgradeable.toUint32(block.number),
votes: SafeCastUpgradeable.toUint224(newWeight) votes: SafeCastUpgradeable.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;
} }
uint256[48] private __gap; uint256[47] private __gap;
} }
...@@ -18,6 +18,7 @@ interface IERC20VotesUpgradeable is IERC20Upgradeable { ...@@ -18,6 +18,7 @@ interface IERC20VotesUpgradeable is IERC20Upgradeable {
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;
} }
...@@ -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 () {
...@@ -192,7 +192,7 @@ contract('ERC20Votes', function (accounts) { ...@@ -192,7 +192,7 @@ contract('ERC20Votes', function (accounts) {
await expectRevert( await expectRevert(
this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s),
'ERC20Votes::delegateBySig: invalid nonce', 'ERC20Votes: invalid nonce',
); );
}); });
...@@ -224,7 +224,7 @@ contract('ERC20Votes', function (accounts) { ...@@ -224,7 +224,7 @@ contract('ERC20Votes', function (accounts) {
)); ));
await expectRevert( await expectRevert(
this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s),
'ERC20Votes::delegateBySig: invalid nonce', 'ERC20Votes: invalid nonce',
); );
}); });
...@@ -241,7 +241,7 @@ contract('ERC20Votes', function (accounts) { ...@@ -241,7 +241,7 @@ contract('ERC20Votes', function (accounts) {
await expectRevert( await expectRevert(
this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s),
'ERC20Votes::delegateBySig: signature expired', 'ERC20Votes: signature expired',
); );
}); });
}); });
...@@ -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');
...@@ -402,7 +411,7 @@ contract('ERC20Votes', function (accounts) { ...@@ -402,7 +411,7 @@ contract('ERC20Votes', function (accounts) {
it('reverts if block number >= current block', async function () { it('reverts if block number >= current block', async function () {
await expectRevert( await expectRevert(
this.token.getPriorVotes(other1, 5e10), this.token.getPriorVotes(other1, 5e10),
'ERC20Votes::getPriorVotes: not yet determined', 'ERC20Votes: block not yet mined',
); );
}); });
...@@ -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(5e10),
'ERC20Votes: block not yet mined',
);
});
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');
});
});
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment