Преглед на файлове

Improve ERC20s tests coverage (#712)

* Improve StandardToken tests coverage
* Improve BasicToken test coverage
* Improve MintableToken test coverage
* Improve BurnableToken test coverage
* Improve PausableToken tests coverage
Facundo Spagnuolo преди 7 години
родител
ревизия
6ad275befb
променени са 5 файла, в които са добавени 898 реда и са изтрити 187 реда
  1. 69 20
      test/token/BasicToken.test.js
  2. 28 26
      test/token/BurnableToken.test.js
  3. 122 29
      test/token/MintableToken.test.js
  4. 244 43
      test/token/PausableToken.test.js
  5. 435 69
      test/token/StandardToken.test.js

+ 69 - 20
test/token/BasicToken.test.js

@@ -1,33 +1,82 @@
 import assertRevert from '../helpers/assertRevert';
+const BasicToken = artifacts.require('BasicTokenMock');
 
-var BasicTokenMock = artifacts.require('BasicTokenMock');
+contract('StandardToken', function ([_, owner, recipient, anotherAccount]) {
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
 
-contract('BasicToken', function (accounts) {
-  it('should return the correct totalSupply after construction', async function () {
-    let token = await BasicTokenMock.new(accounts[0], 100);
-    let totalSupply = await token.totalSupply();
+  beforeEach(async function () {
+    this.token = await BasicToken.new(owner, 100);
+  });
+
+  describe('total supply', function () {
+    it('returns the total amount of tokens', async function () {
+      const totalSupply = await this.token.totalSupply();
 
-    assert.equal(totalSupply, 100);
+      assert.equal(totalSupply, 100);
+    });
   });
 
-  it('should return correct balances after transfer', async function () {
-    let token = await BasicTokenMock.new(accounts[0], 100);
-    await token.transfer(accounts[1], 100);
+  describe('balanceOf', function () {
+    describe('when the requested account has no tokens', function () {
+      it('returns zero', async function () {
+        const balance = await this.token.balanceOf(anotherAccount);
 
-    let firstAccountBalance = await token.balanceOf(accounts[0]);
-    assert.equal(firstAccountBalance, 0);
+        assert.equal(balance, 0);
+      });
+    });
 
-    let secondAccountBalance = await token.balanceOf(accounts[1]);
-    assert.equal(secondAccountBalance, 100);
-  });
+    describe('when the requested account has some tokens', function () {
+      it('returns the total amount of tokens', async function () {
+        const balance = await this.token.balanceOf(owner);
 
-  it('should throw an error when trying to transfer more than balance', async function () {
-    let token = await BasicTokenMock.new(accounts[0], 100);
-    await assertRevert(token.transfer(accounts[1], 101));
+        assert.equal(balance, 100);
+      });
+    });
   });
 
-  it('should throw an error when trying to transfer to 0x0', async function () {
-    let token = await BasicTokenMock.new(accounts[0], 100);
-    await assertRevert(token.transfer(0x0, 100));
+  describe('transfer', function () {
+    describe('when the recipient is not the zero address', function () {
+      const to = recipient;
+
+      describe('when the sender does not have enough balance', function () {
+        const amount = 101;
+
+        it('reverts', async function () {
+          await assertRevert(this.token.transfer(to, amount, { from: owner }));
+        });
+      });
+
+      describe('when the sender has enough balance', function () {
+        const amount = 100;
+
+        it('transfers the requested amount', async function () {
+          await this.token.transfer(to, amount, { from: owner });
+
+          const senderBalance = await this.token.balanceOf(owner);
+          assert.equal(senderBalance, 0);
+
+          const recipientBalance = await this.token.balanceOf(to);
+          assert.equal(recipientBalance, amount);
+        });
+
+        it('emits a transfer event', async function () {
+          const { logs } = await this.token.transfer(to, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Transfer');
+          assert.equal(logs[0].args.from, owner);
+          assert.equal(logs[0].args.to, to);
+          assert(logs[0].args.value.eq(amount));
+        });
+      });
+    });
+
+    describe('when the recipient is the zero address', function () {
+      const to = ZERO_ADDRESS;
+
+      it('reverts', async function () {
+        await assertRevert(this.token.transfer(to, 100, { from: owner }));
+      });
+    });
   });
 });

+ 28 - 26
test/token/BurnableToken.test.js

@@ -1,38 +1,40 @@
-
-const EVMRevert = require('../helpers/EVMRevert.js');
+import assertRevert from '../helpers/assertRevert';
 const BurnableTokenMock = artifacts.require('BurnableTokenMock');
-const BigNumber = web3.BigNumber;
 
-require('chai')
-  .use(require('chai-as-promised'))
-  .use(require('chai-bignumber')(BigNumber))
-  .should();
+contract('BurnableToken', function ([owner]) {
+  beforeEach(async function () {
+    this.token = await BurnableTokenMock.new(owner, 1000);
+  });
 
-const expect = require('chai').expect;
+  describe('burn', function () {
+    const from = owner;
 
-contract('BurnableToken', function (accounts) {
-  let token;
-  let expectedTokenSupply = new BigNumber(999);
+    describe('when the given amount is not greater than balance of the sender', function () {
+      const amount = 100;
 
-  beforeEach(async function () {
-    token = await BurnableTokenMock.new(accounts[0], 1000);
-  });
+      it('burns the requested amount', async function () {
+        await this.token.burn(amount, { from });
 
-  it('owner should be able to burn tokens', async function () {
-    const { logs } = await token.burn(1, { from: accounts[0] });
+        const balance = await this.token.balanceOf(from);
+        assert.equal(balance, 900);
+      });
 
-    const balance = await token.balanceOf(accounts[0]);
-    balance.should.be.bignumber.equal(expectedTokenSupply);
+      it('emits a burn event', async function () {
+        const { logs } = await this.token.burn(amount, { from });
 
-    const totalSupply = await token.totalSupply();
-    totalSupply.should.be.bignumber.equal(expectedTokenSupply);
+        assert.equal(logs.length, 1);
+        assert.equal(logs[0].event, 'Burn');
+        assert.equal(logs[0].args.burner, owner);
+        assert.equal(logs[0].args.value, amount);
+      });
+    });
 
-    const event = logs.find(e => e.event === 'Burn');
-    expect(event).to.exist;
-  });
+    describe('when the given amount is greater than the balance of the sender', function () {
+      const amount = 1001;
 
-  it('cannot burn more tokens than your balance', async function () {
-    await token.burn(2000, { from: accounts[0] })
-      .should.be.rejectedWith(EVMRevert);
+      it('reverts', async function () {
+        await assertRevert(this.token.burn(amount, { from }));
+      });
+    });
   });
 });

+ 122 - 29
test/token/MintableToken.test.js

@@ -1,44 +1,137 @@
+import assertRevert from '../helpers/assertRevert';
+const MintableToken = artifacts.require('MintableToken');
 
-import expectThrow from '../helpers/expectThrow';
-var MintableToken = artifacts.require('MintableToken');
-
-contract('Mintable', function (accounts) {
-  let token;
-
+contract('Mintable', function ([owner, anotherAccount]) {
   beforeEach(async function () {
-    token = await MintableToken.new();
+    this.token = await MintableToken.new({ from: owner });
   });
 
-  it('should start with a totalSupply of 0', async function () {
-    let totalSupply = await token.totalSupply();
+  describe('minting finished', function () {
+    describe('when the token is not finished', function () {
+      it('returns false', async function () {
+        const mintingFinished = await this.token.mintingFinished();
+        assert.equal(mintingFinished, false);
+      });
+    });
+
+    describe('when the token is finished', function () {
+      beforeEach(async function () {
+        await this.token.finishMinting({ from: owner });
+      });
 
-    assert.equal(totalSupply, 0);
+      it('returns true', async function () {
+        const mintingFinished = await this.token.mintingFinished.call();
+        assert.equal(mintingFinished, true);
+      });
+    });
   });
 
-  it('should return mintingFinished false after construction', async function () {
-    let mintingFinished = await token.mintingFinished();
+  describe('finish minting', function () {
+    describe('when the sender is the token owner', function () {
+      const from = owner;
 
-    assert.equal(mintingFinished, false);
-  });
+      describe('when the token was not finished', function () {
+        it('finishes token minting', async function () {
+          await this.token.finishMinting({ from });
+
+          const mintingFinished = await this.token.mintingFinished();
+          assert.equal(mintingFinished, true);
+        });
+
+        it('emits a mint finished event', async function () {
+          const { logs } = await this.token.finishMinting({ from });
 
-  it('should mint a given amount of tokens to a given address', async function () {
-    const result = await token.mint(accounts[0], 100);
-    assert.equal(result.logs[0].event, 'Mint');
-    assert.equal(result.logs[0].args.to.valueOf(), accounts[0]);
-    assert.equal(result.logs[0].args.amount.valueOf(), 100);
-    assert.equal(result.logs[1].event, 'Transfer');
-    assert.equal(result.logs[1].args.from.valueOf(), 0x0);
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'MintFinished');
+        });
+      });
 
-    let balance0 = await token.balanceOf(accounts[0]);
-    assert(balance0, 100);
+      describe('when the token was already finished', function () {
+        beforeEach(async function () {
+          await this.token.finishMinting({ from });
+        });
 
-    let totalSupply = await token.totalSupply();
-    assert(totalSupply, 100);
+        it('reverts', async function () {
+          await assertRevert(this.token.finishMinting({ from }));
+        });
+      });
+    });
+
+    describe('when the sender is not the token owner', function () {
+      const from = anotherAccount;
+
+      describe('when the token was not finished', function () {
+        it('reverts', async function () {
+          await assertRevert(this.token.finishMinting({ from }));
+        });
+      });
+
+      describe('when the token was already finished', function () {
+        beforeEach(async function () {
+          await this.token.finishMinting({ from: owner });
+        });
+
+        it('reverts', async function () {
+          await assertRevert(this.token.finishMinting({ from }));
+        });
+      });
+    });
   });
 
-  it('should fail to mint after call to finishMinting', async function () {
-    await token.finishMinting();
-    assert.equal(await token.mintingFinished(), true);
-    await expectThrow(token.mint(accounts[0], 100));
+  describe('mint', function () {
+    const amount = 100;
+
+    describe('when the sender is the token owner', function () {
+      const from = owner;
+
+      describe('when the token was not finished', function () {
+        it('mints the requested amount', async function () {
+          await this.token.mint(owner, amount, { from });
+
+          const balance = await this.token.balanceOf(owner);
+          assert.equal(balance, amount);
+        });
+
+        it('emits a mint finished event', async function () {
+          const { logs } = await this.token.mint(owner, amount, { from });
+
+          assert.equal(logs.length, 2);
+          assert.equal(logs[0].event, 'Mint');
+          assert.equal(logs[0].args.to, owner);
+          assert.equal(logs[0].args.amount, amount);
+          assert.equal(logs[1].event, 'Transfer');
+        });
+      });
+
+      describe('when the token minting is finished', function () {
+        beforeEach(async function () {
+          await this.token.finishMinting({ from });
+        });
+
+        it('reverts', async function () {
+          await assertRevert(this.token.mint(owner, amount, { from }));
+        });
+      });
+    });
+
+    describe('when the sender is not the token owner', function () {
+      const from = anotherAccount;
+
+      describe('when the token was not finished', function () {
+        it('reverts', async function () {
+          await assertRevert(this.token.mint(owner, amount, { from }));
+        });
+      });
+
+      describe('when the token was already finished', function () {
+        beforeEach(async function () {
+          await this.token.finishMinting({ from: owner });
+        });
+
+        it('reverts', async function () {
+          await assertRevert(this.token.mint(owner, amount, { from }));
+        });
+      });
+    });
   });
 });

+ 244 - 43
test/token/PausableToken.test.js

@@ -1,63 +1,264 @@
-'user strict';
-
 import assertRevert from '../helpers/assertRevert';
-var PausableTokenMock = artifacts.require('PausableTokenMock');
-
-contract('PausableToken', function (accounts) {
-  let token;
+const PausableToken = artifacts.require('PausableTokenMock');
 
+contract('PausableToken', function ([_, owner, recipient, anotherAccount]) {
   beforeEach(async function () {
-    token = await PausableTokenMock.new(accounts[0], 100);
+    this.token = await PausableToken.new(owner, 100, { from: owner });
   });
 
-  it('should return paused false after construction', async function () {
-    let paused = await token.paused();
+  describe('pause', function () {
+    describe('when the sender is the token owner', function () {
+      const from = owner;
 
-    assert.equal(paused, false);
-  });
+      describe('when the token is unpaused', function () {
+        it('pauses the token', async function () {
+          await this.token.pause({ from });
 
-  it('should return paused true after pause', async function () {
-    await token.pause();
-    let paused = await token.paused();
+          const paused = await this.token.paused();
+          assert.equal(paused, true);
+        });
 
-    assert.equal(paused, true);
-  });
+        it('emits a paused event', async function () {
+          const { logs } = await this.token.pause({ from });
 
-  it('should return paused false after pause and unpause', async function () {
-    await token.pause();
-    await token.unpause();
-    let paused = await token.paused();
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Pause');
+        });
+      });
 
-    assert.equal(paused, false);
-  });
+      describe('when the token is paused', function () {
+        beforeEach(async function () {
+          await this.token.pause({ from });
+        });
+
+        it('reverts', async function () {
+          await assertRevert(this.token.pause({ from }));
+        });
+      });
+    });
 
-  it('should be able to transfer if transfers are unpaused', async function () {
-    await token.transfer(accounts[1], 100);
-    let balance0 = await token.balanceOf(accounts[0]);
-    assert.equal(balance0, 0);
+    describe('when the sender is not the token owner', function () {
+      const from = anotherAccount;
 
-    let balance1 = await token.balanceOf(accounts[1]);
-    assert.equal(balance1, 100);
+      it('reverts', async function () {
+        await assertRevert(this.token.pause({ from }));
+      });
+    });
   });
 
-  it('should be able to transfer after transfers are paused and unpaused', async function () {
-    await token.pause();
-    await token.unpause();
-    await token.transfer(accounts[1], 100);
-    let balance0 = await token.balanceOf(accounts[0]);
-    assert.equal(balance0, 0);
+  describe('unpause', function () {
+    describe('when the sender is the token owner', function () {
+      const from = owner;
 
-    let balance1 = await token.balanceOf(accounts[1]);
-    assert.equal(balance1, 100);
-  });
+      describe('when the token is paused', function () {
+        beforeEach(async function () {
+          await this.token.pause({ from });
+        });
+
+        it('unpauses the token', async function () {
+          await this.token.unpause({ from });
 
-  it('should throw an error trying to transfer while transactions are paused', async function () {
-    await token.pause();
-    await assertRevert(token.transfer(accounts[1], 100));
+          const paused = await this.token.paused();
+          assert.equal(paused, false);
+        });
+
+        it('emits an unpaused event', async function () {
+          const { logs } = await this.token.unpause({ from });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Unpause');
+        });
+      });
+
+      describe('when the token is unpaused', function () {
+        it('reverts', async function () {
+          await assertRevert(this.token.unpause({ from }));
+        });
+      });
+    });
+
+    describe('when the sender is not the token owner', function () {
+      const from = anotherAccount;
+
+      it('reverts', async function () {
+        await assertRevert(this.token.unpause({ from }));
+      });
+    });
   });
 
-  it('should throw an error trying to transfer from another account while transactions are paused', async function () {
-    await token.pause();
-    await assertRevert(token.transferFrom(accounts[0], accounts[1], 100));
+  describe('pausable token', function () {
+    const from = owner;
+
+    describe('paused', function () {
+      it('is not paused by default', async function () {
+        const paused = await this.token.paused({ from });
+
+        assert.equal(paused, false);
+      });
+
+      it('is paused after being paused', async function () {
+        await this.token.pause({ from });
+        const paused = await this.token.paused({ from });
+
+        assert.equal(paused, true);
+      });
+
+      it('is not paused after being paused and then unpaused', async function () {
+        await this.token.pause({ from });
+        await this.token.unpause({ from });
+        const paused = await this.token.paused();
+
+        assert.equal(paused, false);
+      });
+    });
+
+    describe('transfer', function () {
+      it('allows to transfer when unpaused', async function () {
+        await this.token.transfer(recipient, 100, { from: owner });
+
+        const senderBalance = await this.token.balanceOf(owner);
+        assert.equal(senderBalance, 0);
+
+        const recipientBalance = await this.token.balanceOf(recipient);
+        assert.equal(recipientBalance, 100);
+      });
+
+      it('allows to transfer when paused and then unpaused', async function () {
+        await this.token.pause({ from: owner });
+        await this.token.unpause({ from: owner });
+
+        await this.token.transfer(recipient, 100, { from: owner });
+
+        const senderBalance = await this.token.balanceOf(owner);
+        assert.equal(senderBalance, 0);
+
+        const recipientBalance = await this.token.balanceOf(recipient);
+        assert.equal(recipientBalance, 100);
+      });
+
+      it('reverts when trying to transfer when paused', async function () {
+        await this.token.pause({ from: owner });
+
+        await assertRevert(this.token.transfer(recipient, 100, { from: owner }));
+      });
+    });
+
+    describe('approve', function () {
+      it('allows to approve when unpaused', async function () {
+        await this.token.approve(anotherAccount, 40, { from: owner });
+
+        const allowance = await this.token.allowance(owner, anotherAccount);
+        assert.equal(allowance, 40);
+      });
+
+      it('allows to transfer when paused and then unpaused', async function () {
+        await this.token.pause({ from: owner });
+        await this.token.unpause({ from: owner });
+
+        await this.token.approve(anotherAccount, 40, { from: owner });
+
+        const allowance = await this.token.allowance(owner, anotherAccount);
+        assert.equal(allowance, 40);
+      });
+
+      it('reverts when trying to transfer when paused', async function () {
+        await this.token.pause({ from: owner });
+
+        await assertRevert(this.token.approve(anotherAccount, 40, { from: owner }));
+      });
+    });
+
+    describe('transfer from', function () {
+      beforeEach(async function () {
+        await this.token.approve(anotherAccount, 50, { from: owner });
+      });
+
+      it('allows to transfer from when unpaused', async function () {
+        await this.token.transferFrom(owner, recipient, 40, { from: anotherAccount });
+
+        const senderBalance = await this.token.balanceOf(owner);
+        assert.equal(senderBalance, 60);
+
+        const recipientBalance = await this.token.balanceOf(recipient);
+        assert.equal(recipientBalance, 40);
+      });
+
+      it('allows to transfer when paused and then unpaused', async function () {
+        await this.token.pause({ from: owner });
+        await this.token.unpause({ from: owner });
+
+        await this.token.transferFrom(owner, recipient, 40, { from: anotherAccount });
+
+        const senderBalance = await this.token.balanceOf(owner);
+        assert.equal(senderBalance, 60);
+
+        const recipientBalance = await this.token.balanceOf(recipient);
+        assert.equal(recipientBalance, 40);
+      });
+
+      it('reverts when trying to transfer from when paused', async function () {
+        await this.token.pause({ from: owner });
+
+        await assertRevert(this.token.transferFrom(owner, recipient, 40, { from: anotherAccount }));
+      });
+    });
+
+    describe('decrease approval', function () {
+      beforeEach(async function () {
+        await this.token.approve(anotherAccount, 100, { from: owner });
+      });
+
+      it('allows to decrease approval when unpaused', async function () {
+        await this.token.decreaseApproval(anotherAccount, 40, { from: owner });
+
+        const allowance = await this.token.allowance(owner, anotherAccount);
+        assert.equal(allowance, 60);
+      });
+
+      it('allows to decrease approval when paused and then unpaused', async function () {
+        await this.token.pause({ from: owner });
+        await this.token.unpause({ from: owner });
+
+        await this.token.decreaseApproval(anotherAccount, 40, { from: owner });
+
+        const allowance = await this.token.allowance(owner, anotherAccount);
+        assert.equal(allowance, 60);
+      });
+
+      it('reverts when trying to transfer when paused', async function () {
+        await this.token.pause({ from: owner });
+
+        await assertRevert(this.token.decreaseApproval(anotherAccount, 40, { from: owner }));
+      });
+    });
+
+    describe('increase approval', function () {
+      beforeEach(async function () {
+        await this.token.approve(anotherAccount, 100, { from: owner });
+      });
+
+      it('allows to increase approval when unpaused', async function () {
+        await this.token.increaseApproval(anotherAccount, 40, { from: owner });
+
+        const allowance = await this.token.allowance(owner, anotherAccount);
+        assert.equal(allowance, 140);
+      });
+
+      it('allows to increase approval when paused and then unpaused', async function () {
+        await this.token.pause({ from: owner });
+        await this.token.unpause({ from: owner });
+
+        await this.token.increaseApproval(anotherAccount, 40, { from: owner });
+
+        const allowance = await this.token.allowance(owner, anotherAccount);
+        assert.equal(allowance, 140);
+      });
+
+      it('reverts when trying to increase approval when paused', async function () {
+        await this.token.pause({ from: owner });
+
+        await assertRevert(this.token.increaseApproval(anotherAccount, 40, { from: owner }));
+      });
+    });
   });
 });

+ 435 - 69
test/token/StandardToken.test.js

@@ -1,103 +1,469 @@
-
 import assertRevert from '../helpers/assertRevert';
+const StandardTokenMock = artifacts.require('StandardTokenMock');
 
-var StandardTokenMock = artifacts.require('StandardTokenMock');
-
-contract('StandardToken', function (accounts) {
-  let token;
+contract('StandardToken', function ([_, owner, recipient, anotherAccount]) {
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
 
   beforeEach(async function () {
-    token = await StandardTokenMock.new(accounts[0], 100);
+    this.token = await StandardTokenMock.new(owner, 100);
   });
 
-  it('should return the correct totalSupply after construction', async function () {
-    let totalSupply = await token.totalSupply();
+  describe('total supply', function () {
+    it('returns the total amount of tokens', async function () {
+      const totalSupply = await this.token.totalSupply();
 
-    assert.equal(totalSupply, 100);
+      assert.equal(totalSupply, 100);
+    });
   });
 
-  it('should return the correct allowance amount after approval', async function () {
-    let token = await StandardTokenMock.new();
-    await token.approve(accounts[1], 100);
-    let allowance = await token.allowance(accounts[0], accounts[1]);
+  describe('balanceOf', function () {
+    describe('when the requested account has no tokens', function () {
+      it('returns zero', async function () {
+        const balance = await this.token.balanceOf(anotherAccount);
 
-    assert.equal(allowance, 100);
-  });
+        assert.equal(balance, 0);
+      });
+    });
 
-  it('should return correct balances after transfer', async function () {
-    let token = await StandardTokenMock.new(accounts[0], 100);
-    await token.transfer(accounts[1], 100);
-    let balance0 = await token.balanceOf(accounts[0]);
-    assert.equal(balance0, 0);
+    describe('when the requested account has some tokens', function () {
+      it('returns the total amount of tokens', async function () {
+        const balance = await this.token.balanceOf(owner);
 
-    let balance1 = await token.balanceOf(accounts[1]);
-    assert.equal(balance1, 100);
+        assert.equal(balance, 100);
+      });
+    });
   });
 
-  it('should throw an error when trying to transfer more than balance', async function () {
-    let token = await StandardTokenMock.new(accounts[0], 100);
-    await assertRevert(token.transfer(accounts[1], 101));
-  });
+  describe('transfer', function () {
+    describe('when the recipient is not the zero address', function () {
+      const to = recipient;
 
-  it('should return correct balances after transfering from another account', async function () {
-    let token = await StandardTokenMock.new(accounts[0], 100);
-    await token.approve(accounts[1], 100);
-    await token.transferFrom(accounts[0], accounts[2], 100, { from: accounts[1] });
+      describe('when the sender does not have enough balance', function () {
+        const amount = 101;
 
-    let balance0 = await token.balanceOf(accounts[0]);
-    assert.equal(balance0, 0);
+        it('reverts', async function () {
+          await assertRevert(this.token.transfer(to, amount, { from: owner }));
+        });
+      });
 
-    let balance1 = await token.balanceOf(accounts[2]);
-    assert.equal(balance1, 100);
+      describe('when the sender has enough balance', function () {
+        const amount = 100;
 
-    let balance2 = await token.balanceOf(accounts[1]);
-    assert.equal(balance2, 0);
-  });
+        it('transfers the requested amount', async function () {
+          await this.token.transfer(to, amount, { from: owner });
 
-  it('should throw an error when trying to transfer more than allowed', async function () {
-    await token.approve(accounts[1], 99);
-    await assertRevert(token.transferFrom(accounts[0], accounts[2], 100, { from: accounts[1] }));
-  });
+          const senderBalance = await this.token.balanceOf(owner);
+          assert.equal(senderBalance, 0);
+
+          const recipientBalance = await this.token.balanceOf(to);
+          assert.equal(recipientBalance, amount);
+        });
+
+        it('emits a transfer event', async function () {
+          const { logs } = await this.token.transfer(to, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Transfer');
+          assert.equal(logs[0].args.from, owner);
+          assert.equal(logs[0].args.to, to);
+          assert(logs[0].args.value.eq(amount));
+        });
+      });
+    });
 
-  it('should throw an error when trying to transferFrom more than _from has', async function () {
-    let balance0 = await token.balanceOf(accounts[0]);
-    await token.approve(accounts[1], 99);
-    await assertRevert(token.transferFrom(accounts[0], accounts[2], balance0 + 1, { from: accounts[1] }));
+    describe('when the recipient is the zero address', function () {
+      const to = ZERO_ADDRESS;
+
+      it('reverts', async function () {
+        await assertRevert(this.token.transfer(to, 100, { from: owner }));
+      });
+    });
   });
 
-  describe('validating allowance updates to spender', function () {
-    let preApproved;
+  describe('approve', function () {
+    describe('when the spender is not the zero address', function () {
+      const spender = recipient;
+
+      describe('when the sender has enough balance', function () {
+        const amount = 100;
+
+        it('emits an approval event', async function () {
+          const { logs } = await this.token.approve(spender, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Approval');
+          assert.equal(logs[0].args.owner, owner);
+          assert.equal(logs[0].args.spender, spender);
+          assert(logs[0].args.value.eq(amount));
+        });
+
+        describe('when there was no approved amount before', function () {
+          it('approves the requested amount', async function () {
+            await this.token.approve(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount);
+          });
+        });
+
+        describe('when the spender had an approved amount', function () {
+          beforeEach(async function () {
+            await this.token.approve(spender, 1, { from: owner });
+          });
+
+          it('approves the requested amount and replaces the previous one', async function () {
+            await this.token.approve(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount);
+          });
+        });
+      });
+
+      describe('when the sender does not have enough balance', function () {
+        const amount = 101;
+
+        it('emits an approval event', async function () {
+          const { logs } = await this.token.approve(spender, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Approval');
+          assert.equal(logs[0].args.owner, owner);
+          assert.equal(logs[0].args.spender, spender);
+          assert(logs[0].args.value.eq(amount));
+        });
 
-    it('should start with zero', async function () {
-      preApproved = await token.allowance(accounts[0], accounts[1]);
-      assert.equal(preApproved, 0);
+        describe('when there was no approved amount before', function () {
+          it('approves the requested amount', async function () {
+            await this.token.approve(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount);
+          });
+        });
+
+        describe('when the spender had an approved amount', function () {
+          beforeEach(async function () {
+            await this.token.approve(spender, 1, { from: owner });
+          });
+
+          it('approves the requested amount and replaces the previous one', async function () {
+            await this.token.approve(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount);
+          });
+        });
+      });
     });
 
-    it('should increase by 50 then decrease by 10', async function () {
-      await token.increaseApproval(accounts[1], 50);
-      let postIncrease = await token.allowance(accounts[0], accounts[1]);
-      preApproved.plus(50).should.be.bignumber.equal(postIncrease);
-      await token.decreaseApproval(accounts[1], 10);
-      let postDecrease = await token.allowance(accounts[0], accounts[1]);
-      postIncrease.minus(10).should.be.bignumber.equal(postDecrease);
+    describe('when the spender is the zero address', function () {
+      const amount = 100;
+      const spender = ZERO_ADDRESS;
+
+      it('approves the requested amount', async function () {
+        await this.token.approve(spender, amount, { from: owner });
+
+        const allowance = await this.token.allowance(owner, spender);
+        assert.equal(allowance, amount);
+      });
+
+      it('emits an approval event', async function () {
+        const { logs } = await this.token.approve(spender, amount, { from: owner });
+
+        assert.equal(logs.length, 1);
+        assert.equal(logs[0].event, 'Approval');
+        assert.equal(logs[0].args.owner, owner);
+        assert.equal(logs[0].args.spender, spender);
+        assert(logs[0].args.value.eq(amount));
+      });
     });
   });
 
-  it('should increase by 50 then set to 0 when decreasing by more than 50', async function () {
-    await token.approve(accounts[1], 50);
-    await token.decreaseApproval(accounts[1], 60);
-    let postDecrease = await token.allowance(accounts[0], accounts[1]);
-    postDecrease.should.be.bignumber.equal(0);
+  describe('transfer from', function () {
+    const spender = recipient;
+
+    describe('when the recipient is not the zero address', function () {
+      const to = anotherAccount;
+
+      describe('when the spender has enough approved balance', function () {
+        beforeEach(async function () {
+          await this.token.approve(spender, 100, { from: owner });
+        });
+
+        describe('when the owner has enough balance', function () {
+          const amount = 100;
+
+          it('transfers the requested amount', async function () {
+            await this.token.transferFrom(owner, to, amount, { from: spender });
+
+            const senderBalance = await this.token.balanceOf(owner);
+            assert.equal(senderBalance, 0);
+
+            const recipientBalance = await this.token.balanceOf(to);
+            assert.equal(recipientBalance, amount);
+          });
+
+          it('decreases the spender allowance', async function () {
+            await this.token.transferFrom(owner, to, amount, { from: spender });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert(allowance.eq(0));
+          });
+
+          it('emits a transfer event', async function () {
+            const { logs } = await this.token.transferFrom(owner, to, amount, { from: spender });
+
+            assert.equal(logs.length, 1);
+            assert.equal(logs[0].event, 'Transfer');
+            assert.equal(logs[0].args.from, owner);
+            assert.equal(logs[0].args.to, to);
+            assert(logs[0].args.value.eq(amount));
+          });
+        });
+
+        describe('when the owner does not have enough balance', function () {
+          const amount = 101;
+
+          it('reverts', async function () {
+            await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender }));
+          });
+        });
+      });
+
+      describe('when the spender does not have enough approved balance', function () {
+        beforeEach(async function () {
+          await this.token.approve(spender, 99, { from: owner });
+        });
+
+        describe('when the owner has enough balance', function () {
+          const amount = 100;
+
+          it('reverts', async function () {
+            await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender }));
+          });
+        });
+
+        describe('when the owner does not have enough balance', function () {
+          const amount = 101;
+
+          it('reverts', async function () {
+            await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender }));
+          });
+        });
+      });
+    });
+
+    describe('when the recipient is the zero address', function () {
+      const amount = 100;
+      const to = ZERO_ADDRESS;
+
+      beforeEach(async function () {
+        await this.token.approve(spender, amount, { from: owner });
+      });
+
+      it('reverts', async function () {
+        await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender }));
+      });
+    });
   });
 
-  it('should throw an error when trying to transfer to 0x0', async function () {
-    let token = await StandardTokenMock.new(accounts[0], 100);
-    await assertRevert(token.transfer(0x0, 100));
+  describe('decrease approval', function () {
+    describe('when the spender is not the zero address', function () {
+      const spender = recipient;
+
+      describe('when the sender has enough balance', function () {
+        const amount = 100;
+
+        it('emits an approval event', async function () {
+          const { logs } = await this.token.decreaseApproval(spender, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Approval');
+          assert.equal(logs[0].args.owner, owner);
+          assert.equal(logs[0].args.spender, spender);
+          assert(logs[0].args.value.eq(0));
+        });
+
+        describe('when there was no approved amount before', function () {
+          it('keeps the allowance to zero', async function () {
+            await this.token.decreaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, 0);
+          });
+        });
+
+        describe('when the spender had an approved amount', function () {
+          beforeEach(async function () {
+            await this.token.approve(spender, amount + 1, { from: owner });
+          });
+
+          it('decreases the spender allowance subtracting the requested amount', async function () {
+            await this.token.decreaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, 1);
+          });
+        });
+      });
+
+      describe('when the sender does not have enough balance', function () {
+        const amount = 101;
+
+        it('emits an approval event', async function () {
+          const { logs } = await this.token.decreaseApproval(spender, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Approval');
+          assert.equal(logs[0].args.owner, owner);
+          assert.equal(logs[0].args.spender, spender);
+          assert(logs[0].args.value.eq(0));
+        });
+
+        describe('when there was no approved amount before', function () {
+          it('keeps the allowance to zero', async function () {
+            await this.token.decreaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, 0);
+          });
+        });
+
+        describe('when the spender had an approved amount', function () {
+          beforeEach(async function () {
+            await this.token.approve(spender, amount + 1, { from: owner });
+          });
+
+          it('decreases the spender allowance subtracting the requested amount', async function () {
+            await this.token.decreaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, 1);
+          });
+        });
+      });
+    });
+
+    describe('when the spender is the zero address', function () {
+      const amount = 100;
+      const spender = ZERO_ADDRESS;
+
+      it('decreases the requested amount', async function () {
+        await this.token.decreaseApproval(spender, amount, { from: owner });
+
+        const allowance = await this.token.allowance(owner, spender);
+        assert.equal(allowance, 0);
+      });
+
+      it('emits an approval event', async function () {
+        const { logs } = await this.token.decreaseApproval(spender, amount, { from: owner });
+
+        assert.equal(logs.length, 1);
+        assert.equal(logs[0].event, 'Approval');
+        assert.equal(logs[0].args.owner, owner);
+        assert.equal(logs[0].args.spender, spender);
+        assert(logs[0].args.value.eq(0));
+      });
+    });
   });
 
-  it('should throw an error when trying to transferFrom to 0x0', async function () {
-    let token = await StandardTokenMock.new(accounts[0], 100);
-    await token.approve(accounts[1], 100);
-    await assertRevert(token.transferFrom(accounts[0], 0x0, 100, { from: accounts[1] }));
+  describe('increase approval', function () {
+    const amount = 100;
+
+    describe('when the spender is not the zero address', function () {
+      const spender = recipient;
+
+      describe('when the sender has enough balance', function () {
+        it('emits an approval event', async function () {
+          const { logs } = await this.token.increaseApproval(spender, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Approval');
+          assert.equal(logs[0].args.owner, owner);
+          assert.equal(logs[0].args.spender, spender);
+          assert(logs[0].args.value.eq(amount));
+        });
+
+        describe('when there was no approved amount before', function () {
+          it('approves the requested amount', async function () {
+            await this.token.increaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount);
+          });
+        });
+
+        describe('when the spender had an approved amount', function () {
+          beforeEach(async function () {
+            await this.token.approve(spender, 1, { from: owner });
+          });
+
+          it('increases the spender allowance adding the requested amount', async function () {
+            await this.token.increaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount + 1);
+          });
+        });
+      });
+
+      describe('when the sender does not have enough balance', function () {
+        const amount = 101;
+
+        it('emits an approval event', async function () {
+          const { logs } = await this.token.increaseApproval(spender, amount, { from: owner });
+
+          assert.equal(logs.length, 1);
+          assert.equal(logs[0].event, 'Approval');
+          assert.equal(logs[0].args.owner, owner);
+          assert.equal(logs[0].args.spender, spender);
+          assert(logs[0].args.value.eq(amount));
+        });
+
+        describe('when there was no approved amount before', function () {
+          it('approves the requested amount', async function () {
+            await this.token.increaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount);
+          });
+        });
+
+        describe('when the spender had an approved amount', function () {
+          beforeEach(async function () {
+            await this.token.approve(spender, 1, { from: owner });
+          });
+
+          it('increases the spender allowance adding the requested amount', async function () {
+            await this.token.increaseApproval(spender, amount, { from: owner });
+
+            const allowance = await this.token.allowance(owner, spender);
+            assert.equal(allowance, amount + 1);
+          });
+        });
+      });
+    });
+
+    describe('when the spender is the zero address', function () {
+      const spender = ZERO_ADDRESS;
+
+      it('approves the requested amount', async function () {
+        await this.token.increaseApproval(spender, amount, { from: owner });
+
+        const allowance = await this.token.allowance(owner, spender);
+        assert.equal(allowance, amount);
+      });
+
+      it('emits an approval event', async function () {
+        const { logs } = await this.token.increaseApproval(spender, amount, { from: owner });
+
+        assert.equal(logs.length, 1);
+        assert.equal(logs[0].event, 'Approval');
+        assert.equal(logs[0].args.owner, owner);
+        assert.equal(logs[0].args.spender, spender);
+        assert(logs[0].args.value.eq(amount));
+      });
+    });
   });
 });