Browse Source

Reorg code

Jorge Izquierdo 8 years ago
parent
commit
929367f0ab
3 changed files with 75 additions and 26 deletions
  1. 54 25
      contracts/token/VestedToken.sol
  2. 18 0
      test/VestedToken.js
  3. 3 1
      truffle.js

+ 54 - 25
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));
   }

+ 18 - 0
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 () => {

+ 3 - 1
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
     }
+    */
   }
 };