Browse Source

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.
Nicolás Venturo 7 years ago
parent
commit
8d11dcc0e5

+ 0 - 1
contracts/access/SignatureBouncer.sol

@@ -82,7 +82,6 @@ contract SignatureBouncer is Ownable, RBAC {
     public
     onlyOwner
   {
-    require(_bouncer != address(0));
     removeRole(_bouncer, ROLE_BOUNCER);
   }
 

+ 0 - 1
contracts/token/ERC721/ERC721BasicToken.sol

@@ -131,7 +131,6 @@ contract ERC721BasicToken is SupportsInterfaceWithLookup, ERC721Basic {
     public
   {
     require(isApprovedOrOwner(msg.sender, _tokenId));
-    require(_from != address(0));
     require(_to != address(0));
 
     clearApproval(_from, _tokenId);

+ 7 - 1
test/Bounty.test.js

@@ -17,7 +17,7 @@ const sendReward = async (from, to, value) => ethSendTransaction({
 
 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 () {
     beforeEach(async function () {
       this.bounty = await SecureTargetBounty.new({ from: owner });
@@ -82,6 +82,12 @@ contract('Bounty', function ([_, owner, researcher]) {
       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 () {
       beforeEach(async function () {
         await this.bounty.claim(this.targetAddress, { from: researcher });

+ 101 - 49
test/crowdsale/Crowdsale.test.js

@@ -1,3 +1,4 @@
+const { assertRevert } = require('../helpers/assertRevert');
 const { ether } = require('../helpers/ether');
 const { ethGetBalance } = require('../helpers/web3');
 
@@ -15,67 +16,118 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
   const value = ether(42);
   const tokenSupply = new BigNumber('1e22');
   const expectedTokenAmount = rate.mul(value);
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
 
-  beforeEach(async function () {
-    this.token = await SimpleToken.new();
-    this.crowdsale = await Crowdsale.new(rate, wallet, this.token.address);
-    await this.token.transfer(this.crowdsale.address, tokenSupply);
+  it('requires a non-null token', async function () {
+    await assertRevert(
+      Crowdsale.new(rate, wallet, ZERO_ADDRESS)
+    );
   });
 
-  describe('accepting payments', function () {
-    it('should accept payments', async function () {
-      await this.crowdsale.send(value);
-      await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
+  context('with token', async function () {
+    beforeEach(async function () {
+      this.token = await SimpleToken.new();
     });
-  });
 
-  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('requires a non-zero rate', async function () {
+      await assertRevert(
+        Crowdsale.new(0, wallet, this.token.address)
+      );
     });
 
-    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('requires a non-null wallet', async function () {
+      await assertRevert(
+        Crowdsale.new(rate, ZERO_ADDRESS, this.token.address)
+      );
     });
 
-    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);
-    });
-  });
+    context('once deployed', async function () {
+      beforeEach(async function () {
+        this.crowdsale = await Crowdsale.new(rate, wallet, this.token.address);
+        await this.token.transfer(this.crowdsale.address, tokenSupply);
+      });
 
-  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);
-    });
+      describe('accepting payments', function () {
+        describe('bare payments', function () {
+          it('should accept payments', async function () {
+            await this.crowdsale.send(value, { from: purchaser });
+          });
 
-    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('reverts on zero-valued payments', async function () {
+            await assertRevert(
+              this.crowdsale.send(0, { from: purchaser })
+            );
+          });
+        });
+
+        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 () {
-      const pre = await ethGetBalance(wallet);
-      await this.crowdsale.buyTokens(investor, { value, from: purchaser });
-      const post = await ethGetBalance(wallet);
-      post.minus(pre).should.be.bignumber.equal(value);
+        it('should forward funds to wallet', async function () {
+          const pre = await ethGetBalance(wallet);
+          await this.crowdsale.buyTokens(investor, { value, from: purchaser });
+          const post = await ethGetBalance(wallet);
+          post.minus(pre).should.be.bignumber.equal(value);
+        });
+      });
     });
   });
 });

+ 8 - 0
test/ownership/Superuser.test.js

@@ -7,6 +7,8 @@ require('chai')
   .should();
 
 contract('Superuser', function ([_, firstOwner, newSuperuser, newOwner, anyone]) {
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
+
   beforeEach(async function () {
     this.superuser = await Superuser.new({ from: firstOwner });
   });
@@ -27,6 +29,12 @@ contract('Superuser', function ([_, firstOwner, newSuperuser, newOwner, anyone])
       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 () {
       await this.superuser.transferSuperuser(newSuperuser, { from: firstOwner });
 

+ 85 - 60
test/payment/RefundEscrow.test.js

@@ -14,93 +14,118 @@ const RefundEscrow = artifacts.require('RefundEscrow');
 contract('RefundEscrow', function ([_, owner, beneficiary, refundee1, refundee2]) {
   const amount = web3.toWei(54.0, 'ether');
   const refundees = [refundee1, refundee2];
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
 
-  beforeEach(async function () {
-    this.escrow = await RefundEscrow.new(beneficiary, { from: owner });
+  it('requires a non-null beneficiary', async function () {
+    await expectThrow(
+      RefundEscrow.new(ZERO_ADDRESS, { from: owner })
+    );
   });
 
-  context('active state', function () {
-    it('accepts deposits', async function () {
-      await this.escrow.deposit(refundee1, { from: owner, value: amount });
-
-      const deposit = await this.escrow.depositsOf(refundee1);
-      deposit.should.be.bignumber.equal(amount);
+  context('once deployed', function () {
+    beforeEach(async function () {
+      this.escrow = await RefundEscrow.new(beneficiary, { from: owner });
     });
 
-    it('does not refund refundees', async function () {
-      await this.escrow.deposit(refundee1, { from: owner, value: amount });
-      await expectThrow(this.escrow.withdraw(refundee1), EVMRevert);
+    context('active state', function () {
+      it('accepts deposits', async function () {
+        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 () {
-      await this.escrow.deposit(refundee1, { from: owner, value: amount });
-      await expectThrow(this.escrow.beneficiaryWithdraw(), EVMRevert);
+    it('only owner can enter closed state', async function () {
+      await expectThrow(this.escrow.close({ from: beneficiary }), EVMRevert);
+
+      const receipt = await this.escrow.close({ from: owner });
+
+      expectEvent.inLogs(receipt.logs, 'Closed');
     });
-  });
 
-  it('only owner can enter closed state', async function () {
-    await expectThrow(this.escrow.close({ from: beneficiary }), EVMRevert);
+    context('closed state', function () {
+      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 () {
-    beforeEach(async function () {
-      await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount })));
+      it('does not refund refundees', async function () {
+        await expectThrow(this.escrow.withdraw(refundee1), EVMRevert);
+      });
 
-      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 () {
-      await expectThrow(this.escrow.deposit(refundee1, { from: owner, value: amount }), EVMRevert);
-    });
+        beneficiaryFinalBalance.sub(beneficiaryInitialBalance).should.be.bignumber.equal(amount * refundees.length);
+      });
+
+      it('prevents entering the refund state', async function () {
+        await expectThrow(this.escrow.enableRefunds({ from: owner }), EVMRevert);
+      });
 
-    it('does not refund refundees', async function () {
-      await expectThrow(this.escrow.withdraw(refundee1), EVMRevert);
+      it('prevents re-entering the closed state', async function () {
+        await expectThrow(this.escrow.close({ from: owner }), EVMRevert);
+      });
     });
 
-    it('allows beneficiary withdrawal', async function () {
-      const beneficiaryInitialBalance = await ethGetBalance(beneficiary);
-      await this.escrow.beneficiaryWithdraw();
-      const beneficiaryFinalBalance = await ethGetBalance(beneficiary);
+    it('only owner can enter refund state', async function () {
+      await expectThrow(this.escrow.enableRefunds({ from: beneficiary }), EVMRevert);
 
-      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 () {
-    await expectThrow(this.escrow.enableRefunds({ from: beneficiary }), EVMRevert);
+      expectEvent.inLogs(receipt.logs, 'RefundsEnabled');
+    });
 
-    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 () {
-    beforeEach(async function () {
-      await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount })));
+      it('rejects deposits', async function () {
+        await expectThrow(this.escrow.deposit(refundee1, { from: owner, value: amount }), EVMRevert);
+      });
 
-      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 () {
-      await expectThrow(this.escrow.deposit(refundee1, { from: owner, value: amount }), EVMRevert);
-    });
+          refundeeFinalBalance.sub(refundeeInitialBalance).should.be.bignumber.equal(amount);
+        }
+      });
 
-    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('does not allow beneficiary withdrawal', async function () {
+        await expectThrow(this.escrow.beneficiaryWithdraw(), EVMRevert);
+      });
 
-        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 () {
-      await expectThrow(this.escrow.beneficiaryWithdraw(), EVMRevert);
+      it('prevents re-entering the refund state', async function () {
+        await expectThrow(this.escrow.enableRefunds({ from: owner }), EVMRevert);
+      });
     });
   });
 });

+ 13 - 0
test/payment/SplitPayment.test.js

@@ -12,6 +12,7 @@ const SplitPayment = artifacts.require('SplitPayment');
 
 contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1, payer1]) {
   const amount = web3.toWei(1.0, 'ether');
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
 
   it('cannot be created with no payees', async function () {
     await expectThrow(SplitPayment.new([], []), EVMRevert);
@@ -25,6 +26,18 @@ contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1,
     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 () {
     beforeEach(async function () {
       this.payees = [payee1, payee2, payee3];

+ 13 - 4
test/token/ERC20/CappedToken.test.js

@@ -1,3 +1,4 @@
+const { assertRevert } = require('../../helpers/assertRevert');
 const { ether } = require('../../helpers/ether');
 const { shouldBehaveLikeMintableToken } = require('./MintableToken.behavior');
 const { shouldBehaveLikeCappedToken } = require('./CappedToken.behavior');
@@ -7,10 +8,18 @@ const CappedToken = artifacts.require('CappedToken');
 contract('Capped', function ([_, owner, ...otherAccounts]) {
   const cap = ether(1000);
 
-  beforeEach(async function () {
-    this.token = await CappedToken.new(cap, { from: owner });
+  it('requires a non-zero cap', async function () {
+    await assertRevert(
+      CappedToken.new(0, { from: owner })
+    );
   });
 
-  shouldBehaveLikeCappedToken(owner, otherAccounts, cap);
-  shouldBehaveLikeMintableToken(owner, owner, otherAccounts);
+  context('once deployed', async function () {
+    beforeEach(async function () {
+      this.token = await CappedToken.new(cap, { from: owner });
+    });
+
+    shouldBehaveLikeCappedToken(owner, otherAccounts, cap);
+    shouldBehaveLikeMintableToken(owner, owner, otherAccounts);
+  });
 });

+ 45 - 31
test/token/ERC20/TokenTimelock.test.js

@@ -14,41 +14,55 @@ const TokenTimelock = artifacts.require('TokenTimelock');
 contract('TokenTimelock', function ([_, owner, beneficiary]) {
   const amount = new BigNumber(100);
 
-  beforeEach(async function () {
-    this.token = await MintableToken.new({ from: owner });
-    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 });
-  });
+  context('with token', function () {
+    beforeEach(async function () {
+      this.token = await MintableToken.new({ from: owner });
+    });
 
-  it('cannot be released before time limit', async function () {
-    await expectThrow(this.timelock.release());
-  });
+    it('rejects a release time in the past', async function () {
+      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 () {
-    await increaseTimeTo(this.releaseTime - duration.seconds(3));
-    await expectThrow(this.timelock.release());
-  });
+    context('once deployed', function () {
+      beforeEach(async function () {
+        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 () {
-    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('cannot be released before time limit', async function () {
+        await expectThrow(this.timelock.release());
+      });
 
-  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 just before time limit', async function () {
+        await increaseTimeTo(this.releaseTime - duration.seconds(3));
+        await expectThrow(this.timelock.release());
+      });
+
+      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 () {
-    await increaseTimeTo(this.releaseTime + duration.years(1));
-    await this.timelock.release();
-    await expectThrow(this.timelock.release());
-    const balance = await this.token.balanceOf(beneficiary);
-    balance.should.be.bignumber.equal(amount);
+      it('cannot be released twice', async function () {
+        await increaseTimeTo(this.releaseTime + duration.years(1));
+        await this.timelock.release();
+        await expectThrow(this.timelock.release());
+        const balance = await this.token.balanceOf(beneficiary);
+        balance.should.be.bignumber.equal(amount);
+      });
+    });
   });
 });

+ 92 - 68
test/token/ERC20/TokenVesting.test.js

@@ -15,110 +15,134 @@ const TokenVesting = artifacts.require('TokenVesting');
 
 contract('TokenVesting', function ([_, owner, beneficiary]) {
   const amount = new BigNumber(1000);
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
 
   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.cliff = duration.years(1);
     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(
-      this.vesting.release(this.token.address),
-      EVMRevert,
+      TokenVesting.new(beneficiary, this.start, cliff, duration, true, { from: owner })
     );
   });
 
-  it('can be released after cliff', async function () {
-    await increaseTimeTo(this.start + this.cliff + duration.weeks(1));
-    await this.vesting.release(this.token.address);
+  it('requires a valid beneficiary', async function () {
+    await expectThrow(
+      TokenVesting.new(ZERO_ADDRESS, this.start, this.cliff, this.duration, true, { from: owner })
+    );
   });
 
-  it('should release proper amount after cliff', async function () {
-    await increaseTimeTo(this.start + this.cliff);
+  context('once deployed', function () {
+    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);
-    const block = await ethGetBlock(receipt.blockNumber);
-    const releaseTime = block.timestamp;
+      this.token = await MintableToken.new({ from: owner });
+      await this.token.mint(this.vesting.address, amount, { from: owner });
+    });
 
-    const balance = await this.token.balanceOf(beneficiary);
-    balance.should.bignumber.eq(amount.mul(releaseTime - this.start).div(this.duration).floor());
-  });
+    it('cannot be released before cliff', async function () {
+      await expectThrow(
+        this.vesting.release(this.token.address),
+        EVMRevert,
+      );
+    });
 
-  it('should linearly release tokens during vesting period', async function () {
-    const vestingPeriod = this.duration - this.cliff;
-    const checkpoints = 4;
+    it('can be released after cliff', async function () {
+      await increaseTimeTo(this.start + this.cliff + duration.weeks(1));
+      await this.vesting.release(this.token.address);
+    });
 
-    for (let i = 1; i <= checkpoints; i++) {
-      const now = this.start + this.cliff + i * (vestingPeriod / checkpoints);
-      await increaseTimeTo(now);
+    it('should release proper amount after cliff', async function () {
+      await increaseTimeTo(this.start + this.cliff);
+
+      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 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 () {
-    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);
-  });
+      for (let i = 1; i <= checkpoints; i++) {
+        const now = this.start + this.cliff + i * (vestingPeriod / checkpoints);
+        await increaseTimeTo(now);
 
-  it('should be revoked by owner if revocable is set', async function () {
-    await this.vesting.revoke(this.token.address, { from: owner });
-  });
+        await this.vesting.release(this.token.address);
+        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 () {
-    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,
-    );
-  });
+        balance.should.bignumber.eq(expectedVesting);
+      }
+    });
 
-  it('should return the non-vested tokens when revoked by owner', async function () {
-    await increaseTimeTo(this.start + this.cliff + duration.weeks(12));
+    it('should have released all after end', async function () {
+      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);
-    ownerBalance.should.bignumber.eq(amount.sub(vested));
-  });
+      await expectThrow(
+        vesting.revoke(this.token.address, { from: owner }),
+        EVMRevert,
+      );
+    });
 
-  it('should keep the vested tokens when revoked by owner', async function () {
-    await increaseTimeTo(this.start + this.cliff + duration.weeks(12));
+    it('should return the non-vested tokens when revoked by owner', async function () {
+      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 () {
-    await increaseTimeTo(this.start + this.cliff + duration.weeks(12));
+      const vestedPre = await this.vesting.vestedAmount(this.token.address);
 
-    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(
-      this.vesting.revoke(this.token.address, { from: owner }),
-      EVMRevert,
-    );
+      vestedPre.should.bignumber.eq(vestedPost);
+    });
+
+    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,
+      );
+    });
   });
 });

+ 32 - 18
test/token/ERC721/ERC721Token.test.js

@@ -16,7 +16,9 @@ contract('ERC721Token', function (accounts) {
   const symbol = 'NFT';
   const firstTokenId = 100;
   const secondTokenId = 200;
+  const nonExistentTokenId = 999;
   const creator = accounts[0];
+  const anyone = accounts[9];
 
   beforeEach(async function () {
     this.token = await ERC721Token.new(name, symbol, { from: creator });
@@ -77,27 +79,35 @@ contract('ERC721Token', function (accounts) {
     });
 
     describe('removeTokenFrom', function () {
-      beforeEach(async function () {
-        await this.token._removeTokenFrom(creator, firstTokenId, { from: creator });
+      it('reverts if the correct owner is not passed', async function () {
+        await assertRevert(
+          this.token._removeTokenFrom(anyone, firstTokenId, { from: creator })
+        );
       });
 
-      it('has been removed', async function () {
-        await assertRevert(this.token.tokenOfOwnerByIndex(creator, 1));
-      });
+      context('once removed', function () {
+        beforeEach(async function () {
+          await this.token._removeTokenFrom(creator, firstTokenId, { from: creator });
+        });
 
-      it('adjusts token list', async function () {
-        const token = await this.token.tokenOfOwnerByIndex(creator, 0);
-        token.toNumber().should.be.equal(secondTokenId);
-      });
+        it('has been removed', async function () {
+          await assertRevert(this.token.tokenOfOwnerByIndex(creator, 1));
+        });
 
-      it('adjusts owner count', async function () {
-        const count = await this.token.balanceOf(creator);
-        count.toNumber().should.be.equal(1);
-      });
+        it('adjusts token list', async function () {
+          const token = await this.token.tokenOfOwnerByIndex(creator, 0);
+          token.toNumber().should.be.equal(secondTokenId);
+        });
 
-      it('does not adjust supply', async function () {
-        const total = await this.token.totalSupply();
-        total.toNumber().should.be.equal(2);
+        it('adjusts owner count', async function () {
+          const count = await this.token.balanceOf(creator);
+          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) {
         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 () {
         await this.token.setTokenURI(firstTokenId, sampleUri);
         await this.token.burn(firstTokenId);
@@ -132,8 +146,8 @@ contract('ERC721Token', function (accounts) {
         uri.should.be.equal('');
       });
 
-      it('reverts when querying metadata for non existant token id', async function () {
-        await assertRevert(this.token.tokenURI(500));
+      it('reverts when querying metadata for non existent token id', async function () {
+        await assertRevert(this.token.tokenURI(nonExistentTokenId));
       });
     });