Browse Source

Remove TokenTimelock, PaymentSplitter, ERC20Snapshot, ERC20VotesComp, GovernorVotesComp (#4276)

Hadrien Croubois 2 năm trước cách đây
mục cha
commit
15c5c71795

+ 10 - 0
CHANGELOG.md

@@ -1,5 +1,15 @@
 # Changelog
 
+### Removals
+
+The following contracts were removed:
+
+- `ERC20Snapshot`
+- `ERC20VotesComp`
+- `GovernorVotesComp`
+- `PaymentSplitter`
+- `TokenTimelock` (removed in favor of `VestingWallet`)
+
 ### How to upgrade from 4.x
 
 #### ERC20, ERC721, and ERC1155

+ 0 - 214
contracts/finance/PaymentSplitter.sol

@@ -1,214 +0,0 @@
-// SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts (last updated v4.8.0) (finance/PaymentSplitter.sol)
-
-pragma solidity ^0.8.0;
-
-import "../token/ERC20/utils/SafeERC20.sol";
-import "../utils/Address.sol";
-import "../utils/Context.sol";
-
-/**
- * @title PaymentSplitter
- * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware
- * that the Ether will be split in this way, since it is handled transparently by the contract.
- *
- * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each
- * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim
- * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the
- * time of contract deployment and can't be updated thereafter.
- *
- * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the
- * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}
- * function.
- *
- * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and
- * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you
- * to run tests before sending real value to this contract.
- */
-contract PaymentSplitter is Context {
-    event PayeeAdded(address account, uint256 shares);
-    event PaymentReleased(address to, uint256 amount);
-    event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
-    event PaymentReceived(address from, uint256 amount);
-
-    uint256 private _totalShares;
-    uint256 private _totalReleased;
-
-    mapping(address => uint256) private _shares;
-    mapping(address => uint256) private _released;
-    address[] private _payees;
-
-    mapping(IERC20 => uint256) private _erc20TotalReleased;
-    mapping(IERC20 => mapping(address => uint256)) private _erc20Released;
-
-    /**
-     * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at
-     * the matching position in the `shares` array.
-     *
-     * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no
-     * duplicates in `payees`.
-     */
-    constructor(address[] memory payees, uint256[] memory shares_) payable {
-        require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");
-        require(payees.length > 0, "PaymentSplitter: no payees");
-
-        for (uint256 i = 0; i < payees.length; i++) {
-            _addPayee(payees[i], shares_[i]);
-        }
-    }
-
-    /**
-     * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully
-     * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the
-     * reliability of the events, and not the actual splitting of Ether.
-     *
-     * To learn more about this see the Solidity documentation for
-     * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback
-     * functions].
-     */
-    receive() external payable virtual {
-        emit PaymentReceived(_msgSender(), msg.value);
-    }
-
-    /**
-     * @dev Getter for the total shares held by payees.
-     */
-    function totalShares() public view returns (uint256) {
-        return _totalShares;
-    }
-
-    /**
-     * @dev Getter for the total amount of Ether already released.
-     */
-    function totalReleased() public view returns (uint256) {
-        return _totalReleased;
-    }
-
-    /**
-     * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20
-     * contract.
-     */
-    function totalReleased(IERC20 token) public view returns (uint256) {
-        return _erc20TotalReleased[token];
-    }
-
-    /**
-     * @dev Getter for the amount of shares held by an account.
-     */
-    function shares(address account) public view returns (uint256) {
-        return _shares[account];
-    }
-
-    /**
-     * @dev Getter for the amount of Ether already released to a payee.
-     */
-    function released(address account) public view returns (uint256) {
-        return _released[account];
-    }
-
-    /**
-     * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an
-     * IERC20 contract.
-     */
-    function released(IERC20 token, address account) public view returns (uint256) {
-        return _erc20Released[token][account];
-    }
-
-    /**
-     * @dev Getter for the address of the payee number `index`.
-     */
-    function payee(uint256 index) public view returns (address) {
-        return _payees[index];
-    }
-
-    /**
-     * @dev Getter for the amount of payee's releasable Ether.
-     */
-    function releasable(address account) public view returns (uint256) {
-        uint256 totalReceived = address(this).balance + totalReleased();
-        return _pendingPayment(account, totalReceived, released(account));
-    }
-
-    /**
-     * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an
-     * IERC20 contract.
-     */
-    function releasable(IERC20 token, address account) public view returns (uint256) {
-        uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
-        return _pendingPayment(account, totalReceived, released(token, account));
-    }
-
-    /**
-     * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
-     * total shares and their previous withdrawals.
-     */
-    function release(address payable account) public virtual {
-        require(_shares[account] > 0, "PaymentSplitter: account has no shares");
-
-        uint256 payment = releasable(account);
-
-        require(payment != 0, "PaymentSplitter: account is not due payment");
-
-        // _totalReleased is the sum of all values in _released.
-        // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow.
-        _totalReleased += payment;
-        unchecked {
-            _released[account] += payment;
-        }
-
-        Address.sendValue(account, payment);
-        emit PaymentReleased(account, payment);
-    }
-
-    /**
-     * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their
-     * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20
-     * contract.
-     */
-    function release(IERC20 token, address account) public virtual {
-        require(_shares[account] > 0, "PaymentSplitter: account has no shares");
-
-        uint256 payment = releasable(token, account);
-
-        require(payment != 0, "PaymentSplitter: account is not due payment");
-
-        // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token].
-        // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"
-        // cannot overflow.
-        _erc20TotalReleased[token] += payment;
-        unchecked {
-            _erc20Released[token][account] += payment;
-        }
-
-        SafeERC20.safeTransfer(token, account, payment);
-        emit ERC20PaymentReleased(token, account, payment);
-    }
-
-    /**
-     * @dev internal logic for computing the pending payment of an `account` given the token historical balances and
-     * already released amounts.
-     */
-    function _pendingPayment(
-        address account,
-        uint256 totalReceived,
-        uint256 alreadyReleased
-    ) private view returns (uint256) {
-        return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;
-    }
-
-    /**
-     * @dev Add a new payee to the contract.
-     * @param account The address of the payee to add.
-     * @param shares_ The number of shares owned by the payee.
-     */
-    function _addPayee(address account, uint256 shares_) private {
-        require(account != address(0), "PaymentSplitter: account is the zero address");
-        require(shares_ > 0, "PaymentSplitter: shares are 0");
-        require(_shares[account] == 0, "PaymentSplitter: account already has shares");
-
-        _payees.push(account);
-        _shares[account] = shares_;
-        _totalShares = _totalShares + shares_;
-        emit PayeeAdded(account, shares_);
-    }
-}

+ 0 - 6
contracts/finance/README.adoc

@@ -5,16 +5,10 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/
 
 This directory includes primitives for financial systems:
 
-- {PaymentSplitter} allows to split Ether and ERC20 payments among a group of accounts. The sender does not need to be
-  aware that the assets will be split in this way, since it is handled transparently by the contract. The split can be
-  in equal parts or in any other arbitrary proportion.
-
 - {VestingWallet} handles the vesting of Ether and ERC20 tokens for a given beneficiary. Custody of multiple tokens can
   be given to this contract, which will release the token to the beneficiary following a given, customizable, vesting
   schedule.
 
 == Contracts
 
-{{PaymentSplitter}}
-
 {{VestingWallet}}

+ 11 - 1
contracts/finance/VestingWallet.sol

@@ -15,6 +15,9 @@ import "../utils/Context.sol";
  * Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning.
  * Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly)
  * be immediately releasable.
+ *
+ * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for
+ * a beneficiary until a specified time.
  */
 contract VestingWallet is Context {
     event EtherReleased(uint256 amount);
@@ -62,6 +65,13 @@ contract VestingWallet is Context {
         return _duration;
     }
 
+    /**
+     * @dev Getter for the end timestamp.
+     */
+    function end() public view virtual returns (uint256) {
+        return start() + duration();
+    }
+
     /**
      * @dev Amount of eth already released
      */
@@ -136,7 +146,7 @@ contract VestingWallet is Context {
     function _vestingSchedule(uint256 totalAllocation, uint64 timestamp) internal view virtual returns (uint256) {
         if (timestamp < start()) {
             return 0;
-        } else if (timestamp > start() + duration()) {
+        } else if (timestamp > end()) {
             return totalAllocation;
         } else {
             return (totalAllocation * (timestamp - start())) / duration();

+ 0 - 4
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}}

+ 0 - 55
contracts/governance/extensions/GovernorVotesComp.sol

@@ -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);
-    }
-}

+ 0 - 20
contracts/mocks/governance/GovernorCompMock.sol

@@ -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;
-    }
-}

+ 2 - 2
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;

+ 0 - 12
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);

+ 5 - 10
contracts/token/ERC20/README.adoc

@@ -18,18 +18,19 @@ Additionally there are multiple custom extensions, including:
 * {ERC20Burnable}: destruction of own tokens.
 * {ERC20Capped}: enforcement of a cap to the total supply when minting tokens.
 * {ERC20Pausable}: ability to pause token transfers.
-* {ERC20Snapshot}: efficient storage of past token balances to be later queried at any point in time.
 * {ERC20Permit}: gasless approval of tokens (standardized as ERC2612).
 * {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156).
 * {ERC20Votes}: support for voting and vote delegation.
-* {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).
 
-Finally, there are some utilities to interact with ERC20 contracts in various ways.
+Finally, there are some utilities to interact with ERC20 contracts in various ways:
 
 * {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values.
-* {TokenTimelock}: hold tokens for a beneficiary until a specified time.
+
+Other utilities that support ERC20 assets can be found in codebase:
+
+* ERC20 tokens can be timelocked (held tokens for a beneficiary until a specified time) or vested (released following a given schedule) using a {VestingWallet}.
 
 NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <<ERC20-_mint-address-uint256-,`_mint`>>) and expose them as external functions in the way they prefer.
 
@@ -51,12 +52,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
 
 {{ERC20Permit}}
 
-{{ERC20Snapshot}}
-
 {{ERC20Votes}}
 
-{{ERC20VotesComp}}
-
 {{ERC20Wrapper}}
 
 {{ERC20FlashMint}}
@@ -66,5 +63,3 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
 == Utilities
 
 {{SafeERC20}}
-
-{{TokenTimelock}}

+ 0 - 189
contracts/token/ERC20/extensions/ERC20Snapshot.sol

@@ -1,189 +0,0 @@
-// SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Snapshot.sol)
-
-pragma solidity ^0.8.0;
-
-import "../ERC20.sol";
-import "../../../utils/Arrays.sol";
-import "../../../utils/Counters.sol";
-
-/**
- * @dev This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and
- * total supply at the time are recorded for later access.
- *
- * This can be used to safely create mechanisms based on token balances such as trustless dividends or weighted voting.
- * In naive implementations it's possible to perform a "double spend" attack by reusing the same balance from different
- * accounts. By using snapshots to calculate dividends or voting power, those attacks no longer apply. It can also be
- * used to create an efficient ERC20 forking mechanism.
- *
- * Snapshots are created by the internal {_snapshot} function, which will emit the {Snapshot} event and return a
- * snapshot id. To get the total supply at the time of a snapshot, call the function {totalSupplyAt} with the snapshot
- * id. To get the balance of an account at the time of a snapshot, call the {balanceOfAt} function with the snapshot id
- * and the account address.
- *
- * NOTE: Snapshot policy can be customized by overriding the {_getCurrentSnapshotId} method. For example, having it
- * return `block.number` will trigger the creation of snapshot at the beginning of each new block. When overriding this
- * function, be careful about the monotonicity of its result. Non-monotonic snapshot ids will break the contract.
- *
- * Implementing snapshots for every block using this method will incur significant gas costs. For a gas-efficient
- * alternative consider {ERC20Votes}.
- *
- * ==== Gas Costs
- *
- * Snapshots are efficient. Snapshot creation is _O(1)_. Retrieval of balances or total supply from a snapshot is _O(log
- * n)_ in the number of snapshots that have been created, although _n_ for a specific account will generally be much
- * smaller since identical balances in subsequent snapshots are stored as a single entry.
- *
- * There is a constant overhead for normal ERC20 transfers due to the additional snapshot bookkeeping. This overhead is
- * only significant for the first transfer that immediately follows a snapshot for a particular account. Subsequent
- * transfers will have normal cost until the next snapshot, and so on.
- */
-
-abstract contract ERC20Snapshot is ERC20 {
-    // Inspired by Jordi Baylina's MiniMeToken to record historical balances:
-    // https://github.com/Giveth/minime/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol
-
-    using Arrays for uint256[];
-    using Counters for Counters.Counter;
-
-    // Snapshotted values have arrays of ids and the value corresponding to that id. These could be an array of a
-    // Snapshot struct, but that would impede usage of functions that work on an array.
-    struct Snapshots {
-        uint256[] ids;
-        uint256[] values;
-    }
-
-    mapping(address => Snapshots) private _accountBalanceSnapshots;
-    Snapshots private _totalSupplySnapshots;
-
-    // Snapshot ids increase monotonically, with the first value being 1. An id of 0 is invalid.
-    Counters.Counter private _currentSnapshotId;
-
-    /**
-     * @dev Emitted by {_snapshot} when a snapshot identified by `id` is created.
-     */
-    event Snapshot(uint256 id);
-
-    /**
-     * @dev Creates a new snapshot and returns its snapshot id.
-     *
-     * Emits a {Snapshot} event that contains the same id.
-     *
-     * {_snapshot} is `internal` and you have to decide how to expose it externally. Its usage may be restricted to a
-     * set of accounts, for example using {AccessControl}, or it may be open to the public.
-     *
-     * [WARNING]
-     * ====
-     * While an open way of calling {_snapshot} is required for certain trust minimization mechanisms such as forking,
-     * you must consider that it can potentially be used by attackers in two ways.
-     *
-     * First, it can be used to increase the cost of retrieval of values from snapshots, although it will grow
-     * logarithmically thus rendering this attack ineffective in the long term. Second, it can be used to target
-     * specific accounts and increase the cost of ERC20 transfers for them, in the ways specified in the Gas Costs
-     * section above.
-     *
-     * We haven't measured the actual numbers; if this is something you're interested in please reach out to us.
-     * ====
-     */
-    function _snapshot() internal virtual returns (uint256) {
-        _currentSnapshotId.increment();
-
-        uint256 currentId = _getCurrentSnapshotId();
-        emit Snapshot(currentId);
-        return currentId;
-    }
-
-    /**
-     * @dev Get the current snapshotId
-     */
-    function _getCurrentSnapshotId() internal view virtual returns (uint256) {
-        return _currentSnapshotId.current();
-    }
-
-    /**
-     * @dev Retrieves the balance of `account` at the time `snapshotId` was created.
-     */
-    function balanceOfAt(address account, uint256 snapshotId) public view virtual returns (uint256) {
-        (bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]);
-
-        return snapshotted ? value : balanceOf(account);
-    }
-
-    /**
-     * @dev Retrieves the total supply at the time `snapshotId` was created.
-     */
-    function totalSupplyAt(uint256 snapshotId) public view virtual returns (uint256) {
-        (bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots);
-
-        return snapshotted ? value : totalSupply();
-    }
-
-    // Update balance and/or total supply snapshots before the values are modified. This is executed
-    // for _mint, _burn, and _transfer operations.
-    function _update(address from, address to, uint256 amount) internal virtual override {
-        if (from == address(0)) {
-            _updateTotalSupplySnapshot();
-        } else {
-            _updateAccountSnapshot(from);
-        }
-
-        if (to == address(0)) {
-            _updateTotalSupplySnapshot();
-        } else {
-            _updateAccountSnapshot(to);
-        }
-
-        super._update(from, to, amount);
-    }
-
-    function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256) {
-        require(snapshotId > 0, "ERC20Snapshot: id is 0");
-        require(snapshotId <= _getCurrentSnapshotId(), "ERC20Snapshot: nonexistent id");
-
-        // When a valid snapshot is queried, there are three possibilities:
-        //  a) The queried value was not modified after the snapshot was taken. Therefore, a snapshot entry was never
-        //  created for this id, and all stored snapshot ids are smaller than the requested one. The value that corresponds
-        //  to this id is the current one.
-        //  b) The queried value was modified after the snapshot was taken. Therefore, there will be an entry with the
-        //  requested id, and its value is the one to return.
-        //  c) More snapshots were created after the requested one, and the queried value was later modified. There will be
-        //  no entry for the requested id: the value that corresponds to it is that of the smallest snapshot id that is
-        //  larger than the requested one.
-        //
-        // In summary, we need to find an element in an array, returning the index of the smallest value that is larger if
-        // it is not found, unless said value doesn't exist (e.g. when all values are smaller). Arrays.findUpperBound does
-        // exactly this.
-
-        uint256 index = snapshots.ids.findUpperBound(snapshotId);
-
-        if (index == snapshots.ids.length) {
-            return (false, 0);
-        } else {
-            return (true, snapshots.values[index]);
-        }
-    }
-
-    function _updateAccountSnapshot(address account) private {
-        _updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account));
-    }
-
-    function _updateTotalSupplySnapshot() private {
-        _updateSnapshot(_totalSupplySnapshots, totalSupply());
-    }
-
-    function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) private {
-        uint256 currentId = _getCurrentSnapshotId();
-        if (_lastSnapshotId(snapshots.ids) < currentId) {
-            snapshots.ids.push(currentId);
-            snapshots.values.push(currentValue);
-        }
-    }
-
-    function _lastSnapshotId(uint256[] storage ids) private view returns (uint256) {
-        if (ids.length == 0) {
-            return 0;
-        } else {
-            return ids[ids.length - 1];
-        }
-    }
-}

+ 1 - 1
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

+ 0 - 46
contracts/token/ERC20/extensions/ERC20VotesComp.sol

@@ -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;
-    }
-}

+ 0 - 72
contracts/token/ERC20/utils/TokenTimelock.sol

@@ -1,72 +0,0 @@
-// SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/TokenTimelock.sol)
-
-pragma solidity ^0.8.0;
-
-import "./SafeERC20.sol";
-
-/**
- * @dev A token holder contract that will allow a beneficiary to extract the
- * tokens after a given release time.
- *
- * Useful for simple vesting schedules like "advisors get all of their tokens
- * after 1 year".
- */
-contract TokenTimelock {
-    using SafeERC20 for IERC20;
-
-    // ERC20 basic token contract being held
-    IERC20 private immutable _token;
-
-    // beneficiary of tokens after they are released
-    address private immutable _beneficiary;
-
-    // timestamp when token release is enabled
-    uint256 private immutable _releaseTime;
-
-    /**
-     * @dev Deploys a timelock instance that is able to hold the token specified, and will only release it to
-     * `beneficiary_` when {release} is invoked after `releaseTime_`. The release time is specified as a Unix timestamp
-     * (in seconds).
-     */
-    constructor(IERC20 token_, address beneficiary_, uint256 releaseTime_) {
-        require(releaseTime_ > block.timestamp, "TokenTimelock: release time is before current time");
-        _token = token_;
-        _beneficiary = beneficiary_;
-        _releaseTime = releaseTime_;
-    }
-
-    /**
-     * @dev Returns the token being held.
-     */
-    function token() public view virtual returns (IERC20) {
-        return _token;
-    }
-
-    /**
-     * @dev Returns the beneficiary that will receive the tokens.
-     */
-    function beneficiary() public view virtual returns (address) {
-        return _beneficiary;
-    }
-
-    /**
-     * @dev Returns the time when the tokens are released in seconds since Unix epoch (i.e. Unix timestamp).
-     */
-    function releaseTime() public view virtual returns (uint256) {
-        return _releaseTime;
-    }
-
-    /**
-     * @dev Transfers tokens held by the timelock to the beneficiary. Will only succeed if invoked after the release
-     * time.
-     */
-    function release() public virtual {
-        require(block.timestamp >= releaseTime(), "TokenTimelock: current time is before release time");
-
-        uint256 amount = token().balanceOf(address(this));
-        require(amount > 0, "TokenTimelock: no tokens to release");
-
-        token().safeTransfer(beneficiary(), amount);
-    }
-}

+ 5 - 31
scripts/upgradeable/upgradeable.patch

@@ -101,13 +101,13 @@ index 9fc95518..53130e3c 100644
  }
  ```
 diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol
-index fe67eb54..d26ea4e1 100644
+index bb70d19f..38513771 100644
 --- a/contracts/finance/VestingWallet.sol
 +++ b/contracts/finance/VestingWallet.sol
-@@ -15,6 +15,8 @@ import "../utils/Context.sol";
-  * Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning.
-  * Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly)
-  * be immediately releasable.
+@@ -18,6 +18,8 @@ import "../utils/Context.sol";
+  *
+  * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for
+  * a beneficiary until a specified time.
 + *
 + * @custom:storage-size 52
   */
@@ -126,19 +126,6 @@ index 64431711..885f0e42 100644
   */
  abstract contract GovernorVotes is Governor {
      IERC5805 public immutable token;
-diff --git a/contracts/governance/extensions/GovernorVotesComp.sol b/contracts/governance/extensions/GovernorVotesComp.sol
-index 17250ad7..1d26b72e 100644
---- a/contracts/governance/extensions/GovernorVotesComp.sol
-+++ b/contracts/governance/extensions/GovernorVotesComp.sol
-@@ -10,6 +10,8 @@ import "../../token/ERC20/extensions/ERC20VotesComp.sol";
-  * @dev Extension of {Governor} for voting weight extraction from a Comp token.
-  *
-  * _Available since v4.3._
-+ *
-+ * @custom:storage-size 51
-  */
- abstract contract GovernorVotesComp is Governor {
-     ERC20VotesComp public immutable token;
 diff --git a/contracts/package.json b/contracts/package.json
 index 55e70b17..ceefb984 100644
 --- a/contracts/package.json
@@ -198,19 +185,6 @@ index bfe782e4..7264fe32 100644
   */
  abstract contract ERC20Wrapper is ERC20 {
      IERC20 private immutable _underlying;
-diff --git a/contracts/token/ERC20/utils/TokenTimelock.sol b/contracts/token/ERC20/utils/TokenTimelock.sol
-index ed855b7b..3d30f59d 100644
---- a/contracts/token/ERC20/utils/TokenTimelock.sol
-+++ b/contracts/token/ERC20/utils/TokenTimelock.sol
-@@ -11,6 +11,8 @@ import "./SafeERC20.sol";
-  *
-  * Useful for simple vesting schedules like "advisors get all of their tokens
-  * after 1 year".
-+ *
-+ * @custom:storage-size 53
-  */
- contract TokenTimelock {
-     using SafeERC20 for IERC20;
 diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol
 index 6a4e1cad..55d8eced 100644
 --- a/contracts/utils/cryptography/EIP712.sol

+ 0 - 217
test/finance/PaymentSplitter.test.js

@@ -1,217 +0,0 @@
-const { balance, constants, ether, expectEvent, send, expectRevert } = require('@openzeppelin/test-helpers');
-const { ZERO_ADDRESS } = constants;
-
-const { expect } = require('chai');
-
-const PaymentSplitter = artifacts.require('PaymentSplitter');
-const ERC20 = artifacts.require('$ERC20');
-
-contract('PaymentSplitter', function (accounts) {
-  const [owner, payee1, payee2, payee3, nonpayee1, payer1] = accounts;
-
-  const amount = ether('1');
-
-  it('rejects an empty set of payees', async function () {
-    await expectRevert(PaymentSplitter.new([], []), 'PaymentSplitter: no payees');
-  });
-
-  it('rejects more payees than shares', async function () {
-    await expectRevert(
-      PaymentSplitter.new([payee1, payee2, payee3], [20, 30]),
-      'PaymentSplitter: payees and shares length mismatch',
-    );
-  });
-
-  it('rejects more shares than payees', async function () {
-    await expectRevert(
-      PaymentSplitter.new([payee1, payee2], [20, 30, 40]),
-      'PaymentSplitter: payees and shares length mismatch',
-    );
-  });
-
-  it('rejects null payees', async function () {
-    await expectRevert(
-      PaymentSplitter.new([payee1, ZERO_ADDRESS], [20, 30]),
-      'PaymentSplitter: account is the zero address',
-    );
-  });
-
-  it('rejects zero-valued shares', async function () {
-    await expectRevert(PaymentSplitter.new([payee1, payee2], [20, 0]), 'PaymentSplitter: shares are 0');
-  });
-
-  it('rejects repeated payees', async function () {
-    await expectRevert(PaymentSplitter.new([payee1, payee1], [20, 30]), 'PaymentSplitter: account already has shares');
-  });
-
-  context('once deployed', function () {
-    beforeEach(async function () {
-      this.payees = [payee1, payee2, payee3];
-      this.shares = [20, 10, 70];
-
-      this.contract = await PaymentSplitter.new(this.payees, this.shares);
-      this.token = await ERC20.new('MyToken', 'MT');
-      await this.token.$_mint(owner, ether('1000'));
-    });
-
-    it('has total shares', async function () {
-      expect(await this.contract.totalShares()).to.be.bignumber.equal('100');
-    });
-
-    it('has payees', async function () {
-      await Promise.all(
-        this.payees.map(async (payee, index) => {
-          expect(await this.contract.payee(index)).to.equal(payee);
-          expect(await this.contract.released(payee)).to.be.bignumber.equal('0');
-          expect(await this.contract.releasable(payee)).to.be.bignumber.equal('0');
-        }),
-      );
-    });
-
-    describe('accepts payments', function () {
-      it('Ether', async function () {
-        await send.ether(owner, this.contract.address, amount);
-
-        expect(await balance.current(this.contract.address)).to.be.bignumber.equal(amount);
-      });
-
-      it('Token', async function () {
-        await this.token.transfer(this.contract.address, amount, { from: owner });
-
-        expect(await this.token.balanceOf(this.contract.address)).to.be.bignumber.equal(amount);
-      });
-    });
-
-    describe('shares', function () {
-      it('stores shares if address is payee', async function () {
-        expect(await this.contract.shares(payee1)).to.be.bignumber.not.equal('0');
-      });
-
-      it('does not store shares if address is not payee', async function () {
-        expect(await this.contract.shares(nonpayee1)).to.be.bignumber.equal('0');
-      });
-    });
-
-    describe('release', function () {
-      describe('Ether', function () {
-        it('reverts if no funds to claim', async function () {
-          await expectRevert(this.contract.release(payee1), 'PaymentSplitter: account is not due payment');
-        });
-        it('reverts if non-payee want to claim', async function () {
-          await send.ether(payer1, this.contract.address, amount);
-          await expectRevert(this.contract.release(nonpayee1), 'PaymentSplitter: account has no shares');
-        });
-      });
-
-      describe('Token', function () {
-        it('reverts if no funds to claim', async function () {
-          await expectRevert(
-            this.contract.release(this.token.address, payee1),
-            'PaymentSplitter: account is not due payment',
-          );
-        });
-        it('reverts if non-payee want to claim', async function () {
-          await this.token.transfer(this.contract.address, amount, { from: owner });
-          await expectRevert(
-            this.contract.release(this.token.address, nonpayee1),
-            'PaymentSplitter: account has no shares',
-          );
-        });
-      });
-    });
-
-    describe('tracks releasable and released', function () {
-      it('Ether', async function () {
-        await send.ether(payer1, this.contract.address, amount);
-        const payment = amount.divn(10);
-        expect(await this.contract.releasable(payee2)).to.be.bignumber.equal(payment);
-        await this.contract.release(payee2);
-        expect(await this.contract.releasable(payee2)).to.be.bignumber.equal('0');
-        expect(await this.contract.released(payee2)).to.be.bignumber.equal(payment);
-      });
-
-      it('Token', async function () {
-        await this.token.transfer(this.contract.address, amount, { from: owner });
-        const payment = amount.divn(10);
-        expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal(payment);
-        await this.contract.release(this.token.address, payee2);
-        expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal('0');
-        expect(await this.contract.released(this.token.address, payee2)).to.be.bignumber.equal(payment);
-      });
-    });
-
-    describe('distributes funds to payees', function () {
-      it('Ether', async function () {
-        await send.ether(payer1, this.contract.address, amount);
-
-        // receive funds
-        const initBalance = await balance.current(this.contract.address);
-        expect(initBalance).to.be.bignumber.equal(amount);
-
-        // distribute to payees
-
-        const tracker1 = await balance.tracker(payee1);
-        const receipt1 = await this.contract.release(payee1);
-        const profit1 = await tracker1.delta();
-        expect(profit1).to.be.bignumber.equal(ether('0.20'));
-        expectEvent(receipt1, 'PaymentReleased', { to: payee1, amount: profit1 });
-
-        const tracker2 = await balance.tracker(payee2);
-        const receipt2 = await this.contract.release(payee2);
-        const profit2 = await tracker2.delta();
-        expect(profit2).to.be.bignumber.equal(ether('0.10'));
-        expectEvent(receipt2, 'PaymentReleased', { to: payee2, amount: profit2 });
-
-        const tracker3 = await balance.tracker(payee3);
-        const receipt3 = await this.contract.release(payee3);
-        const profit3 = await tracker3.delta();
-        expect(profit3).to.be.bignumber.equal(ether('0.70'));
-        expectEvent(receipt3, 'PaymentReleased', { to: payee3, amount: profit3 });
-
-        // end balance should be zero
-        expect(await balance.current(this.contract.address)).to.be.bignumber.equal('0');
-
-        // check correct funds released accounting
-        expect(await this.contract.totalReleased()).to.be.bignumber.equal(initBalance);
-      });
-
-      it('Token', async function () {
-        expect(await this.token.balanceOf(payee1)).to.be.bignumber.equal('0');
-        expect(await this.token.balanceOf(payee2)).to.be.bignumber.equal('0');
-        expect(await this.token.balanceOf(payee3)).to.be.bignumber.equal('0');
-
-        await this.token.transfer(this.contract.address, amount, { from: owner });
-
-        expectEvent(await this.contract.release(this.token.address, payee1), 'ERC20PaymentReleased', {
-          token: this.token.address,
-          to: payee1,
-          amount: ether('0.20'),
-        });
-
-        await this.token.transfer(this.contract.address, amount, { from: owner });
-
-        expectEvent(await this.contract.release(this.token.address, payee1), 'ERC20PaymentReleased', {
-          token: this.token.address,
-          to: payee1,
-          amount: ether('0.20'),
-        });
-
-        expectEvent(await this.contract.release(this.token.address, payee2), 'ERC20PaymentReleased', {
-          token: this.token.address,
-          to: payee2,
-          amount: ether('0.20'),
-        });
-
-        expectEvent(await this.contract.release(this.token.address, payee3), 'ERC20PaymentReleased', {
-          token: this.token.address,
-          to: payee3,
-          amount: ether('1.40'),
-        });
-
-        expect(await this.token.balanceOf(payee1)).to.be.bignumber.equal(ether('0.40'));
-        expect(await this.token.balanceOf(payee2)).to.be.bignumber.equal(ether('0.20'));
-        expect(await this.token.balanceOf(payee3)).to.be.bignumber.equal(ether('1.40'));
-      });
-    });
-  });
-});

+ 1 - 0
test/finance/VestingWallet.test.js

@@ -30,6 +30,7 @@ contract('VestingWallet', function (accounts) {
     expect(await this.mock.beneficiary()).to.be.equal(beneficiary);
     expect(await this.mock.start()).to.be.bignumber.equal(this.start);
     expect(await this.mock.duration()).to.be.bignumber.equal(duration);
+    expect(await this.mock.end()).to.be.bignumber.equal(this.start.add(duration));
   });
 
   describe('vesting schedule', function () {

+ 2 - 2
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) {

+ 0 - 89
test/governance/extensions/GovernorComp.test.js

@@ -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(),
-            },
-          ],
-          '<proposal description>',
-        );
-      });
-
-      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'));
-        });
-      });
-    });
-  }
-});

+ 0 - 207
test/token/ERC20/extensions/ERC20Snapshot.test.js

@@ -1,207 +0,0 @@
-const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
-const ERC20Snapshot = artifacts.require('$ERC20Snapshot');
-
-const { expect } = require('chai');
-
-contract('ERC20Snapshot', function (accounts) {
-  const [initialHolder, recipient, other] = accounts;
-
-  const initialSupply = new BN(100);
-
-  const name = 'My Token';
-  const symbol = 'MTKN';
-
-  beforeEach(async function () {
-    this.token = await ERC20Snapshot.new(name, symbol);
-    await this.token.$_mint(initialHolder, initialSupply);
-  });
-
-  describe('snapshot', function () {
-    it('emits a snapshot event', async function () {
-      const receipt = await this.token.$_snapshot();
-      expectEvent(receipt, 'Snapshot');
-    });
-
-    it('creates increasing snapshots ids, starting from 1', async function () {
-      for (const id of ['1', '2', '3', '4', '5']) {
-        const receipt = await this.token.$_snapshot();
-        expectEvent(receipt, 'Snapshot', { id });
-      }
-    });
-  });
-
-  describe('totalSupplyAt', function () {
-    it('reverts with a snapshot id of 0', async function () {
-      await expectRevert(this.token.totalSupplyAt(0), 'ERC20Snapshot: id is 0');
-    });
-
-    it('reverts with a not-yet-created snapshot id', async function () {
-      await expectRevert(this.token.totalSupplyAt(1), 'ERC20Snapshot: nonexistent id');
-    });
-
-    context('with initial snapshot', function () {
-      beforeEach(async function () {
-        this.initialSnapshotId = new BN('1');
-
-        const receipt = await this.token.$_snapshot();
-        expectEvent(receipt, 'Snapshot', { id: this.initialSnapshotId });
-      });
-
-      context('with no supply changes after the snapshot', function () {
-        it('returns the current total supply', async function () {
-          expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
-        });
-      });
-
-      context('with supply changes after the snapshot', function () {
-        beforeEach(async function () {
-          await this.token.$_mint(other, new BN('50'));
-          await this.token.$_burn(initialHolder, new BN('20'));
-        });
-
-        it('returns the total supply before the changes', async function () {
-          expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
-        });
-
-        context('with a second snapshot after supply changes', function () {
-          beforeEach(async function () {
-            this.secondSnapshotId = new BN('2');
-
-            const receipt = await this.token.$_snapshot();
-            expectEvent(receipt, 'Snapshot', { id: this.secondSnapshotId });
-          });
-
-          it('snapshots return the supply before and after the changes', async function () {
-            expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
-
-            expect(await this.token.totalSupplyAt(this.secondSnapshotId)).to.be.bignumber.equal(
-              await this.token.totalSupply(),
-            );
-          });
-        });
-
-        context('with multiple snapshots after supply changes', function () {
-          beforeEach(async function () {
-            this.secondSnapshotIds = ['2', '3', '4'];
-
-            for (const id of this.secondSnapshotIds) {
-              const receipt = await this.token.$_snapshot();
-              expectEvent(receipt, 'Snapshot', { id });
-            }
-          });
-
-          it('all posterior snapshots return the supply after the changes', async function () {
-            expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
-
-            const currentSupply = await this.token.totalSupply();
-
-            for (const id of this.secondSnapshotIds) {
-              expect(await this.token.totalSupplyAt(id)).to.be.bignumber.equal(currentSupply);
-            }
-          });
-        });
-      });
-    });
-  });
-
-  describe('balanceOfAt', function () {
-    it('reverts with a snapshot id of 0', async function () {
-      await expectRevert(this.token.balanceOfAt(other, 0), 'ERC20Snapshot: id is 0');
-    });
-
-    it('reverts with a not-yet-created snapshot id', async function () {
-      await expectRevert(this.token.balanceOfAt(other, 1), 'ERC20Snapshot: nonexistent id');
-    });
-
-    context('with initial snapshot', function () {
-      beforeEach(async function () {
-        this.initialSnapshotId = new BN('1');
-
-        const receipt = await this.token.$_snapshot();
-        expectEvent(receipt, 'Snapshot', { id: this.initialSnapshotId });
-      });
-
-      context('with no balance changes after the snapshot', function () {
-        it('returns the current balance for all accounts', async function () {
-          expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal(
-            initialSupply,
-          );
-          expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
-          expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
-        });
-      });
-
-      context('with balance changes after the snapshot', function () {
-        beforeEach(async function () {
-          await this.token.transfer(recipient, new BN('10'), { from: initialHolder });
-          await this.token.$_mint(other, new BN('50'));
-          await this.token.$_burn(initialHolder, new BN('20'));
-        });
-
-        it('returns the balances before the changes', async function () {
-          expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal(
-            initialSupply,
-          );
-          expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
-          expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
-        });
-
-        context('with a second snapshot after supply changes', function () {
-          beforeEach(async function () {
-            this.secondSnapshotId = new BN('2');
-
-            const receipt = await this.token.$_snapshot();
-            expectEvent(receipt, 'Snapshot', { id: this.secondSnapshotId });
-          });
-
-          it('snapshots return the balances before and after the changes', async function () {
-            expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal(
-              initialSupply,
-            );
-            expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
-            expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
-
-            expect(await this.token.balanceOfAt(initialHolder, this.secondSnapshotId)).to.be.bignumber.equal(
-              await this.token.balanceOf(initialHolder),
-            );
-            expect(await this.token.balanceOfAt(recipient, this.secondSnapshotId)).to.be.bignumber.equal(
-              await this.token.balanceOf(recipient),
-            );
-            expect(await this.token.balanceOfAt(other, this.secondSnapshotId)).to.be.bignumber.equal(
-              await this.token.balanceOf(other),
-            );
-          });
-        });
-
-        context('with multiple snapshots after supply changes', function () {
-          beforeEach(async function () {
-            this.secondSnapshotIds = ['2', '3', '4'];
-
-            for (const id of this.secondSnapshotIds) {
-              const receipt = await this.token.$_snapshot();
-              expectEvent(receipt, 'Snapshot', { id });
-            }
-          });
-
-          it('all posterior snapshots return the supply after the changes', async function () {
-            expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal(
-              initialSupply,
-            );
-            expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
-            expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
-
-            for (const id of this.secondSnapshotIds) {
-              expect(await this.token.balanceOfAt(initialHolder, id)).to.be.bignumber.equal(
-                await this.token.balanceOf(initialHolder),
-              );
-              expect(await this.token.balanceOfAt(recipient, id)).to.be.bignumber.equal(
-                await this.token.balanceOf(recipient),
-              );
-              expect(await this.token.balanceOfAt(other, id)).to.be.bignumber.equal(await this.token.balanceOf(other));
-            }
-          });
-        });
-      });
-    });
-  });
-});

+ 0 - 579
test/token/ERC20/extensions/ERC20VotesComp.test.js

@@ -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',
-          );
-        });
-      });
-    });
-  }
-});

+ 0 - 71
test/token/ERC20/utils/TokenTimelock.test.js

@@ -1,71 +0,0 @@
-const { BN, expectRevert, time } = require('@openzeppelin/test-helpers');
-
-const { expect } = require('chai');
-
-const ERC20 = artifacts.require('$ERC20');
-const TokenTimelock = artifacts.require('TokenTimelock');
-
-contract('TokenTimelock', function (accounts) {
-  const [beneficiary] = accounts;
-
-  const name = 'My Token';
-  const symbol = 'MTKN';
-
-  const amount = new BN(100);
-
-  context('with token', function () {
-    beforeEach(async function () {
-      this.token = await ERC20.new(name, symbol);
-    });
-
-    it('rejects a release time in the past', async function () {
-      const pastReleaseTime = (await time.latest()).sub(time.duration.years(1));
-      await expectRevert(
-        TokenTimelock.new(this.token.address, beneficiary, pastReleaseTime),
-        'TokenTimelock: release time is before current time',
-      );
-    });
-
-    context('once deployed', function () {
-      beforeEach(async function () {
-        this.releaseTime = (await time.latest()).add(time.duration.years(1));
-        this.timelock = await TokenTimelock.new(this.token.address, beneficiary, this.releaseTime);
-        await this.token.$_mint(this.timelock.address, amount);
-      });
-
-      it('can get state', async function () {
-        expect(await this.timelock.token()).to.equal(this.token.address);
-        expect(await this.timelock.beneficiary()).to.equal(beneficiary);
-        expect(await this.timelock.releaseTime()).to.be.bignumber.equal(this.releaseTime);
-      });
-
-      it('cannot be released before time limit', async function () {
-        await expectRevert(this.timelock.release(), 'TokenTimelock: current time is before release time');
-      });
-
-      it('cannot be released just before time limit', async function () {
-        await time.increaseTo(this.releaseTime.sub(time.duration.seconds(3)));
-        await expectRevert(this.timelock.release(), 'TokenTimelock: current time is before release time');
-      });
-
-      it('can be released just after limit', async function () {
-        await time.increaseTo(this.releaseTime.add(time.duration.seconds(1)));
-        await this.timelock.release();
-        expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount);
-      });
-
-      it('can be released after time limit', async function () {
-        await time.increaseTo(this.releaseTime.add(time.duration.years(1)));
-        await this.timelock.release();
-        expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount);
-      });
-
-      it('cannot be released twice', async function () {
-        await time.increaseTo(this.releaseTime.add(time.duration.years(1)));
-        await this.timelock.release();
-        await expectRevert(this.timelock.release(), 'TokenTimelock: no tokens to release');
-        expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount);
-      });
-    });
-  });
-});