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