Selaa lähdekoodia

add a TokenTimelock contract

Francisco Giordano 8 vuotta sitten
vanhempi
sitoutus
b3ca0c73b3

+ 41 - 0
contracts/token/TokenTimelock.sol

@@ -0,0 +1,41 @@
+pragma solidity ^0.4.11;
+
+
+import './ERC20Basic.sol';
+
+/**
+ * @title TokenTimelock
+ * @dev TokenTimelock is a token holder contract that will allow a 
+ * beneficiary to extract the tokens after a time has passed
+ */
+contract TokenTimelock {
+  
+  // ERC20 basic token contract being held
+  ERC20Basic token;
+
+  // beneficiary of tokens after they are released
+  address beneficiary;
+
+  // timestamp where token release is enabled
+  uint releaseTime;
+
+  function TokenTimelock(ERC20Basic _token, address _beneficiary, uint _releaseTime) {
+    require(_releaseTime > now);
+    token = _token;
+    beneficiary = _beneficiary;
+    releaseTime = _releaseTime;
+  }
+
+  /**
+   * @dev beneficiary claims tokens held by time lock
+   */
+  function claim() {
+    require(msg.sender == beneficiary);
+    require(now >= releaseTime);
+
+    uint amount = token.balanceOf(this);
+    require(amount > 0);
+
+    token.transfer(beneficiary, amount);
+  }
+}

+ 4 - 0
package.json

@@ -35,9 +35,13 @@
     "babel-preset-stage-2": "^6.18.0",
     "babel-preset-stage-3": "^6.17.0",
     "babel-register": "^6.23.0",
+    "chai": "^4.0.2",
+    "chai-as-promised": "^7.0.0",
+    "chai-bignumber": "^2.0.0",
     "coveralls": "^2.13.1",
     "ethereumjs-testrpc": "^3.0.2",
     "mocha-lcov-reporter": "^1.3.0",
+    "moment": "^2.18.1",
     "solidity-coverage": "^0.1.0",
     "truffle": "3.2.2"
   }

+ 58 - 0
test/TokenTimelock.js

@@ -0,0 +1,58 @@
+const BigNumber = web3.BigNumber
+
+require('chai')
+  .use(require('chai-as-promised'))
+  .use(require('chai-bignumber')(BigNumber))
+  .should()
+
+import moment from 'moment'
+
+import latestTime from './helpers/latestTime'
+import increaseTime from './helpers/increaseTime'
+
+const MintableToken = artifacts.require('MintableToken')
+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 = latestTime().add(1, 'year').unix()
+    this.timelock = await TokenTimelock.new(this.token.address, beneficiary, this.releaseTime)
+    await this.token.mint(this.timelock.address, amount, {from: owner})
+  })
+
+  it('cannot be claimed before time limit', async function () {
+    await this.timelock.claim({from: beneficiary}).should.be.rejected
+  })
+
+  it('cannot be claimed just before time limit', async function () {
+    await increaseTime(moment.duration(0.99, 'year'))
+    await this.timelock.claim({from: beneficiary}).should.be.rejected
+  })
+
+  it('can be claimed just after limit', async function () {
+    await increaseTime(moment.duration(1.01, 'year'))
+    await this.timelock.claim({from: beneficiary}).should.be.fulfilled
+    const balance = await this.token.balanceOf(beneficiary)
+    balance.should.be.bignumber.equal(amount)
+  })
+
+  it('can be claimed after time limit', async function () {
+    await increaseTime(moment.duration(2, 'year'))
+    await this.timelock.claim({from: beneficiary}).should.be.fulfilled
+    const balance = await this.token.balanceOf(beneficiary)
+    balance.should.be.bignumber.equal(amount)
+  })
+
+  it('cannot be claimed twice', async function () {
+    await increaseTime(moment.duration(2, 'year'))
+    await this.timelock.claim({from: beneficiary}).should.be.fulfilled
+    await this.timelock.claim({from: beneficiary}).should.be.rejected
+    const balance = await this.token.balanceOf(beneficiary)
+    balance.should.be.bignumber.equal(amount)
+  })
+
+})

+ 23 - 0
test/helpers/increaseTime.js

@@ -0,0 +1,23 @@
+// Increases testrpc time by the passed duration (a moment.js instance)
+export default function increaseTime(duration) {
+  const id = Date.now()
+
+  return new Promise((resolve, reject) => {
+    web3.currentProvider.sendAsync({
+      jsonrpc: '2.0',
+      method: 'evm_increaseTime',
+      params: [duration.asSeconds()],
+      id: id,
+    }, err1 => {
+      if (err1) return reject(err1)
+
+      web3.currentProvider.sendAsync({
+        jsonrpc: '2.0',
+        method: 'evm_mine',
+        id: id+1,
+      }, (err2, res) => {
+        return err2 ? reject(err2) : resolve(res)
+      })
+    })
+  })
+}

+ 6 - 0
test/helpers/latestTime.js

@@ -0,0 +1,6 @@
+import moment from 'moment'
+
+// Returns a moment.js instance representing the time of the last mined block
+export default function latestTime() {
+  return moment.unix(web3.eth.getBlock('latest').timestamp)
+}