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 <frangio.1@gmail.com> Co-authored-by: Francisco Giordano <frangio.1@gmail.com>pull/2112/head^2
parent
8176a901a9
commit
c9630526e2
@ -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_; |
||||
} |
||||
} |
@ -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}} |
@ -1,5 +0,0 @@ |
||||
= Lifecycle |
||||
|
||||
== Pausable |
||||
|
||||
{{Pausable}} |
@ -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); |
||||
} |
||||
} |
@ -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. |
@ -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); |
||||
}); |
||||
}); |
||||
}); |
@ -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); |
||||
} |
||||
}); |
||||
}); |
Loading…
Reference in new issue