From 929367f0abde25b7106438070f5bdf12a6f46da3 Mon Sep 17 00:00:00 2001 From: Jorge Izquierdo Date: Sun, 21 May 2017 12:10:04 +0200 Subject: [PATCH] Reorg code --- contracts/token/VestedToken.sol | 79 ++++++++++++++++++++++----------- test/VestedToken.js | 18 ++++++++ truffle.js | 4 +- 3 files changed, 75 insertions(+), 26 deletions(-) diff --git a/contracts/token/VestedToken.sol b/contracts/token/VestedToken.sol index 53408071f..cb42a7ee0 100644 --- a/contracts/token/VestedToken.sol +++ b/contracts/token/VestedToken.sol @@ -75,19 +75,71 @@ contract VestedToken is StandardToken, TransferableToken { Transfer(_holder, receiver, nonVested); } - function transferableTokens(address holder, uint64 time) constant public returns (uint256 nonVested) { + function transferableTokens(address holder, uint64 time) constant public returns (uint256) { uint256 grantIndex = tokenGrantsCount(holder); + + if (grantIndex == 0) return balanceOf(holder); // shortcut for holder without grants + + // Iterate through all the grants the holder has, and add all non-vested tokens + uint256 nonVested = 0; for (uint256 i = 0; i < grantIndex; i++) { nonVested = safeAdd(nonVested, nonVestedTokens(grants[holder][i], time)); } - return min256(safeSub(balances[holder], nonVested), super.transferableTokens(holder, time)); + // Balance - totalNonVested is the amount of tokens a holder can transfer at any given time + uint256 vestedTransferable = safeSub(balanceOf(holder), nonVested); + + // Return the minimum of how many vested can transfer and other value + // in case there are other limiting transferability factors (default is balanceOf) + return min256(vestedTransferable, super.transferableTokens(holder, time)); } function tokenGrantsCount(address _holder) constant returns (uint index) { return grants[_holder].length; } + // transferableTokens + // | _/-------- vestedTokens rect + // | _/ + // | _/ + // | _/ + // | _/ + // | / + // | .| + // | . | + // | . | + // | . | + // | . | + // | . | + // +===+===========+---------+----------> time + // Start Clift Vesting + function calculateVestedTokens( + uint256 tokens, + uint256 time, + uint256 start, + uint256 cliff, + uint256 vesting) constant returns (uint256) + { + // Shortcuts for before cliff and after vesting cases. + if (time < cliff) return 0; + if (time >= vesting) return tokens; + + // Interpolate all vested tokens. + // As before cliff the shortcut returns 0, we can use just calculate a value + // in the vesting rect (as shown in above's figure) + + // vestedTokens = tokens * (time - start) / (vesting - start) + uint256 vestedTokens = safeDiv( + safeMul( + tokens, + safeSub(time, start) + ), + safeSub(vesting, start) + ); + + return vestedTokens; + } + function tokenGrant(address _holder, uint _grantId) constant returns (address granter, uint256 value, uint256 vested, uint64 start, uint64 cliff, uint64 vesting, bool revokable, bool burnsOnRevoke) { TokenGrant grant = grants[_holder][_grantId]; @@ -112,29 +164,6 @@ contract VestedToken is StandardToken, TransferableToken { ); } - function calculateVestedTokens( - uint256 tokens, - uint256 time, - uint256 start, - uint256 cliff, - uint256 vesting) constant returns (uint256 vestedTokens) - { - - if (time < cliff) { - return 0; - } - if (time > vesting) { - return tokens; - } - - uint256 cliffTokens = safeDiv(safeMul(tokens, safeSub(cliff, start)), safeSub(vesting, start)); - vestedTokens = cliffTokens; - - uint256 vestingTokens = safeSub(tokens, cliffTokens); - - vestedTokens = safeAdd(vestedTokens, safeDiv(safeMul(vestingTokens, safeSub(time, cliff)), safeSub(vesting, start))); - } - function nonVestedTokens(TokenGrant grant, uint64 time) private constant returns (uint256) { return safeSub(grant.value, vestedTokens(grant, time)); } diff --git a/test/VestedToken.js b/test/VestedToken.js index 8f65c333d..bf59ce77f 100644 --- a/test/VestedToken.js +++ b/test/VestedToken.js @@ -95,6 +95,24 @@ contract('VestedToken', function(accounts) { await token.transferFrom(receiver, accounts[7], tokenAmount, { from: accounts[7] }) assert.equal(await token.balanceOf(accounts[7]), tokenAmount); }) + + it('can handle composed vesting schedules', async () => { + await timer(cliff); + await token.transfer(accounts[7], 12, { from: receiver }) + assert.equal(await token.balanceOf(accounts[7]), 12); + + let newNow = web3.eth.getBlock(web3.eth.blockNumber).timestamp + + await token.grantVestedTokens(receiver, tokenAmount, newNow, newNow + cliff, newNow + vesting, false, false, { from: granter }) + await token.transfer(accounts[7], 13, { from: receiver }) + assert.equal(await token.balanceOf(accounts[7]), tokenAmount / 2); + + assert.equal(await token.balanceOf(receiver), 3 * tokenAmount / 2) + assert.equal(await token.transferableTokens(receiver, newNow), 0) + await timer(vesting); + await token.transfer(accounts[7], 3 * tokenAmount / 2, { from: receiver }) + assert.equal(await token.balanceOf(accounts[7]), tokenAmount * 2) + }) }) describe('getting a non-revokable token grant', async () => { diff --git a/truffle.js b/truffle.js index d101dc5bf..abe8d3561 100644 --- a/truffle.js +++ b/truffle.js @@ -4,7 +4,7 @@ require('babel-polyfill'); var HDWalletProvider = require('truffle-hdwallet-provider'); var mnemonic = '[REDACTED]'; -var provider = new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/'); +// var provider = new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/'); module.exports = { @@ -14,9 +14,11 @@ module.exports = { port: 8545, network_id: '*' }, + /* ropsten: { provider: provider, network_id: 3 // official id of the ropsten network } + */ } };