Pārlūkot izejas kodu

add tests for crowdsale contracts

Francisco Giordano 8 gadi atpakaļ
vecāks
revīzija
fadb2cf47e

+ 3 - 0
package.json

@@ -35,6 +35,9 @@
     "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",

+ 81 - 0
test/CappedCrowdsale.js

@@ -0,0 +1,81 @@
+import ether from './helpers/ether'
+import advanceToBlock from './helpers/advanceToBlock'
+import EVMThrow from './helpers/EVMThrow'
+
+const BigNumber = web3.BigNumber
+
+require('chai')
+  .use(require('chai-as-promised'))
+  .use(require('chai-bignumber')(BigNumber))
+  .should()
+
+const CappedCrowdsale = artifacts.require('./helpers/CappedCrowdsaleImpl.sol')
+const MintableToken = artifacts.require('MintableToken')
+
+contract('CappedCrowdsale', function ([_, wallet]) {
+
+  const rate = new BigNumber(1000)
+
+  const cap = ether(300)
+  const lessThanCap = ether(60)
+
+  beforeEach(async function () {
+    this.startBlock = web3.eth.blockNumber + 10
+    this.endBlock =   web3.eth.blockNumber + 20
+
+    this.crowdsale = await CappedCrowdsale.new(this.startBlock, this.endBlock, rate, wallet, cap)
+
+    this.token = MintableToken.at(await this.crowdsale.token())
+  })
+
+  describe('accepting payments', function () {
+
+    beforeEach(async function () {
+      await advanceToBlock(this.startBlock - 1)
+    })
+
+    it('should accept payments within cap', async function () {
+      await this.crowdsale.send(cap.minus(lessThanCap)).should.be.fulfilled
+      await this.crowdsale.send(lessThanCap).should.be.fulfilled
+    })
+
+    it('should reject payments outside cap', async function () {
+      await this.crowdsale.send(cap)
+      await this.crowdsale.send(1).should.be.rejectedWith(EVMThrow)
+    })
+
+    it('should reject payments that exceed cap', async function () {
+      await this.crowdsale.send(cap.plus(1)).should.be.rejectedWith(EVMThrow)
+    })
+
+  })
+
+  describe('ending', function () {
+
+    beforeEach(async function () {
+      await advanceToBlock(this.startBlock - 1)
+    })
+
+    it('should not be ended if under cap', async function () {
+      let hasEnded = await this.crowdsale.hasEnded()
+      hasEnded.should.equal(false)
+      await this.crowdsale.send(lessThanCap)
+      hasEnded = await this.crowdsale.hasEnded()
+      hasEnded.should.equal(false)
+    })
+
+    it('should not be ended if just under cap', async function () {
+      await this.crowdsale.send(cap.minus(1))
+      let hasEnded = await this.crowdsale.hasEnded()
+      hasEnded.should.equal(false)
+    })
+
+    it('should be ended if cap reached', async function () {
+      await this.crowdsale.send(cap)
+      let hasEnded = await this.crowdsale.hasEnded()
+      hasEnded.should.equal(true)
+    })
+
+  })
+
+})

+ 143 - 0
test/Crowdsale.js

@@ -0,0 +1,143 @@
+import ether from './helpers/ether'
+import advanceToBlock from './helpers/advanceToBlock'
+import EVMThrow from './helpers/EVMThrow'
+
+const BigNumber = web3.BigNumber
+
+const should = require('chai')
+  .use(require('chai-as-promised'))
+  .use(require('chai-bignumber')(BigNumber))
+  .should()
+
+const Crowdsale = artifacts.require('Crowdsale')
+const MintableToken = artifacts.require('MintableToken')
+
+contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
+
+  const rate = new BigNumber(1000)
+  const value = ether(42)
+
+  const expectedTokenAmount = rate.mul(value)
+
+  beforeEach(async function () {
+    this.startBlock = web3.eth.blockNumber + 10
+    this.endBlock =   web3.eth.blockNumber + 20
+
+    this.crowdsale = await Crowdsale.new(this.startBlock, this.endBlock, rate, wallet)
+
+    this.token = MintableToken.at(await this.crowdsale.token())
+  })
+
+  it('should be token owner', async function () {
+    const owner = await this.token.owner()
+    owner.should.equal(this.crowdsale.address)
+  })
+
+  it('should be ended only after end', async function () {
+    let ended = await this.crowdsale.hasEnded()
+    ended.should.equal(false)
+    await advanceToBlock(this.endBlock + 1)
+    ended = await this.crowdsale.hasEnded()
+    ended.should.equal(true)
+  })
+
+  describe('accepting payments', function () {
+
+    it('should reject payments before start', async function () {
+      await this.crowdsale.send(value).should.be.rejectedWith(EVMThrow)
+      await this.crowdsale.buyTokens(investor, value, {from: purchaser}).should.be.rejectedWith(EVMThrow)
+    })
+
+    it('should accept payments after start', async function () {
+      await advanceToBlock(this.startBlock - 1)
+      await this.crowdsale.send(value).should.be.fulfilled
+      await this.crowdsale.buyTokens(investor, {value: value, from: purchaser}).should.be.fulfilled
+    })
+
+    it('should reject payments after end', async function () {
+      await advanceToBlock(this.endBlock)
+      await this.crowdsale.send(value).should.be.rejectedWith(EVMThrow)
+      await this.crowdsale.buyTokens(investor, {value: value, from: purchaser}).should.be.rejectedWith(EVMThrow)
+    })
+
+  })
+
+  describe('high-level purchase', function () {
+
+    beforeEach(async function() {
+      await advanceToBlock(this.startBlock)
+    })
+
+    it('should log purchase', async function () {
+      const {logs} = await this.crowdsale.sendTransaction({value: value, from: investor})
+
+      const event = logs.find(e => e.event === 'TokenPurchase')
+
+      should.exist(event)
+      event.args.purchaser.should.equal(investor)
+      event.args.beneficiary.should.equal(investor)
+      event.args.value.should.be.bignumber.equal(value)
+      event.args.amount.should.be.bignumber.equal(expectedTokenAmount)
+    })
+
+    it('should increase totalSupply', async function () {
+      await this.crowdsale.send(value)
+      const totalSupply = await this.token.totalSupply()
+      totalSupply.should.be.bignumber.equal(expectedTokenAmount)
+    })
+
+    it('should assign tokens to sender', async function () {
+      await this.crowdsale.sendTransaction({value: value, from: investor})
+      let balance = await this.token.balanceOf(investor);
+      balance.should.be.bignumber.equal(expectedTokenAmount)
+    })
+
+    it('should forward funds to wallet', async function () {
+      const pre = web3.eth.getBalance(wallet)
+      await this.crowdsale.sendTransaction({value, from: investor})
+      const post = web3.eth.getBalance(wallet)
+      post.minus(pre).should.be.bignumber.equal(value)
+    })
+
+  })
+
+  describe('low-level purchase', function () {
+
+    beforeEach(async function() {
+      await advanceToBlock(this.startBlock)
+    })
+
+    it('should log purchase', async function () {
+      const {logs} = await this.crowdsale.buyTokens(investor, {value: value, from: purchaser})
+
+      const event = logs.find(e => e.event === 'TokenPurchase')
+
+      should.exist(event)
+      event.args.purchaser.should.equal(purchaser)
+      event.args.beneficiary.should.equal(investor)
+      event.args.value.should.be.bignumber.equal(value)
+      event.args.amount.should.be.bignumber.equal(expectedTokenAmount)
+    })
+
+    it('should increase totalSupply', async function () {
+      await this.crowdsale.buyTokens(investor, {value, from: purchaser})
+      const totalSupply = await this.token.totalSupply()
+      totalSupply.should.be.bignumber.equal(expectedTokenAmount)
+    })
+
+    it('should assign tokens to beneficiary', async function () {
+      await this.crowdsale.buyTokens(investor, {value, from: purchaser})
+      const balance = await this.token.balanceOf(investor)
+      balance.should.be.bignumber.equal(expectedTokenAmount)
+    })
+
+    it('should forward funds to wallet', async function () {
+      const pre = web3.eth.getBalance(wallet)
+      await this.crowdsale.buyTokens(investor, {value, from: purchaser})
+      const post = web3.eth.getBalance(wallet)
+      post.minus(pre).should.be.bignumber.equal(value)
+    })
+
+  })
+
+})

+ 61 - 0
test/FinalizableCrowdsale.js

@@ -0,0 +1,61 @@
+import advanceToBlock from './helpers/advanceToBlock'
+import EVMThrow from './helpers/EVMThrow'
+
+const BigNumber = web3.BigNumber
+
+const should = require('chai')
+  .use(require('chai-as-promised'))
+  .use(require('chai-bignumber')(BigNumber))
+  .should()
+
+const FinalizableCrowdsale = artifacts.require('./helpers/FinalizableCrowdsaleImpl.sol')
+const MintableToken = artifacts.require('MintableToken')
+
+contract('FinalizableCrowdsale', function ([_, owner, wallet, thirdparty]) {
+
+  const rate = new BigNumber(1000)
+
+  beforeEach(async function () {
+    this.startBlock = web3.eth.blockNumber + 10
+    this.endBlock = web3.eth.blockNumber + 20
+
+    this.crowdsale = await FinalizableCrowdsale.new(this.startBlock, this.endBlock, rate, wallet, {from: owner})
+
+    this.token = MintableToken.at(await this.crowdsale.token())
+  })
+
+  it('cannot be finalized before ending', async function () {
+    await this.crowdsale.finalize({from: owner}).should.be.rejectedWith(EVMThrow)
+  })
+
+  it('cannot be finalized by third party after ending', async function () {
+    await advanceToBlock(this.endBlock)
+    await this.crowdsale.finalize({from: thirdparty}).should.be.rejectedWith(EVMThrow)
+  })
+
+  it('can be finalized by owner after ending', async function () {
+    await advanceToBlock(this.endBlock)
+    await this.crowdsale.finalize({from: owner}).should.be.fulfilled
+  })
+
+  it('cannot be finalized twice', async function () {
+    await advanceToBlock(this.endBlock + 1)
+    await this.crowdsale.finalize({from: owner})
+    await this.crowdsale.finalize({from: owner}).should.be.rejectedWith(EVMThrow)
+  })
+
+  it('logs finalized', async function () {
+    await advanceToBlock(this.endBlock)
+    const {logs} = await this.crowdsale.finalize({from: owner})
+    const event = logs.find(e => e.event === 'Finalized')
+    should.exist(event)
+  })
+
+  it('finishes minting of token', async function () {
+    await advanceToBlock(this.endBlock)
+    await this.crowdsale.finalize({from: owner})
+    const finished = await this.token.mintingFinished()
+    finished.should.equal(true)
+  })
+
+})

+ 61 - 0
test/RefundVault.js

@@ -0,0 +1,61 @@
+const BigNumber = web3.BigNumber
+
+require('chai')
+  .use(require('chai-as-promised'))
+  .use(require('chai-bignumber')(BigNumber))
+  .should()
+
+import ether from './helpers/ether'
+import EVMThrow from './helpers/EVMThrow'
+
+const RefundVault = artifacts.require('RefundVault')
+
+contract('RefundVault', function ([_, owner, wallet, investor]) {
+
+  const value = ether(42)
+
+  beforeEach(async function () {
+    this.vault = await RefundVault.new(wallet, {from: owner})
+  })
+
+  it('should accept contributions', async function () {
+    await this.vault.deposit(investor, {value, from: owner}).should.be.fulfilled
+  })
+
+  it('should not refund contribution during active state', async function () {
+    await this.vault.deposit(investor, {value, from: owner})
+    await this.vault.refund(investor).should.be.rejectedWith(EVMThrow)
+  })
+
+  it('only owner can enter refund mode', async function () {
+    await this.vault.enableRefunds({from: _}).should.be.rejectedWith(EVMThrow)
+    await this.vault.enableRefunds({from: owner}).should.be.fulfilled
+  })
+
+  it('should refund contribution after entering refund mode', async function () {
+    await this.vault.deposit(investor, {value, from: owner})
+    await this.vault.enableRefunds({from: owner})
+
+    const pre = web3.eth.getBalance(investor)
+    await this.vault.refund(investor)
+    const post = web3.eth.getBalance(investor)
+
+    post.minus(pre).should.be.bignumber.equal(value)
+  })
+
+  it('only owner can close', async function () {
+    await this.vault.close({from: _}).should.be.rejectedWith(EVMThrow)
+    await this.vault.close({from: owner}).should.be.fulfilled
+  })
+
+  it('should forward funds to wallet after closing', async function () {
+    await this.vault.deposit(investor, {value, from: owner})
+
+    const pre = web3.eth.getBalance(wallet)
+    await this.vault.close({from: owner})
+    const post = web3.eth.getBalance(wallet)
+
+    post.minus(pre).should.be.bignumber.equal(value)
+  })
+
+})

+ 67 - 0
test/RefundableCrowdsale.js

@@ -0,0 +1,67 @@
+import ether from './helpers/ether'
+import advanceToBlock from './helpers/advanceToBlock'
+import EVMThrow from './helpers/EVMThrow'
+
+const BigNumber = web3.BigNumber
+
+require('chai')
+  .use(require('chai-as-promised'))
+  .use(require('chai-bignumber')(BigNumber))
+  .should()
+
+const RefundableCrowdsale = artifacts.require('./helpers/RefundableCrowdsaleImpl.sol')
+
+contract('RefundableCrowdsale', function ([_, owner, wallet, investor]) {
+
+  const rate = new BigNumber(1000)
+  const goal = ether(800)
+  const lessThanGoal = ether(750)
+
+  beforeEach(async function () {
+    this.startBlock = web3.eth.blockNumber + 10
+    this.endBlock =   web3.eth.blockNumber + 20
+
+    this.crowdsale = await RefundableCrowdsale.new(this.startBlock, this.endBlock, rate, wallet, goal, {from: owner})
+  })
+
+  it('should deny refunds before end', async function () {
+    await this.crowdsale.claimRefund({from: investor}).should.be.rejectedWith(EVMThrow)
+    await advanceToBlock(this.endBlock - 1)
+    await this.crowdsale.claimRefund({from: investor}).should.be.rejectedWith(EVMThrow)
+  })
+
+  it('should deny refunds after end if goal was reached', async function () {
+    await advanceToBlock(this.startBlock - 1)
+    await this.crowdsale.sendTransaction({value: goal, from: investor})
+    await advanceToBlock(this.endBlock)
+    await this.crowdsale.claimRefund({from: investor}).should.be.rejectedWith(EVMThrow)
+  })
+
+  it('should allow refunds after end if goal was not reached', async function () {
+    await advanceToBlock(this.startBlock - 1)
+    await this.crowdsale.sendTransaction({value: lessThanGoal, from: investor})
+    await advanceToBlock(this.endBlock)
+
+    await this.crowdsale.finalize({from: owner})
+
+    const pre = web3.eth.getBalance(investor)
+    await this.crowdsale.claimRefund({from: investor, gasPrice: 0})
+			.should.be.fulfilled
+    const post = web3.eth.getBalance(investor)
+
+    post.minus(pre).should.be.bignumber.equal(lessThanGoal)
+  })
+
+  it('should forward funds to wallet after end if goal was reached', async function () {
+    await advanceToBlock(this.startBlock - 1)
+    await this.crowdsale.sendTransaction({value: goal, from: investor})
+    await advanceToBlock(this.endBlock)
+
+    const pre = web3.eth.getBalance(wallet)
+    await this.crowdsale.finalize({from: owner})
+    const post = web3.eth.getBalance(wallet)
+
+    post.minus(pre).should.be.bignumber.equal(goal)
+  })
+
+})

+ 21 - 0
test/helpers/CappedCrowdsaleImpl.sol

@@ -0,0 +1,21 @@
+pragma solidity ^0.4.11;
+
+
+import '../../contracts/crowdsale/CappedCrowdsale.sol';
+
+
+contract CappedCrowdsaleImpl is CappedCrowdsale {
+
+  function CappedCrowdsaleImpl (
+    uint256 _startBlock,
+    uint256 _endBlock,
+    uint256 _rate,
+    address _wallet,
+    uint256 _cap
+  )
+    Crowdsale(_startBlock, _endBlock, _rate, _wallet)
+    CappedCrowdsale(_cap) 
+  {
+  }
+
+}

+ 1 - 0
test/helpers/EVMThrow.js

@@ -0,0 +1 @@
+export default 'invalid opcode'

+ 20 - 0
test/helpers/FinalizableCrowdsaleImpl.sol

@@ -0,0 +1,20 @@
+pragma solidity ^0.4.11;
+
+
+import '../../contracts/crowdsale/FinalizableCrowdsale.sol';
+
+
+contract FinalizableCrowdsaleImpl is FinalizableCrowdsale {
+
+  function FinalizableCrowdsaleImpl (
+    uint256 _startBlock,
+    uint256 _endBlock,
+    uint256 _rate,
+    address _wallet
+  )
+    Crowdsale(_startBlock, _endBlock, _rate, _wallet)
+    FinalizableCrowdsale() 
+  {
+  }
+
+}

+ 21 - 0
test/helpers/RefundableCrowdsaleImpl.sol

@@ -0,0 +1,21 @@
+pragma solidity ^0.4.11;
+
+
+import '../../contracts/crowdsale/RefundableCrowdsale.sol';
+
+
+contract RefundableCrowdsaleImpl is RefundableCrowdsale {
+
+  function RefundableCrowdsaleImpl (
+    uint256 _startBlock,
+    uint256 _endBlock,
+    uint256 _rate,
+    address _wallet,
+    uint256 _goal
+  )
+    Crowdsale(_startBlock, _endBlock, _rate, _wallet)
+    RefundableCrowdsale(_goal) 
+  {
+  }
+
+}

+ 22 - 0
test/helpers/advanceToBlock.js

@@ -0,0 +1,22 @@
+export function advanceBlock() {
+  return new Promise((resolve, reject) => {
+    web3.currentProvider.sendAsync({
+      jsonrpc: '2.0',
+      method: 'evm_mine',
+      id: Date.now(),
+    }, (err, res) => {
+      return err ? reject(err) : resolve(res)
+    })
+  })
+}
+
+// Advances the block number so that the last mined block is `number`.
+export default async function advanceToBlock(number) {
+  if (web3.eth.blockNumber > number) {
+    throw Error(`block number ${number} is in the past (current is ${web3.eth.blockNumber})`)
+  }
+
+  while (web3.eth.blockNumber < number) {
+    await advanceBlock()
+  }
+}

+ 3 - 0
test/helpers/ether.js

@@ -0,0 +1,3 @@
+export default function ether(n) {
+  return new web3.BigNumber(web3.toWei(n, 'ether'))
+}