Unverified Commit 8d11dcc0 by Nicolás Venturo Committed by GitHub

Increase testing coverage (#1195)

* Added non-target bounty test

* Increased ERC721 testing coverage.

* Addressed review comments.

* fix linter error

* Fixed linter error

* Removed unnecessary bouncer require

* Improved Crowdsale tests.

* Added missing SuperUser test.

* Improved payment tests.

* Improved token tests.

* Fixed ERC721 test.

* Reviewed phrasing.
parent d51e3875
...@@ -82,7 +82,6 @@ contract SignatureBouncer is Ownable, RBAC { ...@@ -82,7 +82,6 @@ contract SignatureBouncer is Ownable, RBAC {
public public
onlyOwner onlyOwner
{ {
require(_bouncer != address(0));
removeRole(_bouncer, ROLE_BOUNCER); removeRole(_bouncer, ROLE_BOUNCER);
} }
......
...@@ -131,7 +131,6 @@ contract ERC721BasicToken is SupportsInterfaceWithLookup, ERC721Basic { ...@@ -131,7 +131,6 @@ contract ERC721BasicToken is SupportsInterfaceWithLookup, ERC721Basic {
public public
{ {
require(isApprovedOrOwner(msg.sender, _tokenId)); require(isApprovedOrOwner(msg.sender, _tokenId));
require(_from != address(0));
require(_to != address(0)); require(_to != address(0));
clearApproval(_from, _tokenId); clearApproval(_from, _tokenId);
......
...@@ -17,7 +17,7 @@ const sendReward = async (from, to, value) => ethSendTransaction({ ...@@ -17,7 +17,7 @@ const sendReward = async (from, to, value) => ethSendTransaction({
const reward = new web3.BigNumber(web3.toWei(1, 'ether')); const reward = new web3.BigNumber(web3.toWei(1, 'ether'));
contract('Bounty', function ([_, owner, researcher]) { contract('Bounty', function ([_, owner, researcher, nonTarget]) {
context('against secure contract', function () { context('against secure contract', function () {
beforeEach(async function () { beforeEach(async function () {
this.bounty = await SecureTargetBounty.new({ from: owner }); this.bounty = await SecureTargetBounty.new({ from: owner });
...@@ -82,6 +82,12 @@ contract('Bounty', function ([_, owner, researcher]) { ...@@ -82,6 +82,12 @@ contract('Bounty', function ([_, owner, researcher]) {
researcherCurrBalance.sub(researcherPrevBalance).should.be.bignumber.equal(reward.sub(gasCost)); researcherCurrBalance.sub(researcherPrevBalance).should.be.bignumber.equal(reward.sub(gasCost));
}); });
it('cannot claim reward from non-target', async function () {
await assertRevert(
this.bounty.claim(nonTarget, { from: researcher })
);
});
context('reward claimed', function () { context('reward claimed', function () {
beforeEach(async function () { beforeEach(async function () {
await this.bounty.claim(this.targetAddress, { from: researcher }); await this.bounty.claim(this.targetAddress, { from: researcher });
......
const { assertRevert } = require('../helpers/assertRevert');
const { ether } = require('../helpers/ether'); const { ether } = require('../helpers/ether');
const { ethGetBalance } = require('../helpers/web3'); const { ethGetBalance } = require('../helpers/web3');
...@@ -15,67 +16,118 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) { ...@@ -15,67 +16,118 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
const value = ether(42); const value = ether(42);
const tokenSupply = new BigNumber('1e22'); const tokenSupply = new BigNumber('1e22');
const expectedTokenAmount = rate.mul(value); const expectedTokenAmount = rate.mul(value);
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
beforeEach(async function () { it('requires a non-null token', async function () {
this.token = await SimpleToken.new(); await assertRevert(
this.crowdsale = await Crowdsale.new(rate, wallet, this.token.address); Crowdsale.new(rate, wallet, ZERO_ADDRESS)
await this.token.transfer(this.crowdsale.address, tokenSupply); );
}); });
describe('accepting payments', function () { context('with token', async function () {
it('should accept payments', async function () { beforeEach(async function () {
await this.crowdsale.send(value); this.token = await SimpleToken.new();
await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
}); });
});
describe('high-level purchase', function () { it('requires a non-zero rate', async function () {
it('should log purchase', async function () { await assertRevert(
const { logs } = await this.crowdsale.sendTransaction({ value: value, from: investor }); Crowdsale.new(0, wallet, this.token.address)
const event = logs.find(e => e.event === 'TokenPurchase'); );
should.exist(event);
event.args.purchaser.should.eq(investor);
event.args.beneficiary.should.eq(investor);
event.args.value.should.be.bignumber.equal(value);
event.args.amount.should.be.bignumber.equal(expectedTokenAmount);
}); });
it('should assign tokens to sender', async function () { it('requires a non-null wallet', async function () {
await this.crowdsale.sendTransaction({ value: value, from: investor }); await assertRevert(
const balance = await this.token.balanceOf(investor); Crowdsale.new(rate, ZERO_ADDRESS, this.token.address)
balance.should.be.bignumber.equal(expectedTokenAmount); );
}); });
it('should forward funds to wallet', async function () { context('once deployed', async function () {
const pre = await ethGetBalance(wallet); beforeEach(async function () {
await this.crowdsale.sendTransaction({ value, from: investor }); this.crowdsale = await Crowdsale.new(rate, wallet, this.token.address);
const post = await ethGetBalance(wallet); await this.token.transfer(this.crowdsale.address, tokenSupply);
post.minus(pre).should.be.bignumber.equal(value); });
});
});
describe('low-level purchase', function () { describe('accepting payments', function () {
it('should log purchase', async function () { describe('bare payments', function () {
const { logs } = await this.crowdsale.buyTokens(investor, { value: value, from: purchaser }); it('should accept payments', async function () {
const event = logs.find(e => e.event === 'TokenPurchase'); await this.crowdsale.send(value, { from: purchaser });
should.exist(event); });
event.args.purchaser.should.eq(purchaser);
event.args.beneficiary.should.eq(investor);
event.args.value.should.be.bignumber.equal(value);
event.args.amount.should.be.bignumber.equal(expectedTokenAmount);
});
it('should assign tokens to beneficiary', async function () { it('reverts on zero-valued payments', async function () {
await this.crowdsale.buyTokens(investor, { value, from: purchaser }); await assertRevert(
const balance = await this.token.balanceOf(investor); this.crowdsale.send(0, { from: purchaser })
balance.should.be.bignumber.equal(expectedTokenAmount); );
}); });
});
describe('buyTokens', function () {
it('should accept payments', async function () {
await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
});
it('reverts on zero-valued payments', async function () {
await assertRevert(
this.crowdsale.buyTokens(investor, { value: 0, from: purchaser })
);
});
it('requires a non-null beneficiary', async function () {
await assertRevert(
this.crowdsale.buyTokens(ZERO_ADDRESS, { value: value, from: purchaser })
);
});
});
});
describe('high-level purchase', function () {
it('should log purchase', async function () {
const { logs } = await this.crowdsale.sendTransaction({ value: value, from: investor });
const event = logs.find(e => e.event === 'TokenPurchase');
should.exist(event);
event.args.purchaser.should.eq(investor);
event.args.beneficiary.should.eq(investor);
event.args.value.should.be.bignumber.equal(value);
event.args.amount.should.be.bignumber.equal(expectedTokenAmount);
});
it('should assign tokens to sender', async function () {
await this.crowdsale.sendTransaction({ value: value, from: investor });
const balance = await this.token.balanceOf(investor);
balance.should.be.bignumber.equal(expectedTokenAmount);
});
it('should forward funds to wallet', async function () {
const pre = await ethGetBalance(wallet);
await this.crowdsale.sendTransaction({ value, from: investor });
const post = await ethGetBalance(wallet);
post.minus(pre).should.be.bignumber.equal(value);
});
});
describe('low-level purchase', function () {
it('should log purchase', async function () {
const { logs } = await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
const event = logs.find(e => e.event === 'TokenPurchase');
should.exist(event);
event.args.purchaser.should.eq(purchaser);
event.args.beneficiary.should.eq(investor);
event.args.value.should.be.bignumber.equal(value);
event.args.amount.should.be.bignumber.equal(expectedTokenAmount);
});
it('should assign tokens to beneficiary', async function () {
await this.crowdsale.buyTokens(investor, { value, from: purchaser });
const balance = await this.token.balanceOf(investor);
balance.should.be.bignumber.equal(expectedTokenAmount);
});
it('should forward funds to wallet', async function () { it('should forward funds to wallet', async function () {
const pre = await ethGetBalance(wallet); const pre = await ethGetBalance(wallet);
await this.crowdsale.buyTokens(investor, { value, from: purchaser }); await this.crowdsale.buyTokens(investor, { value, from: purchaser });
const post = await ethGetBalance(wallet); const post = await ethGetBalance(wallet);
post.minus(pre).should.be.bignumber.equal(value); post.minus(pre).should.be.bignumber.equal(value);
});
});
}); });
}); });
}); });
...@@ -7,6 +7,8 @@ require('chai') ...@@ -7,6 +7,8 @@ require('chai')
.should(); .should();
contract('Superuser', function ([_, firstOwner, newSuperuser, newOwner, anyone]) { contract('Superuser', function ([_, firstOwner, newSuperuser, newOwner, anyone]) {
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
beforeEach(async function () { beforeEach(async function () {
this.superuser = await Superuser.new({ from: firstOwner }); this.superuser = await Superuser.new({ from: firstOwner });
}); });
...@@ -27,6 +29,12 @@ contract('Superuser', function ([_, firstOwner, newSuperuser, newOwner, anyone]) ...@@ -27,6 +29,12 @@ contract('Superuser', function ([_, firstOwner, newSuperuser, newOwner, anyone])
newSuperuserIsSuperuser.should.be.equal(true); newSuperuserIsSuperuser.should.be.equal(true);
}); });
it('should prevent changing to a null superuser', async function () {
await expectThrow(
this.superuser.transferSuperuser(ZERO_ADDRESS, { from: firstOwner })
);
});
it('should change owner after the superuser transfers the ownership', async function () { it('should change owner after the superuser transfers the ownership', async function () {
await this.superuser.transferSuperuser(newSuperuser, { from: firstOwner }); await this.superuser.transferSuperuser(newSuperuser, { from: firstOwner });
......
...@@ -14,93 +14,118 @@ const RefundEscrow = artifacts.require('RefundEscrow'); ...@@ -14,93 +14,118 @@ const RefundEscrow = artifacts.require('RefundEscrow');
contract('RefundEscrow', function ([_, owner, beneficiary, refundee1, refundee2]) { contract('RefundEscrow', function ([_, owner, beneficiary, refundee1, refundee2]) {
const amount = web3.toWei(54.0, 'ether'); const amount = web3.toWei(54.0, 'ether');
const refundees = [refundee1, refundee2]; const refundees = [refundee1, refundee2];
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
beforeEach(async function () { it('requires a non-null beneficiary', async function () {
this.escrow = await RefundEscrow.new(beneficiary, { from: owner }); await expectThrow(
RefundEscrow.new(ZERO_ADDRESS, { from: owner })
);
}); });
context('active state', function () { context('once deployed', function () {
it('accepts deposits', async function () { beforeEach(async function () {
await this.escrow.deposit(refundee1, { from: owner, value: amount }); this.escrow = await RefundEscrow.new(beneficiary, { from: owner });
const deposit = await this.escrow.depositsOf(refundee1);
deposit.should.be.bignumber.equal(amount);
}); });
it('does not refund refundees', async function () { context('active state', function () {
await this.escrow.deposit(refundee1, { from: owner, value: amount }); it('accepts deposits', async function () {
await expectThrow(this.escrow.withdraw(refundee1), EVMRevert); await this.escrow.deposit(refundee1, { from: owner, value: amount });
const deposit = await this.escrow.depositsOf(refundee1);
deposit.should.be.bignumber.equal(amount);
});
it('does not refund refundees', async function () {
await this.escrow.deposit(refundee1, { from: owner, value: amount });
await expectThrow(this.escrow.withdraw(refundee1), EVMRevert);
});
it('does not allow beneficiary withdrawal', async function () {
await this.escrow.deposit(refundee1, { from: owner, value: amount });
await expectThrow(this.escrow.beneficiaryWithdraw(), EVMRevert);
});
}); });
it('does not allow beneficiary withdrawal', async function () { it('only owner can enter closed state', async function () {
await this.escrow.deposit(refundee1, { from: owner, value: amount }); await expectThrow(this.escrow.close({ from: beneficiary }), EVMRevert);
await expectThrow(this.escrow.beneficiaryWithdraw(), EVMRevert);
const receipt = await this.escrow.close({ from: owner });
expectEvent.inLogs(receipt.logs, 'Closed');
}); });
});
it('only owner can enter closed state', async function () { context('closed state', function () {
await expectThrow(this.escrow.close({ from: beneficiary }), EVMRevert); beforeEach(async function () {
await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount })));
const receipt = await this.escrow.close({ from: owner }); await this.escrow.close({ from: owner });
});
expectEvent.inLogs(receipt.logs, 'Closed'); it('rejects deposits', async function () {
}); await expectThrow(this.escrow.deposit(refundee1, { from: owner, value: amount }), EVMRevert);
});
context('closed state', function () { it('does not refund refundees', async function () {
beforeEach(async function () { await expectThrow(this.escrow.withdraw(refundee1), EVMRevert);
await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount }))); });
await this.escrow.close({ from: owner }); it('allows beneficiary withdrawal', async function () {
}); const beneficiaryInitialBalance = await ethGetBalance(beneficiary);
await this.escrow.beneficiaryWithdraw();
const beneficiaryFinalBalance = await ethGetBalance(beneficiary);
it('rejects deposits', async function () { beneficiaryFinalBalance.sub(beneficiaryInitialBalance).should.be.bignumber.equal(amount * refundees.length);
await expectThrow(this.escrow.deposit(refundee1, { from: owner, value: amount }), EVMRevert); });
});
it('prevents entering the refund state', async function () {
await expectThrow(this.escrow.enableRefunds({ from: owner }), EVMRevert);
});
it('does not refund refundees', async function () { it('prevents re-entering the closed state', async function () {
await expectThrow(this.escrow.withdraw(refundee1), EVMRevert); await expectThrow(this.escrow.close({ from: owner }), EVMRevert);
});
}); });
it('allows beneficiary withdrawal', async function () { it('only owner can enter refund state', async function () {
const beneficiaryInitialBalance = await ethGetBalance(beneficiary); await expectThrow(this.escrow.enableRefunds({ from: beneficiary }), EVMRevert);
await this.escrow.beneficiaryWithdraw();
const beneficiaryFinalBalance = await ethGetBalance(beneficiary);
beneficiaryFinalBalance.sub(beneficiaryInitialBalance).should.be.bignumber.equal(amount * refundees.length); const receipt = await this.escrow.enableRefunds({ from: owner });
});
});
it('only owner can enter refund state', async function () { expectEvent.inLogs(receipt.logs, 'RefundsEnabled');
await expectThrow(this.escrow.enableRefunds({ from: beneficiary }), EVMRevert); });
const receipt = await this.escrow.enableRefunds({ from: owner }); context('refund state', function () {
beforeEach(async function () {
await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount })));
expectEvent.inLogs(receipt.logs, 'RefundsEnabled'); await this.escrow.enableRefunds({ from: owner });
}); });
context('refund state', function () { it('rejects deposits', async function () {
beforeEach(async function () { await expectThrow(this.escrow.deposit(refundee1, { from: owner, value: amount }), EVMRevert);
await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount }))); });
await this.escrow.enableRefunds({ from: owner }); it('refunds refundees', async function () {
}); for (const refundee of [refundee1, refundee2]) {
const refundeeInitialBalance = await ethGetBalance(refundee);
await this.escrow.withdraw(refundee, { from: owner });
const refundeeFinalBalance = await ethGetBalance(refundee);
it('rejects deposits', async function () { refundeeFinalBalance.sub(refundeeInitialBalance).should.be.bignumber.equal(amount);
await expectThrow(this.escrow.deposit(refundee1, { from: owner, value: amount }), EVMRevert); }
}); });
it('refunds refundees', async function () { it('does not allow beneficiary withdrawal', async function () {
for (const refundee of [refundee1, refundee2]) { await expectThrow(this.escrow.beneficiaryWithdraw(), EVMRevert);
const refundeeInitialBalance = await ethGetBalance(refundee); });
await this.escrow.withdraw(refundee, { from: owner });
const refundeeFinalBalance = await ethGetBalance(refundee);
refundeeFinalBalance.sub(refundeeInitialBalance).should.be.bignumber.equal(amount); it('prevents entering the closed state', async function () {
} await expectThrow(this.escrow.close({ from: owner }), EVMRevert);
}); });
it('does not allow beneficiary withdrawal', async function () { it('prevents re-entering the refund state', async function () {
await expectThrow(this.escrow.beneficiaryWithdraw(), EVMRevert); await expectThrow(this.escrow.enableRefunds({ from: owner }), EVMRevert);
});
}); });
}); });
}); });
...@@ -12,6 +12,7 @@ const SplitPayment = artifacts.require('SplitPayment'); ...@@ -12,6 +12,7 @@ const SplitPayment = artifacts.require('SplitPayment');
contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1, payer1]) { contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1, payer1]) {
const amount = web3.toWei(1.0, 'ether'); const amount = web3.toWei(1.0, 'ether');
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
it('cannot be created with no payees', async function () { it('cannot be created with no payees', async function () {
await expectThrow(SplitPayment.new([], []), EVMRevert); await expectThrow(SplitPayment.new([], []), EVMRevert);
...@@ -25,6 +26,18 @@ contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1, ...@@ -25,6 +26,18 @@ contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1,
await expectThrow(SplitPayment.new([payee1, payee2], [20, 30, 40]), EVMRevert); await expectThrow(SplitPayment.new([payee1, payee2], [20, 30, 40]), EVMRevert);
}); });
it('requires non-null payees', async function () {
await expectThrow(SplitPayment.new([payee1, ZERO_ADDRESS], [20, 30]), EVMRevert);
});
it('requires non-zero shares', async function () {
await expectThrow(SplitPayment.new([payee1, payee2], [20, 0]), EVMRevert);
});
it('rejects repeated payees', async function () {
await expectThrow(SplitPayment.new([payee1, payee1], [20, 0]), EVMRevert);
});
context('once deployed', function () { context('once deployed', function () {
beforeEach(async function () { beforeEach(async function () {
this.payees = [payee1, payee2, payee3]; this.payees = [payee1, payee2, payee3];
......
const { assertRevert } = require('../../helpers/assertRevert');
const { ether } = require('../../helpers/ether'); const { ether } = require('../../helpers/ether');
const { shouldBehaveLikeMintableToken } = require('./MintableToken.behavior'); const { shouldBehaveLikeMintableToken } = require('./MintableToken.behavior');
const { shouldBehaveLikeCappedToken } = require('./CappedToken.behavior'); const { shouldBehaveLikeCappedToken } = require('./CappedToken.behavior');
...@@ -7,10 +8,18 @@ const CappedToken = artifacts.require('CappedToken'); ...@@ -7,10 +8,18 @@ const CappedToken = artifacts.require('CappedToken');
contract('Capped', function ([_, owner, ...otherAccounts]) { contract('Capped', function ([_, owner, ...otherAccounts]) {
const cap = ether(1000); const cap = ether(1000);
beforeEach(async function () { it('requires a non-zero cap', async function () {
this.token = await CappedToken.new(cap, { from: owner }); await assertRevert(
CappedToken.new(0, { from: owner })
);
}); });
shouldBehaveLikeCappedToken(owner, otherAccounts, cap); context('once deployed', async function () {
shouldBehaveLikeMintableToken(owner, owner, otherAccounts); beforeEach(async function () {
this.token = await CappedToken.new(cap, { from: owner });
});
shouldBehaveLikeCappedToken(owner, otherAccounts, cap);
shouldBehaveLikeMintableToken(owner, owner, otherAccounts);
});
}); });
...@@ -14,41 +14,55 @@ const TokenTimelock = artifacts.require('TokenTimelock'); ...@@ -14,41 +14,55 @@ const TokenTimelock = artifacts.require('TokenTimelock');
contract('TokenTimelock', function ([_, owner, beneficiary]) { contract('TokenTimelock', function ([_, owner, beneficiary]) {
const amount = new BigNumber(100); const amount = new BigNumber(100);
beforeEach(async function () { context('with token', function () {
this.token = await MintableToken.new({ from: owner }); beforeEach(async function () {
this.releaseTime = (await latestTime()) + duration.years(1); this.token = await MintableToken.new({ from: owner });
this.timelock = await TokenTimelock.new(this.token.address, beneficiary, this.releaseTime); });
await this.token.mint(this.timelock.address, amount, { from: owner });
});
it('cannot be released before time limit', async function () { it('rejects a release time in the past', async function () {
await expectThrow(this.timelock.release()); const pastReleaseTime = (await latestTime()) - duration.years(1);
}); await expectThrow(
TokenTimelock.new(this.token.address, beneficiary, pastReleaseTime)
);
});
it('cannot be released just before time limit', async function () { context('once deployed', function () {
await increaseTimeTo(this.releaseTime - duration.seconds(3)); beforeEach(async function () {
await expectThrow(this.timelock.release()); this.releaseTime = (await latestTime()) + duration.years(1);
}); this.timelock = await TokenTimelock.new(this.token.address, beneficiary, this.releaseTime);
await this.token.mint(this.timelock.address, amount, { from: owner });
});
it('can be released just after limit', async function () { it('cannot be released before time limit', async function () {
await increaseTimeTo(this.releaseTime + duration.seconds(1)); await expectThrow(this.timelock.release());
await this.timelock.release(); });
const balance = await this.token.balanceOf(beneficiary);
balance.should.be.bignumber.equal(amount);
});
it('can be released after time limit', async function () { it('cannot be released just before time limit', async function () {
await increaseTimeTo(this.releaseTime + duration.years(1)); await increaseTimeTo(this.releaseTime - duration.seconds(3));
await this.timelock.release(); await expectThrow(this.timelock.release());
const balance = await this.token.balanceOf(beneficiary); });
balance.should.be.bignumber.equal(amount);
}); it('can be released just after limit', async function () {
await increaseTimeTo(this.releaseTime + duration.seconds(1));
await this.timelock.release();
const balance = await this.token.balanceOf(beneficiary);
balance.should.be.bignumber.equal(amount);
});
it('can be released after time limit', async function () {
await increaseTimeTo(this.releaseTime + duration.years(1));
await this.timelock.release();
const balance = await this.token.balanceOf(beneficiary);
balance.should.be.bignumber.equal(amount);
});
it('cannot be released twice', async function () { it('cannot be released twice', async function () {
await increaseTimeTo(this.releaseTime + duration.years(1)); await increaseTimeTo(this.releaseTime + duration.years(1));
await this.timelock.release(); await this.timelock.release();
await expectThrow(this.timelock.release()); await expectThrow(this.timelock.release());
const balance = await this.token.balanceOf(beneficiary); const balance = await this.token.balanceOf(beneficiary);
balance.should.be.bignumber.equal(amount); balance.should.be.bignumber.equal(amount);
});
});
}); });
}); });
...@@ -15,110 +15,134 @@ const TokenVesting = artifacts.require('TokenVesting'); ...@@ -15,110 +15,134 @@ const TokenVesting = artifacts.require('TokenVesting');
contract('TokenVesting', function ([_, owner, beneficiary]) { contract('TokenVesting', function ([_, owner, beneficiary]) {
const amount = new BigNumber(1000); const amount = new BigNumber(1000);
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
beforeEach(async function () { beforeEach(async function () {
this.token = await MintableToken.new({ from: owner });
this.start = (await latestTime()) + duration.minutes(1); // +1 minute so it starts after contract instantiation this.start = (await latestTime()) + duration.minutes(1); // +1 minute so it starts after contract instantiation
this.cliff = duration.years(1); this.cliff = duration.years(1);
this.duration = duration.years(2); this.duration = duration.years(2);
});
this.vesting = await TokenVesting.new(beneficiary, this.start, this.cliff, this.duration, true, { from: owner }); it('rejects a duration shorter than the cliff', async function () {
const cliff = this.duration;
const duration = this.cliff;
await this.token.mint(this.vesting.address, amount, { from: owner }); cliff.should.be.gt(duration);
});
it('cannot be released before cliff', async function () {
await expectThrow( await expectThrow(
this.vesting.release(this.token.address), TokenVesting.new(beneficiary, this.start, cliff, duration, true, { from: owner })
EVMRevert,
); );
}); });
it('can be released after cliff', async function () { it('requires a valid beneficiary', async function () {
await increaseTimeTo(this.start + this.cliff + duration.weeks(1)); await expectThrow(
await this.vesting.release(this.token.address); TokenVesting.new(ZERO_ADDRESS, this.start, this.cliff, this.duration, true, { from: owner })
);
}); });
it('should release proper amount after cliff', async function () { context('once deployed', function () {
await increaseTimeTo(this.start + this.cliff); beforeEach(async function () {
this.vesting = await TokenVesting.new(beneficiary, this.start, this.cliff, this.duration, true, { from: owner });
const { receipt } = await this.vesting.release(this.token.address); this.token = await MintableToken.new({ from: owner });
const block = await ethGetBlock(receipt.blockNumber); await this.token.mint(this.vesting.address, amount, { from: owner });
const releaseTime = block.timestamp; });
const balance = await this.token.balanceOf(beneficiary); it('cannot be released before cliff', async function () {
balance.should.bignumber.eq(amount.mul(releaseTime - this.start).div(this.duration).floor()); await expectThrow(
}); this.vesting.release(this.token.address),
EVMRevert,
);
});
it('should linearly release tokens during vesting period', async function () { it('can be released after cliff', async function () {
const vestingPeriod = this.duration - this.cliff; await increaseTimeTo(this.start + this.cliff + duration.weeks(1));
const checkpoints = 4; await this.vesting.release(this.token.address);
});
for (let i = 1; i <= checkpoints; i++) { it('should release proper amount after cliff', async function () {
const now = this.start + this.cliff + i * (vestingPeriod / checkpoints); await increaseTimeTo(this.start + this.cliff);
await increaseTimeTo(now);
const { receipt } = await this.vesting.release(this.token.address);
const block = await ethGetBlock(receipt.blockNumber);
const releaseTime = block.timestamp;
await this.vesting.release(this.token.address);
const balance = await this.token.balanceOf(beneficiary); const balance = await this.token.balanceOf(beneficiary);
const expectedVesting = amount.mul(now - this.start).div(this.duration).floor(); balance.should.bignumber.eq(amount.mul(releaseTime - this.start).div(this.duration).floor());
});
balance.should.bignumber.eq(expectedVesting); it('should linearly release tokens during vesting period', async function () {
} const vestingPeriod = this.duration - this.cliff;
}); const checkpoints = 4;
it('should have released all after end', async function () { for (let i = 1; i <= checkpoints; i++) {
await increaseTimeTo(this.start + this.duration); const now = this.start + this.cliff + i * (vestingPeriod / checkpoints);
await this.vesting.release(this.token.address); await increaseTimeTo(now);
const balance = await this.token.balanceOf(beneficiary);
balance.should.bignumber.eq(amount);
});
it('should be revoked by owner if revocable is set', async function () { await this.vesting.release(this.token.address);
await this.vesting.revoke(this.token.address, { from: owner }); const balance = await this.token.balanceOf(beneficiary);
}); const expectedVesting = amount.mul(now - this.start).div(this.duration).floor();
it('should fail to be revoked by owner if revocable not set', async function () { balance.should.bignumber.eq(expectedVesting);
const vesting = await TokenVesting.new(beneficiary, this.start, this.cliff, this.duration, false, { from: owner }); }
await expectThrow( });
vesting.revoke(this.token.address, { from: owner }),
EVMRevert,
);
});
it('should return the non-vested tokens when revoked by owner', async function () { it('should have released all after end', async function () {
await increaseTimeTo(this.start + this.cliff + duration.weeks(12)); await increaseTimeTo(this.start + this.duration);
await this.vesting.release(this.token.address);
const balance = await this.token.balanceOf(beneficiary);
balance.should.bignumber.eq(amount);
});
const vested = await this.vesting.vestedAmount(this.token.address); it('should be revoked by owner if revocable is set', async function () {
await this.vesting.revoke(this.token.address, { from: owner });
});
await this.vesting.revoke(this.token.address, { from: owner }); it('should fail to be revoked by owner if revocable not set', async function () {
const vesting = await TokenVesting.new(
beneficiary, this.start, this.cliff, this.duration, false, { from: owner }
);
const ownerBalance = await this.token.balanceOf(owner); await expectThrow(
ownerBalance.should.bignumber.eq(amount.sub(vested)); vesting.revoke(this.token.address, { from: owner }),
}); EVMRevert,
);
});
it('should keep the vested tokens when revoked by owner', async function () { it('should return the non-vested tokens when revoked by owner', async function () {
await increaseTimeTo(this.start + this.cliff + duration.weeks(12)); await increaseTimeTo(this.start + this.cliff + duration.weeks(12));
const vestedPre = await this.vesting.vestedAmount(this.token.address); const vested = await this.vesting.vestedAmount(this.token.address);
await this.vesting.revoke(this.token.address, { from: owner }); await this.vesting.revoke(this.token.address, { from: owner });
const vestedPost = await this.vesting.vestedAmount(this.token.address); const ownerBalance = await this.token.balanceOf(owner);
ownerBalance.should.bignumber.eq(amount.sub(vested));
});
vestedPre.should.bignumber.eq(vestedPost); it('should keep the vested tokens when revoked by owner', async function () {
}); await increaseTimeTo(this.start + this.cliff + duration.weeks(12));
it('should fail to be revoked a second time', async function () { const vestedPre = await this.vesting.vestedAmount(this.token.address);
await increaseTimeTo(this.start + this.cliff + duration.weeks(12));
await this.vesting.vestedAmount(this.token.address); await this.vesting.revoke(this.token.address, { from: owner });
await this.vesting.revoke(this.token.address, { from: owner }); const vestedPost = await this.vesting.vestedAmount(this.token.address);
await expectThrow( vestedPre.should.bignumber.eq(vestedPost);
this.vesting.revoke(this.token.address, { from: owner }), });
EVMRevert,
); it('should fail to be revoked a second time', async function () {
await increaseTimeTo(this.start + this.cliff + duration.weeks(12));
await this.vesting.vestedAmount(this.token.address);
await this.vesting.revoke(this.token.address, { from: owner });
await expectThrow(
this.vesting.revoke(this.token.address, { from: owner }),
EVMRevert,
);
});
}); });
}); });
...@@ -16,7 +16,9 @@ contract('ERC721Token', function (accounts) { ...@@ -16,7 +16,9 @@ contract('ERC721Token', function (accounts) {
const symbol = 'NFT'; const symbol = 'NFT';
const firstTokenId = 100; const firstTokenId = 100;
const secondTokenId = 200; const secondTokenId = 200;
const nonExistentTokenId = 999;
const creator = accounts[0]; const creator = accounts[0];
const anyone = accounts[9];
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC721Token.new(name, symbol, { from: creator }); this.token = await ERC721Token.new(name, symbol, { from: creator });
...@@ -77,27 +79,35 @@ contract('ERC721Token', function (accounts) { ...@@ -77,27 +79,35 @@ contract('ERC721Token', function (accounts) {
}); });
describe('removeTokenFrom', function () { describe('removeTokenFrom', function () {
beforeEach(async function () { it('reverts if the correct owner is not passed', async function () {
await this.token._removeTokenFrom(creator, firstTokenId, { from: creator }); await assertRevert(
this.token._removeTokenFrom(anyone, firstTokenId, { from: creator })
);
}); });
it('has been removed', async function () { context('once removed', function () {
await assertRevert(this.token.tokenOfOwnerByIndex(creator, 1)); beforeEach(async function () {
}); await this.token._removeTokenFrom(creator, firstTokenId, { from: creator });
});
it('adjusts token list', async function () { it('has been removed', async function () {
const token = await this.token.tokenOfOwnerByIndex(creator, 0); await assertRevert(this.token.tokenOfOwnerByIndex(creator, 1));
token.toNumber().should.be.equal(secondTokenId); });
});
it('adjusts owner count', async function () { it('adjusts token list', async function () {
const count = await this.token.balanceOf(creator); const token = await this.token.tokenOfOwnerByIndex(creator, 0);
count.toNumber().should.be.equal(1); token.toNumber().should.be.equal(secondTokenId);
}); });
it('does not adjust supply', async function () { it('adjusts owner count', async function () {
const total = await this.token.totalSupply(); const count = await this.token.balanceOf(creator);
total.toNumber().should.be.equal(2); count.toNumber().should.be.equal(1);
});
it('does not adjust supply', async function () {
const total = await this.token.totalSupply();
total.toNumber().should.be.equal(2);
});
}); });
}); });
...@@ -120,6 +130,10 @@ contract('ERC721Token', function (accounts) { ...@@ -120,6 +130,10 @@ contract('ERC721Token', function (accounts) {
uri.should.be.equal(sampleUri); uri.should.be.equal(sampleUri);
}); });
it('reverts when setting metadata for non existent token id', async function () {
await assertRevert(this.token.setTokenURI(nonExistentTokenId, sampleUri));
});
it('can burn token with metadata', async function () { it('can burn token with metadata', async function () {
await this.token.setTokenURI(firstTokenId, sampleUri); await this.token.setTokenURI(firstTokenId, sampleUri);
await this.token.burn(firstTokenId); await this.token.burn(firstTokenId);
...@@ -132,8 +146,8 @@ contract('ERC721Token', function (accounts) { ...@@ -132,8 +146,8 @@ contract('ERC721Token', function (accounts) {
uri.should.be.equal(''); uri.should.be.equal('');
}); });
it('reverts when querying metadata for non existant token id', async function () { it('reverts when querying metadata for non existent token id', async function () {
await assertRevert(this.token.tokenURI(500)); await assertRevert(this.token.tokenURI(nonExistentTokenId));
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment