From c9630526e24ba53d9647787588a19ffaa3dd65e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 16 Mar 2020 16:27:15 -0300 Subject: [PATCH] Draft and lifecycles directories cleanup (#2122) * Move Pausable into utils * Move Strings into utils * Move Counters into utils * Move SignedSafeMath into math * Remove ERC1046 * Make ERC20Snapshot.snapshot internal * Move ERC20Snapshot into ERC20 * Add drafts deprecation notice * Remove drafts directory * Add changelog entry * Apply suggestions from code review Co-Authored-By: Francisco Giordano Co-authored-by: Francisco Giordano --- CHANGELOG.md | 5 + contracts/drafts/ERC1046/ERC20Metadata.sol | 24 --- contracts/drafts/README.adoc | 23 --- contracts/lifecycle/README.adoc | 5 - contracts/math/README.adoc | 2 + contracts/{drafts => math}/SignedSafeMath.sol | 0 contracts/mocks/CountersImpl.sol | 2 +- contracts/mocks/ERC20MetadataMock.sol | 12 -- contracts/mocks/ERC20SnapshotMock.sol | 6 +- contracts/mocks/PausableMock.sol | 2 +- contracts/mocks/SignedSafeMathMock.sol | 2 +- contracts/mocks/StringsMock.sol | 2 +- contracts/token/ERC20/ERC20Pausable.sol | 2 +- .../{drafts => token/ERC20}/ERC20Snapshot.sol | 53 +++--- contracts/token/ERC20/README.adoc | 2 + contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/ERC721Pausable.sol | 2 +- contracts/{drafts => utils}/Counters.sol | 0 contracts/{lifecycle => utils}/Pausable.sol | 0 contracts/utils/README.adoc | 6 + contracts/{drafts => utils}/Strings.sol | 7 +- docs/modules/ROOT/pages/drafts.adoc | 19 ++ docs/modules/ROOT/pages/erc721.adoc | 2 +- test/drafts/ERC1046/ERC20Metadata.test.js | 26 --- test/drafts/TokenVesting.test.js | 172 ------------------ test/{drafts => math}/SignedSafeMath.test.js | 0 .../ERC20}/ERC20Snapshot.test.js | 0 test/{drafts => utils}/Counters.test.js | 0 test/{lifecycle => utils}/Pausable.test.js | 0 test/{drafts => utils}/Strings.test.js | 0 30 files changed, 79 insertions(+), 299 deletions(-) delete mode 100644 contracts/drafts/ERC1046/ERC20Metadata.sol delete mode 100644 contracts/drafts/README.adoc delete mode 100644 contracts/lifecycle/README.adoc rename contracts/{drafts => math}/SignedSafeMath.sol (100%) delete mode 100644 contracts/mocks/ERC20MetadataMock.sol rename contracts/{drafts => token/ERC20}/ERC20Snapshot.sol (70%) rename contracts/{drafts => utils}/Counters.sol (100%) rename contracts/{lifecycle => utils}/Pausable.sol (100%) rename contracts/{drafts => utils}/Strings.sol (71%) create mode 100644 docs/modules/ROOT/pages/drafts.adoc delete mode 100644 test/drafts/ERC1046/ERC20Metadata.test.js delete mode 100644 test/drafts/TokenVesting.test.js rename test/{drafts => math}/SignedSafeMath.test.js (100%) rename test/{drafts => token/ERC20}/ERC20Snapshot.test.js (100%) rename test/{drafts => utils}/Counters.test.js (100%) rename test/{lifecycle => utils}/Pausable.test.js (100%) rename test/{drafts => utils}/Strings.test.js (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce747e9ae24..bf376f6e360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### Breaking changes * `ECDSA`: when receiving an invalid signature, `recover` now reverts instead of returning the zero address. ([#2114](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2114)) + * `Pausable`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) + * `Strings`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) + * `Counters`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) + * `SignedSafeMath`: moved to the `math` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) + * `ERC20Snapshot`: moved to the `token/ERC20` directory. `snapshot` was changed into an `internal` function. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) * `Ownable`: moved to the `access` directory. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) * `Ownable`: removed `isOwner`. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) * `Secondary`: removed from the library, use `Ownable` instead. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) diff --git a/contracts/drafts/ERC1046/ERC20Metadata.sol b/contracts/drafts/ERC1046/ERC20Metadata.sol deleted file mode 100644 index 1a768cfc186..00000000000 --- a/contracts/drafts/ERC1046/ERC20Metadata.sol +++ /dev/null @@ -1,24 +0,0 @@ -pragma solidity ^0.6.0; - -import "../../token/ERC20/IERC20.sol"; - -/** - * @title ERC-1047 Token Metadata - * @dev See https://eips.ethereum.org/EIPS/eip-1046 - * @dev {tokenURI} must respond with a URI that implements https://eips.ethereum.org/EIPS/eip-1047 - */ -contract ERC20Metadata { - string private _tokenURI; - - constructor (string memory tokenURI_) public { - _setTokenURI(tokenURI_); - } - - function tokenURI() external view returns (string memory) { - return _tokenURI; - } - - function _setTokenURI(string memory tokenURI_) internal virtual { - _tokenURI = tokenURI_; - } -} diff --git a/contracts/drafts/README.adoc b/contracts/drafts/README.adoc deleted file mode 100644 index 19e981ae48a..00000000000 --- a/contracts/drafts/README.adoc +++ /dev/null @@ -1,23 +0,0 @@ -= Drafts - -Contracts in this category should be considered unstable. They are as thoroughly reviewed as everything else in OpenZeppelin Contracts, but we have doubts about their API so we don't commit to backwards compatibility. This means these contracts can receive breaking changes in a minor version, so you should pay special attention to the changelog when upgrading. For anything that is outside of this category you can read more about xref:ROOT:api-stability.adoc[API Stability]. - -NOTE: This page is incomplete. We're working to improve it for the next release. Stay tuned! - -== ERC 20 - -{{ERC20Migrator}} - -{{ERC20Snapshot}} - -{{TokenVesting}} - -== Miscellaneous - -{{Counters}} - -{{SignedSafeMath}} - -== ERC 1046 - -{{ERC1046}} diff --git a/contracts/lifecycle/README.adoc b/contracts/lifecycle/README.adoc deleted file mode 100644 index 523207707d0..00000000000 --- a/contracts/lifecycle/README.adoc +++ /dev/null @@ -1,5 +0,0 @@ -= Lifecycle - -== Pausable - -{{Pausable}} diff --git a/contracts/math/README.adoc b/contracts/math/README.adoc index 99789b94b83..38d1b9724d5 100644 --- a/contracts/math/README.adoc +++ b/contracts/math/README.adoc @@ -6,4 +6,6 @@ These are math-related utilities. {{SafeMath}} +{{SignedSafeMath}} + {{Math}} diff --git a/contracts/drafts/SignedSafeMath.sol b/contracts/math/SignedSafeMath.sol similarity index 100% rename from contracts/drafts/SignedSafeMath.sol rename to contracts/math/SignedSafeMath.sol diff --git a/contracts/mocks/CountersImpl.sol b/contracts/mocks/CountersImpl.sol index 2f3b008a14f..19e9eb8de74 100644 --- a/contracts/mocks/CountersImpl.sol +++ b/contracts/mocks/CountersImpl.sol @@ -1,6 +1,6 @@ pragma solidity ^0.6.0; -import "../drafts/Counters.sol"; +import "../utils/Counters.sol"; contract CountersImpl { using Counters for Counters.Counter; diff --git a/contracts/mocks/ERC20MetadataMock.sol b/contracts/mocks/ERC20MetadataMock.sol deleted file mode 100644 index f18845e11c1..00000000000 --- a/contracts/mocks/ERC20MetadataMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -pragma solidity ^0.6.0; - -import "../token/ERC20/ERC20.sol"; -import "../drafts/ERC1046/ERC20Metadata.sol"; - -contract ERC20MetadataMock is ERC20, ERC20Metadata { - constructor (string memory tokenURI) public ERC20Metadata(tokenURI) { } - - function setTokenURI(string memory tokenURI) public { - _setTokenURI(tokenURI); - } -} diff --git a/contracts/mocks/ERC20SnapshotMock.sol b/contracts/mocks/ERC20SnapshotMock.sol index 5333a964fdd..9d5c091ca75 100644 --- a/contracts/mocks/ERC20SnapshotMock.sol +++ b/contracts/mocks/ERC20SnapshotMock.sol @@ -1,6 +1,6 @@ pragma solidity ^0.6.0; -import "../drafts/ERC20Snapshot.sol"; +import "../token/ERC20/ERC20Snapshot.sol"; contract ERC20SnapshotMock is ERC20Snapshot { @@ -8,6 +8,10 @@ contract ERC20SnapshotMock is ERC20Snapshot { _mint(initialAccount, initialBalance); } + function snapshot() public { + _snapshot(); + } + function mint(address account, uint256 amount) public { _mint(account, amount); } diff --git a/contracts/mocks/PausableMock.sol b/contracts/mocks/PausableMock.sol index 2f48a1ad1ae..548b06428c5 100644 --- a/contracts/mocks/PausableMock.sol +++ b/contracts/mocks/PausableMock.sol @@ -1,6 +1,6 @@ pragma solidity ^0.6.0; -import "../lifecycle/Pausable.sol"; +import "../utils/Pausable.sol"; contract PausableMock is Pausable { bool public drasticMeasureTaken; diff --git a/contracts/mocks/SignedSafeMathMock.sol b/contracts/mocks/SignedSafeMathMock.sol index 33f705f0d23..a66fa2b6b4e 100644 --- a/contracts/mocks/SignedSafeMathMock.sol +++ b/contracts/mocks/SignedSafeMathMock.sol @@ -1,6 +1,6 @@ pragma solidity ^0.6.0; -import "../drafts/SignedSafeMath.sol"; +import "../math/SignedSafeMath.sol"; contract SignedSafeMathMock { function mul(int256 a, int256 b) public pure returns (int256) { diff --git a/contracts/mocks/StringsMock.sol b/contracts/mocks/StringsMock.sol index bc19b40c998..383278070a3 100644 --- a/contracts/mocks/StringsMock.sol +++ b/contracts/mocks/StringsMock.sol @@ -1,6 +1,6 @@ pragma solidity ^0.6.0; -import "../drafts/Strings.sol"; +import "../utils/Strings.sol"; contract StringsMock { function fromUint256(uint256 value) public pure returns (string memory) { diff --git a/contracts/token/ERC20/ERC20Pausable.sol b/contracts/token/ERC20/ERC20Pausable.sol index 61d57b80595..a29554a8641 100644 --- a/contracts/token/ERC20/ERC20Pausable.sol +++ b/contracts/token/ERC20/ERC20Pausable.sol @@ -1,7 +1,7 @@ pragma solidity ^0.6.0; import "./ERC20.sol"; -import "../../lifecycle/Pausable.sol"; +import "../../utils/Pausable.sol"; /** * @title Pausable token diff --git a/contracts/drafts/ERC20Snapshot.sol b/contracts/token/ERC20/ERC20Snapshot.sol similarity index 70% rename from contracts/drafts/ERC20Snapshot.sol rename to contracts/token/ERC20/ERC20Snapshot.sol index 9e2b1558ae7..0783308d8b9 100644 --- a/contracts/drafts/ERC20Snapshot.sol +++ b/contracts/token/ERC20/ERC20Snapshot.sol @@ -1,15 +1,12 @@ pragma solidity ^0.6.0; -import "../math/SafeMath.sol"; -import "../utils/Arrays.sol"; -import "../drafts/Counters.sol"; -import "../token/ERC20/ERC20.sol"; +import "../../math/SafeMath.sol"; +import "../../utils/Arrays.sol"; +import "../../utils/Counters.sol"; +import "./ERC20.sol"; /** - * @title ERC20 token with snapshots. - * @dev Inspired by Jordi Baylina's - * https://github.com/Giveth/minimd/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol[MiniMeToken] - * to record historical balances. + * @dev ERC20 token with snapshots. * * When a snapshot is made, the balances and total supply at the time of the snapshot are recorded for later * access. @@ -21,6 +18,9 @@ import "../token/ERC20/ERC20.sol"; * @author Validity Labs AG */ contract ERC20Snapshot is ERC20 { + // Inspired by Jordi Baylina's MiniMeToken to record historical balances: + // https://github.com/Giveth/minimd/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol + using SafeMath for uint256; using Arrays for uint256[]; using Counters for Counters.Counter; @@ -40,10 +40,12 @@ contract ERC20Snapshot is ERC20 { event Snapshot(uint256 id); - // Creates a new snapshot id. Balances are only stored in snapshots on demand: unless a snapshot was taken, a - // balance change will not be recorded. This means the extra added cost of storing snapshotted balances is only paid - // when required, but is also flexible enough that it allows for e.g. daily snapshots. - function snapshot() public virtual returns (uint256) { + /** + * @dev Creates a new snapshot id. Balances are only stored in snapshots on demand: unless a snapshot was taken, a + * balance change will not be recorded. This means the extra added cost of storing snapshotted balances is only paid + * when required, but is also flexible enough that it allows for e.g. daily snapshots. + */ + function _snapshot() internal virtual returns (uint256) { _currentSnapshotId.increment(); uint256 currentId = _currentSnapshotId.current(); @@ -87,19 +89,6 @@ contract ERC20Snapshot is ERC20 { super._burn(account, value); } - // 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. function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256) { @@ -107,6 +96,20 @@ contract ERC20Snapshot is ERC20 { // solhint-disable-next-line max-line-length require(snapshotId <= _currentSnapshotId.current(), "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) { diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index 4524a2d086e..9b46537d723 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -43,6 +43,8 @@ NOTE: This page is incomplete. We're working to improve it for the next release. {{ERC20Capped}} +{{ERC20Snapshot}} + == Utilities {{SafeERC20}} diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index b8470fa9e4e..e2269948680 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -5,7 +5,7 @@ import "./IERC721.sol"; import "./IERC721Receiver.sol"; import "../../math/SafeMath.sol"; import "../../utils/Address.sol"; -import "../../drafts/Counters.sol"; +import "../../utils/Counters.sol"; import "../../introspection/ERC165.sol"; /** diff --git a/contracts/token/ERC721/ERC721Pausable.sol b/contracts/token/ERC721/ERC721Pausable.sol index efe9309beb5..3d422f89d53 100644 --- a/contracts/token/ERC721/ERC721Pausable.sol +++ b/contracts/token/ERC721/ERC721Pausable.sol @@ -1,7 +1,7 @@ pragma solidity ^0.6.0; import "./ERC721.sol"; -import "../../lifecycle/Pausable.sol"; +import "../../utils/Pausable.sol"; /** * @title ERC721 Non-Fungible Pausable token diff --git a/contracts/drafts/Counters.sol b/contracts/utils/Counters.sol similarity index 100% rename from contracts/drafts/Counters.sol rename to contracts/utils/Counters.sol diff --git a/contracts/lifecycle/Pausable.sol b/contracts/utils/Pausable.sol similarity index 100% rename from contracts/lifecycle/Pausable.sol rename to contracts/utils/Pausable.sol diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index e994449ae88..9e56e5b074a 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -10,8 +10,14 @@ Miscellaneous contracts containing utility functions, often related to working w {{Arrays}} +{{Counters}} + +{{Strings}} + {{EnumerableSet}} {{Create2}} {{ReentrancyGuard}} + +{{Pausable}} diff --git a/contracts/drafts/Strings.sol b/contracts/utils/Strings.sol similarity index 71% rename from contracts/drafts/Strings.sol rename to contracts/utils/Strings.sol index a441eaa6029..6977c809f7e 100644 --- a/contracts/drafts/Strings.sol +++ b/contracts/utils/Strings.sol @@ -6,11 +6,12 @@ pragma solidity ^0.6.0; */ library Strings { /** - * @dev Converts a `uint256` to a `string`. - * via OraclizeAPI - MIT licence - * https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + * @dev Converts a `uint256` to its ASCII `string` representation. */ function fromUint256(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + if (value == 0) { return "0"; } diff --git a/docs/modules/ROOT/pages/drafts.adoc b/docs/modules/ROOT/pages/drafts.adoc new file mode 100644 index 00000000000..d1793449bdc --- /dev/null +++ b/docs/modules/ROOT/pages/drafts.adoc @@ -0,0 +1,19 @@ += Drafts + +All draft contracts were either moved into a different directory or removed from the OpenZeppelin Contracts library on the https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256[v3.0.0 release]. + +* `ERC20Migrator`: removed. +* xref:api:token/ERC20.adoc#ERC20Snapshot[`ERC20Snapshot`]: moved to `token/ERC20`. +* `ERC20Detailed` and `ERC1046`: removed. +* `TokenVesting`: removed. Pending a replacement that is being discussed in https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1214[`#1214`]. +* xref:api:utils.adoc#Counters[`Counters`]: moved to xref:api:utils.adoc[`utils`]. +* xref:api:utils.adoc#Strings[`Strings`]: moved to xref:api:utils.adoc[`utils`]. +* xref:api:utils.adoc#SignedSafeMath[`SignedSafeMath`]: moved to xref:api:utils.adoc[`utils`]. + +Removed contracts are still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: + +```console +$ npm install @openzeppelin/contracts@v2.5 +``` + +Refer to the xref:2.x@contracts:api:utils.adoc[v2.x documentation] when working with them. diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 9708710fd49..c88a248af2d 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -15,7 +15,7 @@ Here's what a contract for tokenized items might look like: pragma solidity ^0.5.0; import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol"; -import "@openzeppelin/contracts/drafts/Counters.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; contract GameItem is ERC721Full { using Counters for Counters.Counter; diff --git a/test/drafts/ERC1046/ERC20Metadata.test.js b/test/drafts/ERC1046/ERC20Metadata.test.js deleted file mode 100644 index 625bc65873d..00000000000 --- a/test/drafts/ERC1046/ERC20Metadata.test.js +++ /dev/null @@ -1,26 +0,0 @@ -const { contract } = require('@openzeppelin/test-environment'); -require('@openzeppelin/test-helpers'); - -const ERC20MetadataMock = contract.fromArtifact('ERC20MetadataMock'); - -const { expect } = require('chai'); - -const metadataURI = 'https://example.com'; - -describe('ERC20Metadata', function () { - beforeEach(async function () { - this.token = await ERC20MetadataMock.new(metadataURI); - }); - - it('responds with the metadata', async function () { - expect(await this.token.tokenURI()).to.equal(metadataURI); - }); - - describe('setTokenURI', function () { - it('changes the original URI', async function () { - const newMetadataURI = 'https://betterexample.com'; - await this.token.setTokenURI(newMetadataURI); - expect(await this.token.tokenURI()).to.equal(newMetadataURI); - }); - }); -}); diff --git a/test/drafts/TokenVesting.test.js b/test/drafts/TokenVesting.test.js deleted file mode 100644 index 3eb2d7c51f2..00000000000 --- a/test/drafts/TokenVesting.test.js +++ /dev/null @@ -1,172 +0,0 @@ -const { accounts, contract } = require('@openzeppelin/test-environment'); - -const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); - -const ERC20Mock = contract.fromArtifact('ERC20Mock'); -const TokenVesting = contract.fromArtifact('TokenVesting'); - -describe('TokenVesting', function () { - const [ owner, beneficiary ] = accounts; - - const amount = new BN('1000'); - - beforeEach(async function () { - // +1 minute so it starts after contract instantiation - this.start = (await time.latest()).add(time.duration.minutes(1)); - this.cliffDuration = time.duration.years(1); - this.duration = time.duration.years(2); - }); - - it('reverts with a duration shorter than the cliff', async function () { - const cliffDuration = this.duration; - const duration = this.cliffDuration; - - expect(cliffDuration).to.be.bignumber.that.is.at.least(duration); - - await expectRevert( - TokenVesting.new(beneficiary, this.start, cliffDuration, duration, true, { from: owner }), - 'TokenVesting: cliff is longer than duration' - ); - }); - - it('reverts with a null beneficiary', async function () { - await expectRevert( - TokenVesting.new(ZERO_ADDRESS, this.start, this.cliffDuration, this.duration, true, { from: owner }), - 'TokenVesting: beneficiary is the zero address' - ); - }); - - it('reverts with a null duration', async function () { - // cliffDuration should also be 0, since the duration must be larger than the cliff - await expectRevert( - TokenVesting.new(beneficiary, this.start, 0, 0, true, { from: owner }), 'TokenVesting: duration is 0' - ); - }); - - it('reverts if the end time is in the past', async function () { - const now = await time.latest(); - - this.start = now.sub(this.duration).sub(time.duration.minutes(1)); - await expectRevert( - TokenVesting.new(beneficiary, this.start, this.cliffDuration, this.duration, true, { from: owner }), - 'TokenVesting: final time is before current time' - ); - }); - - context('once deployed', function () { - beforeEach(async function () { - this.vesting = await TokenVesting.new( - beneficiary, this.start, this.cliffDuration, this.duration, true, { from: owner }); - - this.token = await ERC20Mock.new(this.vesting.address, amount); - }); - - it('can get state', async function () { - expect(await this.vesting.beneficiary()).to.equal(beneficiary); - expect(await this.vesting.cliff()).to.be.bignumber.equal(this.start.add(this.cliffDuration)); - expect(await this.vesting.start()).to.be.bignumber.equal(this.start); - expect(await this.vesting.duration()).to.be.bignumber.equal(this.duration); - expect(await this.vesting.revocable()).to.be.equal(true); - }); - - it('cannot be released before cliff', async function () { - await expectRevert(this.vesting.release(this.token.address), - 'TokenVesting: no tokens are due' - ); - }); - - it('can be released after cliff', async function () { - await time.increaseTo(this.start.add(this.cliffDuration).add(time.duration.weeks(1))); - const { logs } = await this.vesting.release(this.token.address); - expectEvent.inLogs(logs, 'TokensReleased', { - token: this.token.address, - amount: await this.token.balanceOf(beneficiary), - }); - }); - - it('should release proper amount after cliff', async function () { - await time.increaseTo(this.start.add(this.cliffDuration)); - - await this.vesting.release(this.token.address); - const releaseTime = await time.latest(); - - const releasedAmount = amount.mul(releaseTime.sub(this.start)).div(this.duration); - expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(releasedAmount); - expect(await this.vesting.released(this.token.address)).to.be.bignumber.equal(releasedAmount); - }); - - it('should linearly release tokens during vesting period', async function () { - const vestingPeriod = this.duration.sub(this.cliffDuration); - const checkpoints = 4; - - for (let i = 1; i <= checkpoints; i++) { - const now = this.start.add(this.cliffDuration).add((vestingPeriod.muln(i).divn(checkpoints))); - await time.increaseTo(now); - - await this.vesting.release(this.token.address); - const expectedVesting = amount.mul(now.sub(this.start)).div(this.duration); - expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(expectedVesting); - expect(await this.vesting.released(this.token.address)).to.be.bignumber.equal(expectedVesting); - } - }); - - it('should have released all after end', async function () { - await time.increaseTo(this.start.add(this.duration)); - await this.vesting.release(this.token.address); - expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount); - expect(await this.vesting.released(this.token.address)).to.be.bignumber.equal(amount); - }); - - it('should be revoked by owner if revocable is set', async function () { - const { logs } = await this.vesting.revoke(this.token.address, { from: owner }); - expectEvent.inLogs(logs, 'TokenVestingRevoked', { token: this.token.address }); - expect(await this.vesting.revoked(this.token.address)).to.equal(true); - }); - - it('should fail to be revoked by owner if revocable not set', async function () { - const vesting = await TokenVesting.new( - beneficiary, this.start, this.cliffDuration, this.duration, false, { from: owner } - ); - - await expectRevert(vesting.revoke(this.token.address, { from: owner }), - 'TokenVesting: cannot revoke' - ); - }); - - it('should return the non-vested tokens when revoked by owner', async function () { - await time.increaseTo(this.start.add(this.cliffDuration).add(time.duration.weeks(12))); - - const vested = vestedAmount(amount, await time.latest(), this.start, this.cliffDuration, this.duration); - - await this.vesting.revoke(this.token.address, { from: owner }); - - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(amount.sub(vested)); - }); - - it('should keep the vested tokens when revoked by owner', async function () { - await time.increaseTo(this.start.add(this.cliffDuration).add(time.duration.weeks(12))); - - const vestedPre = vestedAmount(amount, await time.latest(), this.start, this.cliffDuration, this.duration); - - await this.vesting.revoke(this.token.address, { from: owner }); - - const vestedPost = vestedAmount(amount, await time.latest(), this.start, this.cliffDuration, this.duration); - - expect(vestedPre).to.be.bignumber.equal(vestedPost); - }); - - it('should fail to be revoked a second time', async function () { - await this.vesting.revoke(this.token.address, { from: owner }); - await expectRevert(this.vesting.revoke(this.token.address, { from: owner }), - 'TokenVesting: token already revoked' - ); - }); - - function vestedAmount (total, now, start, cliffDuration, duration) { - return (now.lt(start.add(cliffDuration))) ? new BN(0) : total.mul((now.sub(start))).div(duration); - } - }); -}); diff --git a/test/drafts/SignedSafeMath.test.js b/test/math/SignedSafeMath.test.js similarity index 100% rename from test/drafts/SignedSafeMath.test.js rename to test/math/SignedSafeMath.test.js diff --git a/test/drafts/ERC20Snapshot.test.js b/test/token/ERC20/ERC20Snapshot.test.js similarity index 100% rename from test/drafts/ERC20Snapshot.test.js rename to test/token/ERC20/ERC20Snapshot.test.js diff --git a/test/drafts/Counters.test.js b/test/utils/Counters.test.js similarity index 100% rename from test/drafts/Counters.test.js rename to test/utils/Counters.test.js diff --git a/test/lifecycle/Pausable.test.js b/test/utils/Pausable.test.js similarity index 100% rename from test/lifecycle/Pausable.test.js rename to test/utils/Pausable.test.js diff --git a/test/drafts/Strings.test.js b/test/utils/Strings.test.js similarity index 100% rename from test/drafts/Strings.test.js rename to test/utils/Strings.test.js