diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e02ec31b17..c2a9fde89e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The following contracts were removed: - `ERC20Snapshot` +- `ERC20VotesComp` +- `GovernorVotesComp` - `PaymentSplitter` - `TokenTimelock` (removed in favor of `VestinWallet`) diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 171283662dc..00edfe23d1b 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -22,8 +22,6 @@ Votes modules determine the source of voting power, and sometimes quorum number. * {GovernorVotes}: Extracts voting weight from an {ERC20Votes}, or since v4.5 an {ERC721Votes} token. -* {GovernorVotesComp}: Extracts voting weight from a COMP-like or {ERC20VotesComp} token. - * {GovernorVotesQuorumFraction}: Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply. Counting modules determine valid voting options. @@ -66,8 +64,6 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{GovernorVotesQuorumFraction}} -{{GovernorVotesComp}} - === Extensions {{GovernorTimelockControl}} diff --git a/contracts/governance/extensions/GovernorVotesComp.sol b/contracts/governance/extensions/GovernorVotesComp.sol deleted file mode 100644 index e7d7c2c7c8f..00000000000 --- a/contracts/governance/extensions/GovernorVotesComp.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotesComp.sol) - -pragma solidity ^0.8.0; - -import "../Governor.sol"; -import "../../token/ERC20/extensions/ERC20VotesComp.sol"; - -/** - * @dev Extension of {Governor} for voting weight extraction from a Comp token. - * - * _Available since v4.3._ - */ -abstract contract GovernorVotesComp is Governor { - ERC20VotesComp public immutable token; - - constructor(ERC20VotesComp token_) { - token = token_; - } - - /** - * @dev Clock (as specified in EIP-6372) is set to match the token's clock. Fallback to block numbers if the token - * does not implement EIP-6372. - */ - function clock() public view virtual override returns (uint48) { - try token.clock() returns (uint48 timepoint) { - return timepoint; - } catch { - return SafeCast.toUint48(block.number); - } - } - - /** - * @dev Machine-readable description of the clock as specified in EIP-6372. - */ - // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public view virtual override returns (string memory) { - try token.CLOCK_MODE() returns (string memory clockmode) { - return clockmode; - } catch { - return "mode=blocknumber&from=default"; - } - } - - /** - * Read the voting weight from the token's built-in snapshot mechanism (see {Governor-_getVotes}). - */ - function _getVotes( - address account, - uint256 timepoint, - bytes memory /*params*/ - ) internal view virtual override returns (uint256) { - return token.getPriorVotes(account, timepoint); - } -} diff --git a/contracts/mocks/governance/GovernorCompMock.sol b/contracts/mocks/governance/GovernorCompMock.sol deleted file mode 100644 index cc368c42fb9..00000000000 --- a/contracts/mocks/governance/GovernorCompMock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotesComp.sol"; - -abstract contract GovernorCompMock is GovernorVotesComp, GovernorCountingSimple { - function quorum(uint256) public pure override returns (uint256) { - return 0; - } - - function votingDelay() public pure override returns (uint256) { - return 4; - } - - function votingPeriod() public pure override returns (uint256) { - return 16; - } -} diff --git a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol index 1b87d143390..4cdc0b8884f 100644 --- a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol +++ b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol @@ -5,13 +5,13 @@ pragma solidity ^0.8.0; import "../../governance/compatibility/GovernorCompatibilityBravo.sol"; import "../../governance/extensions/GovernorTimelockCompound.sol"; import "../../governance/extensions/GovernorSettings.sol"; -import "../../governance/extensions/GovernorVotesComp.sol"; +import "../../governance/extensions/GovernorVotes.sol"; abstract contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorSettings, GovernorTimelockCompound, - GovernorVotesComp + GovernorVotes { function quorum(uint256) public pure override returns (uint256) { return 0; diff --git a/contracts/mocks/token/VotesTimestamp.sol b/contracts/mocks/token/VotesTimestamp.sol index 179c500f4e7..630e778f1ab 100644 --- a/contracts/mocks/token/VotesTimestamp.sol +++ b/contracts/mocks/token/VotesTimestamp.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import "../../token/ERC20/extensions/ERC20Votes.sol"; -import "../../token/ERC20/extensions/ERC20VotesComp.sol"; import "../../token/ERC721/extensions/ERC721Votes.sol"; abstract contract ERC20VotesTimestampMock is ERC20Votes { @@ -17,17 +16,6 @@ abstract contract ERC20VotesTimestampMock is ERC20Votes { } } -abstract contract ERC20VotesCompTimestampMock is ERC20VotesComp { - function clock() public view virtual override returns (uint48) { - return SafeCast.toUint48(block.timestamp); - } - - // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public view virtual override returns (string memory) { - return "mode=timestamp"; - } -} - abstract contract ERC721VotesTimestampMock is ERC721Votes { function clock() public view virtual override returns (uint48) { return SafeCast.toUint48(block.timestamp); diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index 8e7216a8dc1..9482b581b4b 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -21,7 +21,6 @@ Additionally there are multiple custom extensions, including: * {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. -* {ERC20VotesComp}: support for voting and vote delegation (compatible with Compound's token, with uint96 restrictions). * {ERC20Wrapper}: wrapper to create an ERC20 backed by another ERC20, with deposit and withdraw methods. Useful in conjunction with {ERC20Votes}. * {ERC4626}: tokenized vault that manages shares (represented as ERC20) that are backed by assets (another ERC20). @@ -55,8 +54,6 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{ERC20Votes}} -{{ERC20VotesComp}} - {{ERC20Wrapper}} {{ERC20FlashMint}} diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 24fc7c2e55d..e7cda4f3501 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -11,7 +11,7 @@ import "../../../utils/math/SafeCast.sol"; * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, * and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1. * - * NOTE: If exact COMP compatibility is required, use the {ERC20VotesComp} variant of this module. + * NOTE: This contract does not provide interface compatibility with Compound's COMP token. * * This extension 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 diff --git a/contracts/token/ERC20/extensions/ERC20VotesComp.sol b/contracts/token/ERC20/extensions/ERC20VotesComp.sol deleted file mode 100644 index 0461310a444..00000000000 --- a/contracts/token/ERC20/extensions/ERC20VotesComp.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20VotesComp.sol) - -pragma solidity ^0.8.0; - -import "./ERC20Votes.sol"; - -/** - * @dev Extension of ERC20 to support Compound's voting and delegation. This version exactly matches Compound's - * interface, with the drawback of only supporting supply up to (2^96^ - 1). - * - * NOTE: You should use this contract if you need exact compatibility with COMP (for example in order to use your token - * with Governor Alpha or Bravo) and if you are sure the supply cap of 2^96^ is enough for you. Otherwise, use the - * {ERC20Votes} variant of this module. - * - * This extension 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. - * - * _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 virtual 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 virtual returns (uint96) { - return SafeCast.toUint96(getPastVotes(account, blockNumber)); - } - - /** - * @dev Maximum token supply. Reduced to `type(uint96).max` (2^96^ - 1) to fit COMP interface. - */ - function _maxSupply() internal view virtual override returns (uint224) { - return type(uint96).max; - } -} diff --git a/test/governance/compatibility/GovernorCompatibilityBravo.test.js b/test/governance/compatibility/GovernorCompatibilityBravo.test.js index d6d9f6a3c88..9c45277d18f 100644 --- a/test/governance/compatibility/GovernorCompatibilityBravo.test.js +++ b/test/governance/compatibility/GovernorCompatibilityBravo.test.js @@ -21,8 +21,8 @@ function makeContractAddress(creator, nonce) { } const TOKENS = [ - { Token: artifacts.require('$ERC20VotesComp'), mode: 'blocknumber' }, - { Token: artifacts.require('$ERC20VotesCompTimestampMock'), mode: 'timestamp' }, + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, ]; contract('GovernorCompatibilityBravo', function (accounts) { diff --git a/test/governance/extensions/GovernorComp.test.js b/test/governance/extensions/GovernorComp.test.js deleted file mode 100644 index 92a59d05f27..00000000000 --- a/test/governance/extensions/GovernorComp.test.js +++ /dev/null @@ -1,89 +0,0 @@ -const { expect } = require('chai'); - -const Enums = require('../../helpers/enums'); -const { GovernorHelper } = require('../../helpers/governance'); - -const Governor = artifacts.require('$GovernorCompMock'); -const CallReceiver = artifacts.require('CallReceiverMock'); - -const TOKENS = [ - { Token: artifacts.require('$ERC20VotesComp'), mode: 'blocknumber' }, - { Token: artifacts.require('$ERC20VotesCompTimestampMock'), mode: 'timestamp' }, -]; - -contract('GovernorComp', function (accounts) { - const [owner, voter1, voter2, voter3, voter4] = accounts; - - const name = 'OZ-Governor'; - const version = '1'; - const tokenName = 'MockToken'; - const tokenSymbol = 'MTKN'; - const tokenSupply = web3.utils.toWei('100'); - const votingDelay = web3.utils.toBN(4); - const votingPeriod = web3.utils.toBN(16); - const value = web3.utils.toWei('1'); - - for (const { mode, Token } of TOKENS) { - describe(`using ${Token._json.contractName}`, function () { - beforeEach(async function () { - this.owner = owner; - this.token = await Token.new(tokenName, tokenSymbol, tokenName, version); - this.mock = await Governor.new(name, this.token.address); - this.receiver = await CallReceiver.new(); - - this.helper = new GovernorHelper(this.mock, mode); - - await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value }); - - await this.token.$_mint(owner, tokenSupply); - await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner }); - - // default proposal - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.address, - value, - data: this.receiver.contract.methods.mockFunction().encodeABI(), - }, - ], - '', - ); - }); - - it('deployment check', async function () { - expect(await this.mock.name()).to.be.equal(name); - expect(await this.mock.token()).to.be.equal(this.token.address); - expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay); - expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod); - expect(await this.mock.quorum(0)).to.be.bignumber.equal('0'); - }); - - it('voting with comp token', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); - await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }); - await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }); - await this.helper.waitForDeadline(); - await this.helper.execute(); - - expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true); - expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true); - expect(await this.mock.hasVoted(this.proposal.id, voter3)).to.be.equal(true); - expect(await this.mock.hasVoted(this.proposal.id, voter4)).to.be.equal(true); - - await this.mock.proposalVotes(this.proposal.id).then(results => { - expect(results.forVotes).to.be.bignumber.equal(web3.utils.toWei('17')); - expect(results.againstVotes).to.be.bignumber.equal(web3.utils.toWei('5')); - expect(results.abstainVotes).to.be.bignumber.equal(web3.utils.toWei('2')); - }); - }); - }); - } -}); diff --git a/test/token/ERC20/extensions/ERC20VotesComp.test.js b/test/token/ERC20/extensions/ERC20VotesComp.test.js deleted file mode 100644 index 815f22de04a..00000000000 --- a/test/token/ERC20/extensions/ERC20VotesComp.test.js +++ /dev/null @@ -1,579 +0,0 @@ -/* eslint-disable */ - -const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS } = constants; - -const { batchInBlock } = require('../../../helpers/txpool'); -const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); -const { fromRpcSig } = require('ethereumjs-util'); -const ethSigUtil = require('eth-sig-util'); -const Wallet = require('ethereumjs-wallet').default; - -const { getDomain, domainType, domainSeparator } = require('../../../helpers/eip712'); -const { clock, clockFromReceipt } = require('../../../helpers/time'); - -const Delegation = [ - { name: 'delegatee', type: 'address' }, - { name: 'nonce', type: 'uint256' }, - { name: 'expiry', type: 'uint256' }, -]; - -const MODES = { - blocknumber: artifacts.require('$ERC20VotesComp'), - // no timestamp mode for ERC20VotesComp yet -}; - -contract('ERC20VotesComp', function (accounts) { - const [holder, recipient, holderDelegatee, other1, other2] = accounts; - - const name = 'My Token'; - const symbol = 'MTKN'; - const version = '1'; - const supply = new BN('10000000000000000000000000'); - - for (const [mode, artifact] of Object.entries(MODES)) { - describe(`vote with ${mode}`, function () { - beforeEach(async function () { - this.token = await artifact.new(name, symbol, name, version); - this.votes = this.token; - }); - - // includes EIP6372 behavior check - shouldBehaveLikeVotes(accounts, [1, 17, 42], { mode, fungible: true }); - - it('initial nonce is 0', async function () { - expect(await this.token.nonces(holder)).to.be.bignumber.equal('0'); - }); - - it('domain separator', async function () { - expect(await this.token.DOMAIN_SEPARATOR()).to.equal(await getDomain(this.token).then(domainSeparator)); - }); - - it('minting restriction', async function () { - const amount = new BN('2').pow(new BN('96')); - await expectRevert(this.token.$_mint(holder, amount), 'ERC20Votes: total supply risks overflowing votes'); - }); - - it('recent checkpoints', async function () { - await this.token.delegate(holder, { from: holder }); - for (let i = 0; i < 6; i++) { - await this.token.$_mint(holder, 1); - } - const timepoint = await clock[mode](); - expect(await this.token.numCheckpoints(holder)).to.be.bignumber.equal('6'); - // recent - expect(await this.token.getPastVotes(holder, timepoint - 1)).to.be.bignumber.equal('5'); - // non-recent - expect(await this.token.getPastVotes(holder, timepoint - 6)).to.be.bignumber.equal('0'); - }); - - describe('set delegation', function () { - describe('call', function () { - it('delegation with balance', async function () { - await this.token.$_mint(holder, supply); - expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS); - - const { receipt } = await this.token.delegate(holder, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - - expectEvent(receipt, 'DelegateChanged', { - delegator: holder, - fromDelegate: ZERO_ADDRESS, - toDelegate: holder, - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: '0', - newBalance: supply, - }); - - expect(await this.token.delegates(holder)).to.be.equal(holder); - - expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(holder, timepoint - 1)).to.be.bignumber.equal('0'); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(holder, timepoint)).to.be.bignumber.equal(supply); - }); - - it('delegation without balance', async function () { - expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS); - - const { receipt } = await this.token.delegate(holder, { from: holder }); - expectEvent(receipt, 'DelegateChanged', { - delegator: holder, - fromDelegate: ZERO_ADDRESS, - toDelegate: holder, - }); - expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); - - expect(await this.token.delegates(holder)).to.be.equal(holder); - }); - }); - - describe('with signature', function () { - const delegator = Wallet.generate(); - const delegatorAddress = web3.utils.toChecksumAddress(delegator.getAddressString()); - const nonce = 0; - - const buildData = (contract, message) => - getDomain(contract).then(domain => ({ - primaryType: 'Delegation', - types: { EIP712Domain: domainType(domain), Delegation }, - domain, - message, - })); - - beforeEach(async function () { - await this.token.$_mint(delegatorAddress, supply); - }); - - it('accept signed delegation', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - expect(await this.token.delegates(delegatorAddress)).to.be.equal(ZERO_ADDRESS); - - const { receipt } = await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s); - const timepoint = await clockFromReceipt[mode](receipt); - - expectEvent(receipt, 'DelegateChanged', { - delegator: delegatorAddress, - fromDelegate: ZERO_ADDRESS, - toDelegate: delegatorAddress, - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: delegatorAddress, - previousBalance: '0', - newBalance: supply, - }); - - expect(await this.token.delegates(delegatorAddress)).to.be.equal(delegatorAddress); - - expect(await this.token.getCurrentVotes(delegatorAddress)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(delegatorAddress, timepoint - 1)).to.be.bignumber.equal('0'); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(delegatorAddress, timepoint)).to.be.bignumber.equal(supply); - }); - - it('rejects reused signature', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s); - - await expectRevert( - this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', - ); - }); - - it('rejects bad delegatee', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - const receipt = await this.token.delegateBySig(holderDelegatee, nonce, MAX_UINT256, v, r, s); - const { args } = receipt.logs.find(({ event }) => event == 'DelegateChanged'); - expect(args.delegator).to.not.be.equal(delegatorAddress); - expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS); - expect(args.toDelegate).to.be.equal(holderDelegatee); - }); - - it('rejects bad nonce', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - await expectRevert( - this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', - ); - }); - - it('rejects expired permit', async function () { - const expiry = (await time.latest()) - time.duration.weeks(1); - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - await expectRevert( - this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), - 'Votes: signature expired', - ); - }); - }); - }); - - describe('change delegation', function () { - beforeEach(async function () { - await this.token.$_mint(holder, supply); - await this.token.delegate(holder, { from: holder }); - }); - - it('call', async function () { - expect(await this.token.delegates(holder)).to.be.equal(holder); - - const { receipt } = await this.token.delegate(holderDelegatee, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - - expectEvent(receipt, 'DelegateChanged', { - delegator: holder, - fromDelegate: holder, - toDelegate: holderDelegatee, - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: supply, - newBalance: '0', - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holderDelegatee, - previousBalance: '0', - newBalance: supply, - }); - - expect(await this.token.delegates(holder)).to.be.equal(holderDelegatee); - - expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal('0'); - expect(await this.token.getCurrentVotes(holderDelegatee)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(holder, timepoint - 1)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(holderDelegatee, timepoint - 1)).to.be.bignumber.equal('0'); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(holder, timepoint)).to.be.bignumber.equal('0'); - expect(await this.token.getPriorVotes(holderDelegatee, timepoint)).to.be.bignumber.equal(supply); - }); - }); - - describe('transfers', function () { - beforeEach(async function () { - await this.token.$_mint(holder, supply); - }); - - it('no delegation', async function () { - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); - - this.holderVotes = '0'; - this.recipientVotes = '0'; - }); - - it('sender delegation', async function () { - await this.token.delegate(holder, { from: holder }); - - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: supply, - newBalance: supply.subn(1), - }); - - const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); - expect( - receipt.logs - .filter(({ event }) => event == 'DelegateVotesChanged') - .every(({ logIndex }) => transferLogIndex < logIndex), - ).to.be.equal(true); - - this.holderVotes = supply.subn(1); - this.recipientVotes = '0'; - }); - - it('receiver delegation', async function () { - await this.token.delegate(recipient, { from: recipient }); - - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); - - const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); - expect( - receipt.logs - .filter(({ event }) => event == 'DelegateVotesChanged') - .every(({ logIndex }) => transferLogIndex < logIndex), - ).to.be.equal(true); - - this.holderVotes = '0'; - this.recipientVotes = '1'; - }); - - it('full delegation', async function () { - await this.token.delegate(holder, { from: holder }); - await this.token.delegate(recipient, { from: recipient }); - - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: supply, - newBalance: supply.subn(1), - }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); - - const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); - expect( - receipt.logs - .filter(({ event }) => event == 'DelegateVotesChanged') - .every(({ logIndex }) => transferLogIndex < logIndex), - ).to.be.equal(true); - - this.holderVotes = supply.subn(1); - this.recipientVotes = '1'; - }); - - afterEach(async function () { - expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal(this.holderVotes); - expect(await this.token.getCurrentVotes(recipient)).to.be.bignumber.equal(this.recipientVotes); - - // need to advance 2 blocks to see the effect of a transfer on "getPriorVotes" - const timepoint = await clock[mode](); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(holder, timepoint)).to.be.bignumber.equal(this.holderVotes); - expect(await this.token.getPriorVotes(recipient, timepoint)).to.be.bignumber.equal(this.recipientVotes); - }); - }); - - // 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 () { - beforeEach(async function () { - await this.token.$_mint(holder, supply); - }); - - describe('balanceOf', function () { - it('grants to initial account', async function () { - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('10000000000000000000000000'); - }); - }); - - describe('numCheckpoints', function () { - it('returns the number of checkpoints for a delegate', async function () { - await this.token.transfer(recipient, '100', { from: holder }); //give an account a few tokens for readability - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0'); - - const t1 = await this.token.delegate(other1, { from: recipient }); - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1'); - - const t2 = await this.token.transfer(other2, 10, { from: recipient }); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2'); - - const t3 = await this.token.transfer(other2, 10, { from: recipient }); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('3'); - - const t4 = await this.token.transfer(recipient, 20, { from: holder }); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('4'); - - expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([t1.timepoint.toString(), '100']); - expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([t2.timepoint.toString(), '90']); - expect(await this.token.checkpoints(other1, 2)).to.be.deep.equal([t3.timepoint.toString(), '80']); - expect(await this.token.checkpoints(other1, 3)).to.be.deep.equal([t4.timepoint.toString(), '100']); - - await time.advanceBlock(); - expect(await this.token.getPriorVotes(other1, t1.timepoint)).to.be.bignumber.equal('100'); - expect(await this.token.getPriorVotes(other1, t2.timepoint)).to.be.bignumber.equal('90'); - expect(await this.token.getPriorVotes(other1, t3.timepoint)).to.be.bignumber.equal('80'); - expect(await this.token.getPriorVotes(other1, t4.timepoint)).to.be.bignumber.equal('100'); - }); - - it('does not add more than one checkpoint in a block', async function () { - await this.token.transfer(recipient, '100', { from: holder }); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0'); - - const [t1, t2, t3] = await batchInBlock([ - () => this.token.delegate(other1, { from: recipient, gas: 200000 }), - () => this.token.transfer(other2, 10, { from: recipient, gas: 200000 }), - () => this.token.transfer(other2, 10, { from: recipient, gas: 200000 }), - ]); - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1'); - expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([t1.timepoint.toString(), '80']); - - const t4 = await this.token.transfer(recipient, 20, { from: holder }); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2'); - expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([t4.timepoint.toString(), '100']); - }); - }); - - describe('getPriorVotes', function () { - it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPriorVotes(other1, 5e10), 'Votes: future lookup'); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.token.getPriorVotes(other1, 0)).to.be.bignumber.equal('0'); - }); - - it('returns the latest block if >= last checkpoint block', async function () { - const { receipt } = await this.token.delegate(other1, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPriorVotes(other1, timepoint)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - - it('returns zero if < first checkpoint block', async function () { - await time.advanceBlock(); - const { receipt } = await this.token.delegate(other1, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPriorVotes(other1, timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPriorVotes(other1, timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - - it('generally returns the voting balance at the appropriate checkpoint', async function () { - const t1 = await this.token.delegate(other1, { from: holder }); - await time.advanceBlock(); - await time.advanceBlock(); - const t2 = await this.token.transfer(other2, 10, { from: holder }); - await time.advanceBlock(); - await time.advanceBlock(); - const t3 = await this.token.transfer(other2, 10, { from: holder }); - await time.advanceBlock(); - await time.advanceBlock(); - const t4 = await this.token.transfer(holder, 20, { from: other2 }); - await time.advanceBlock(); - await time.advanceBlock(); - - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - - expect(await this.token.getPriorVotes(other1, t1.timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPriorVotes(other1, t1.timepoint)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, t1.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, t2.timepoint)).to.be.bignumber.equal( - '9999999999999999999999990', - ); - expect(await this.token.getPriorVotes(other1, t2.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999990', - ); - expect(await this.token.getPriorVotes(other1, t3.timepoint)).to.be.bignumber.equal( - '9999999999999999999999980', - ); - expect(await this.token.getPriorVotes(other1, t3.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999980', - ); - expect(await this.token.getPriorVotes(other1, t4.timepoint)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, t4.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - }); - }); - - describe('getPastTotalSupply', function () { - beforeEach(async function () { - await this.token.delegate(holder, { from: holder }); - }); - - it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPastTotalSupply(5e10), 'Votes: future lookup'); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.token.getPastTotalSupply(0)).to.be.bignumber.equal('0'); - }); - - it('returns the latest block if >= last checkpoint block', async function () { - const { receipt } = await this.token.$_mint(holder, supply); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPastTotalSupply(timepoint)).to.be.bignumber.equal(supply); - expect(await this.token.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal(supply); - }); - - it('returns zero if < first checkpoint block', async function () { - await time.advanceBlock(); - const { receipt } = await this.token.$_mint(holder, supply); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPastTotalSupply(timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPastTotalSupply(timepoint + 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(); - - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - - expect(await this.token.getPastTotalSupply(t1.timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPastTotalSupply(t1.timepoint)).to.be.bignumber.equal('10000000000000000000000000'); - expect(await this.token.getPastTotalSupply(t1.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPastTotalSupply(t2.timepoint)).to.be.bignumber.equal('9999999999999999999999990'); - expect(await this.token.getPastTotalSupply(t2.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999990', - ); - expect(await this.token.getPastTotalSupply(t3.timepoint)).to.be.bignumber.equal('9999999999999999999999980'); - expect(await this.token.getPastTotalSupply(t3.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999980', - ); - expect(await this.token.getPastTotalSupply(t4.timepoint)).to.be.bignumber.equal('10000000000000000000000000'); - expect(await this.token.getPastTotalSupply(t4.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - }); - }); - } -});