From 8d9e12eda38a3b35ffca5d848aefc8bf823e283d Mon Sep 17 00:00:00 2001 From: Jorge Izquierdo Date: Thu, 9 Feb 2017 13:23:12 +0100 Subject: [PATCH] Add GrantableToken tests --- contracts/test-helpers/GrantableTokenMock.sol | 12 ++- contracts/token/GrantableToken.sol | 48 +++++------ package.json | 1 + test/AGrantableToken.js | 66 --------------- test/GrantableToken.js | 81 +++++++++++++++++++ test/helpers/timer.js | 5 ++ 6 files changed, 119 insertions(+), 94 deletions(-) delete mode 100644 test/AGrantableToken.js create mode 100644 test/GrantableToken.js create mode 100644 test/helpers/timer.js diff --git a/contracts/test-helpers/GrantableTokenMock.sol b/contracts/test-helpers/GrantableTokenMock.sol index 3f663caf1..74cadd1d1 100644 --- a/contracts/test-helpers/GrantableTokenMock.sol +++ b/contracts/test-helpers/GrantableTokenMock.sol @@ -1,7 +1,11 @@ pragma solidity ^0.4.4; -import "./StandardTokenMock.sol"; -contract GrantableTokenMock is StandardTokenMock { - function GrantableTokenMock(address initialAccount, uint initialBalance) - StandardTokenMock(initialAccount, initialBalance) {} +import '../token/GrantableToken.sol'; + +// mock class using StandardToken +contract GrantableTokenMock is GrantableToken { + function GrantableTokenMock(address initialAccount, uint initialBalance) { + balances[initialAccount] = initialBalance; + totalSupply = initialBalance; + } } diff --git a/contracts/token/GrantableToken.sol b/contracts/token/GrantableToken.sol index 45153bde9..9f2111b8d 100644 --- a/contracts/token/GrantableToken.sol +++ b/contracts/token/GrantableToken.sol @@ -3,7 +3,7 @@ pragma solidity ^0.4.8; import "./StandardToken.sol"; contract GrantableToken is StandardToken { - struct StockGrant { + struct TokenGrant { address granter; uint256 value; uint64 cliff; @@ -11,28 +11,28 @@ contract GrantableToken is StandardToken { uint64 start; } - mapping (address => StockGrant[]) public grants; + mapping (address => TokenGrant[]) public grants; - function grantStock(address _to, uint256 _value) { + function grantTokens(address _to, uint256 _value) { transfer(_to, _value); } - function grantVestedStock(address _to, uint256 _value, uint64 _start, uint64 _cliff, uint64 _vesting) { + function grantVestedTokens(address _to, uint256 _value, uint64 _start, uint64 _cliff, uint64 _vesting) { if (_cliff < _start) throw; if (_vesting < _start) throw; if (_vesting < _cliff) throw; - StockGrant memory grant = StockGrant({start: _start, value: _value, cliff: _cliff, vesting: _vesting, granter: msg.sender}); + TokenGrant memory grant = TokenGrant({start: _start, value: _value, cliff: _cliff, vesting: _vesting, granter: msg.sender}); grants[_to].push(grant); - grantStock(_to, _value); + grantTokens(_to, _value); } - function revokeStockGrant(address _holder, uint _grantId) { - StockGrant grant = grants[_holder][_grantId]; + function revokeTokenGrant(address _holder, uint _grantId) { + TokenGrant grant = grants[_holder][_grantId]; if (grant.granter != msg.sender) throw; - uint256 nonVested = nonVestedShares(grant, uint64(now)); + uint256 nonVested = nonVestedTokens(grant, uint64(now)); // remove grant from array delete grants[_holder][_grantId]; @@ -43,12 +43,12 @@ contract GrantableToken is StandardToken { balances[_holder] = safeSub(balances[_holder], nonVested); } - function stockGrantCount(address _holder) constant returns (uint index) { + function tokenGrantsCount(address _holder) constant returns (uint index) { return grants[_holder].length; } - function stockGrant(address _holder, uint _grantId) constant returns (address granter, uint256 value, uint256 vested, uint64 start, uint64 cliff, uint64 vesting) { - StockGrant grant = grants[_holder][_grantId]; + function tokenGrant(address _holder, uint _grantId) constant returns (address granter, uint256 value, uint256 vested, uint64 start, uint64 cliff, uint64 vesting) { + TokenGrant grant = grants[_holder][_grantId]; granter = grant.granter; value = grant.value; @@ -56,26 +56,26 @@ contract GrantableToken is StandardToken { cliff = grant.cliff; vesting = grant.vesting; - vested = vestedShares(grant, uint64(now)); + vested = vestedTokens(grant, uint64(now)); } - function vestedShares(StockGrant grant, uint64 time) private constant returns (uint256 vestedShares) { + function vestedTokens(TokenGrant grant, uint64 time) private constant returns (uint256 vestedTokens) { if (time < grant.cliff) return 0; if (time > grant.vesting) return grant.value; - uint256 cliffShares = grant.value * uint256(grant.cliff - grant.start) / uint256(grant.vesting - grant.start); - vestedShares = cliffShares; + uint256 cliffTokens = grant.value * uint256(grant.cliff - grant.start) / uint256(grant.vesting - grant.start); + vestedTokens = cliffTokens; - uint256 vestingShares = safeSub(grant.value, cliffShares); + uint256 vestingTokens = safeSub(grant.value, cliffTokens); - vestedShares = safeAdd(vestedShares, vestingShares * (time - uint256(grant.cliff)) / uint256(grant.vesting - grant.start)); + vestedTokens = safeAdd(vestedTokens, vestingTokens * (time - uint256(grant.cliff)) / uint256(grant.vesting - grant.start)); } - function nonVestedShares(StockGrant grant, uint64 time) private constant returns (uint256) { - return safeSub(grant.value, vestedShares(grant, time)); + function nonVestedTokens(TokenGrant grant, uint64 time) private constant returns (uint256) { + return safeSub(grant.value, vestedTokens(grant, time)); } - function lastStockIsTransferrableEvent(address holder) constant public returns (uint64 date) { + function lastTokenIsTransferrableEvent(address holder) constant public returns (uint64 date) { date = uint64(now); uint256 grantIndex = grants[holder].length; for (uint256 i = 0; i < grantIndex; i++) { @@ -83,18 +83,18 @@ contract GrantableToken is StandardToken { } } - function transferrableShares(address holder, uint64 time) constant public returns (uint256 nonVested) { + function transferrableTokens(address holder, uint64 time) constant public returns (uint256 nonVested) { uint256 grantIndex = grants[holder].length; for (uint256 i = 0; i < grantIndex; i++) { - nonVested = safeAdd(nonVested, nonVestedShares(grants[holder][i], time)); + nonVested = safeAdd(nonVested, nonVestedTokens(grants[holder][i], time)); } return safeSub(balances[holder], nonVested); } function transfer(address _to, uint _value) returns (bool success){ - if (_value > transferrableShares(msg.sender, uint64(now))) throw; + if (_value > transferrableTokens(msg.sender, uint64(now))) throw; return super.transfer(_to, _value); } diff --git a/package.json b/package.json index 91a2eb65d..24e5ff5dd 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "scripts": { "test": "truffle test", + "console": "truffle console", "install": "scripts/install.sh" }, "repository": { diff --git a/test/AGrantableToken.js b/test/AGrantableToken.js deleted file mode 100644 index 0bfbd5421..000000000 --- a/test/AGrantableToken.js +++ /dev/null @@ -1,66 +0,0 @@ -const assertJump = require('./helpers/assertJump'); - -contract('GrantableToken', function(accounts) { - - it("should return the correct totalSupply after construction", async function() { - let token = await StandardTokenMock.new(accounts[0], 100); - let totalSupply = await token.totalSupply(); - - assert.equal(totalSupply, 100); - }) - - it("should return the correct allowance amount after approval", async function() { - let token = await StandardTokenMock.new(); - let approve = await token.approve(accounts[1], 100); - let allowance = await token.allowance(accounts[0], accounts[1]); - - assert.equal(allowance, 100); - }); - - it("should return correct balances after transfer", async function() { - let token = await StandardTokenMock.new(accounts[0], 100); - let transfer = await token.transfer(accounts[1], 100); - let balance0 = await token.balanceOf(accounts[0]); - assert.equal(balance0, 0); - - let balance1 = await token.balanceOf(accounts[1]); - assert.equal(balance1, 100); - }); - - it("should throw an error when trying to transfer more than balance", async function() { - let token = await StandardTokenMock.new(accounts[0], 100); - try { - let transfer = await token.transfer(accounts[1], 101); - } catch(error) { - return assertJump(error); - } - assert.fail('should have thrown before'); - }); - - it("should return correct balances after transfering from another account", async function() { - let token = await StandardTokenMock.new(accounts[0], 100); - let approve = await token.approve(accounts[1], 100); - let transferFrom = await token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]}); - - let balance0 = await token.balanceOf(accounts[0]); - assert.equal(balance0, 0); - - let balance1 = await token.balanceOf(accounts[2]); - assert.equal(balance1, 100); - - let balance2 = await token.balanceOf(accounts[1]); - assert.equal(balance2, 0); - }); - - it("should throw an error when trying to transfer more than allowed", async function() { - let token = await StandardTokenMock.new(); - let approve = await token.approve(accounts[1], 99); - try { - let transfer = await token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]}); - } catch (error) { - return assertJump(error); - } - assert.fail('should have thrown before'); - }); - -}); diff --git a/test/GrantableToken.js b/test/GrantableToken.js new file mode 100644 index 000000000..1f423e116 --- /dev/null +++ b/test/GrantableToken.js @@ -0,0 +1,81 @@ +const assertJump = require('./helpers/assertJump'); +const timer = require('./helpers/timer'); + +contract('GrantableToken', function(accounts) { + let token = null + let now = 0 + + const tokenAmount = 50 + + const granter = accounts[0] + const receiver = accounts[1] + + beforeEach(async () => { + token = await GrantableTokenMock.new(granter, 100); + now = +new Date()/1000; + }) + + it('granter can grant tokens without vesting', async () => { + await token.grantTokens(receiver, tokenAmount, { from: granter }) + + assert.equal(await token.balanceOf(receiver), tokenAmount); + assert.equal(await token.transferrableTokens(receiver, +new Date()/1000), tokenAmount); + }) + + describe('getting a token grant', async () => { + const cliff = 1 + const vesting = 2 // seconds + + beforeEach(async () => { + await token.grantVestedTokens(receiver, tokenAmount, now, now + cliff, now + vesting, { from: granter }) + }) + + it('tokens are received', async () => { + assert.equal(await token.balanceOf(receiver), tokenAmount); + }) + + it('has 0 transferrable tokens before cliff', async () => { + assert.equal(await token.transferrableTokens(receiver, now), 0); + }) + + it('all tokens are transferrable after vesting', async () => { + assert.equal(await token.transferrableTokens(receiver, now + vesting + 1), tokenAmount); + }) + + it('throws when trying to transfer non vested tokens', async () => { + try { + await token.transfer(accounts[7], 1, { from: receiver }) + } catch(error) { + return assertJump(error); + } + assert.fail('should have thrown before'); + }) + + it('can be revoked by granter', async () => { + await token.revokeTokenGrant(receiver, 0, { from: granter }); + assert.equal(await token.balanceOf(receiver), 0); + assert.equal(await token.balanceOf(granter), 100); + }) + + it('cannot be revoked by non granter', async () => { + try { + await token.revokeTokenGrant(receiver, 0, { from: accounts[3] }); + } catch(error) { + return assertJump(error); + } + assert.fail('should have thrown before'); + }) + + it('can be revoked by granter and non vested tokens are returned', async () => { + await timer(cliff); + await token.revokeTokenGrant(receiver, 0, { from: granter }); + assert.equal(await token.balanceOf(receiver), tokenAmount * cliff / vesting); + }) + + it('can transfer all tokens after vesting ends', async () => { + await timer(vesting + 1); + await token.transfer(accounts[7], tokenAmount, { from: receiver }) + assert.equal(await token.balanceOf(accounts[7]), tokenAmount); + }) + }) +}); diff --git a/test/helpers/timer.js b/test/helpers/timer.js new file mode 100644 index 000000000..ca969bcd0 --- /dev/null +++ b/test/helpers/timer.js @@ -0,0 +1,5 @@ +module.exports = s => { + return new Promise(resolve => { + setTimeout(() => resolve(), s * 1000 + 600) // 600ms breathing room for testrpc to sync + }) +}