From 8056433c44d40173127798345e308333e98b7fce Mon Sep 17 00:00:00 2001 From: Tal Ater Date: Fri, 14 Dec 2018 17:35:38 +0200 Subject: [PATCH] Added PausableCrowdsale contract (#832) * Added PausableCrowdsale contract * Changed inheritance order to prevent "Linearization of inheritance graph impossible" error * Updated mock PausableCrowdsaleImpl to new constructor syntax * Broke function definition to multiple lines Comply with new max line length * Rename events to past-tense in PausableCrowdsale test * Removed should.be.fullfilled from PausableCrowdsale tests * Change import assertRevert to require in PausableCrowdsale tests * Remove dependency on chai-as-promised and added BigNumber support in PausableCrowdsale tests * reindent solidity with 4 spaces * add missing view modifier in _preValidatePurchase * convert assertRevert to new shoulFail helper * add new setup helper * use expectEvent * convert to assert to chai should style * add description to beforeEach blocks * extract common step to beforeEach * improve documentation * revert inheritance error * move PausableCrowdsale into crowdsale/validation * make documentation more specific * put whitespace in line with convention * improve test suite account names * undo beforeEach descriptions * simplify tests * fix transaction senders to be the anyone account * make transaction senders more explicit * remove mocha only --- .../validation/PausableCrowdsale.sol | 22 +++++++++ contracts/mocks/PausableCrowdsaleImpl.sol | 9 ++++ test/crowdsale/PausableCrowdsale.test.js | 46 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 contracts/crowdsale/validation/PausableCrowdsale.sol create mode 100644 contracts/mocks/PausableCrowdsaleImpl.sol create mode 100644 test/crowdsale/PausableCrowdsale.test.js diff --git a/contracts/crowdsale/validation/PausableCrowdsale.sol b/contracts/crowdsale/validation/PausableCrowdsale.sol new file mode 100644 index 00000000000..65fb0019fc8 --- /dev/null +++ b/contracts/crowdsale/validation/PausableCrowdsale.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.18; + +import "../Crowdsale.sol"; +import "../../lifecycle/Pausable.sol"; + + +/** + * @title PausableCrowdsale + * @dev Extension of Crowdsale contract where purchases can be paused and unpaused by the pauser role. + */ +contract PausableCrowdsale is Crowdsale, Pausable { + + /** + * @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met. Use super to concatenate validations. + * Adds the validation that the crowdsale must not be paused. + * @param _beneficiary Address performing the token purchase + * @param _weiAmount Value in wei involved in the purchase + */ + function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) internal view whenNotPaused { + return super._preValidatePurchase(_beneficiary, _weiAmount); + } +} diff --git a/contracts/mocks/PausableCrowdsaleImpl.sol b/contracts/mocks/PausableCrowdsaleImpl.sol new file mode 100644 index 00000000000..64b4bc578d0 --- /dev/null +++ b/contracts/mocks/PausableCrowdsaleImpl.sol @@ -0,0 +1,9 @@ +pragma solidity ^0.4.18; + +import "../token/ERC20/ERC20.sol"; +import "../crowdsale/validation/PausableCrowdsale.sol"; + +contract PausableCrowdsaleImpl is PausableCrowdsale { + constructor (uint256 _rate, address _wallet, ERC20 _token) public Crowdsale(_rate, _wallet, _token) { + } +} diff --git a/test/crowdsale/PausableCrowdsale.test.js b/test/crowdsale/PausableCrowdsale.test.js new file mode 100644 index 00000000000..2d0d0513d70 --- /dev/null +++ b/test/crowdsale/PausableCrowdsale.test.js @@ -0,0 +1,46 @@ +const shouldFail = require('../helpers/shouldFail'); + +const PausableCrowdsale = artifacts.require('PausableCrowdsaleImpl'); +const SimpleToken = artifacts.require('SimpleToken'); + +require('../helpers/setup'); + +contract('PausableCrowdsale', function ([_, pauser, wallet, anyone]) { + const rate = 1; + const value = 1; + + beforeEach(async function () { + const from = pauser; + + this.token = await SimpleToken.new({ from }); + this.crowdsale = await PausableCrowdsale.new(rate, wallet, this.token.address, { from }); + await this.token.transfer(this.crowdsale.address, 2 * value, { from }); + }); + + it('purchases work', async function () { + await this.crowdsale.sendTransaction({ from: anyone, value }); + await this.crowdsale.buyTokens(anyone, { from: anyone, value }); + }); + + context('after pause', function () { + beforeEach(async function () { + await this.crowdsale.pause({ from: pauser }); + }); + + it('purchases do not work', async function () { + await shouldFail.reverting(this.crowdsale.sendTransaction({ from: anyone, value })); + await shouldFail.reverting(this.crowdsale.buyTokens(anyone, { from: anyone, value })); + }); + + context('after unpause', function () { + beforeEach(async function () { + await this.crowdsale.unpause({ from: pauser }); + }); + + it('purchases work', async function () { + await this.crowdsale.sendTransaction({ from: anyone, value }); + await this.crowdsale.buyTokens(anyone, { from: anyone, value }); + }); + }); + }); +});