From 83a3809a858368557723b46b4f7ce364c552e4e4 Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 24 Apr 2017 11:04:42 -0600 Subject: [PATCH] Create and test PausableToken Contract --- contracts/lifecycle/Pausable.sol | 40 ++++++++-------- contracts/token/PausableToken.sol | 25 ++++++++++ docs/source/pausable.rst | 19 ++++---- test/Pausable.js | 18 ++++---- test/PausableToken.js | 73 ++++++++++++++++++++++++++++++ test/helpers/PausableMock.sol | 4 +- test/helpers/PausableTokenMock.sol | 12 +++++ 7 files changed, 152 insertions(+), 39 deletions(-) create mode 100644 contracts/token/PausableToken.sol create mode 100644 test/PausableToken.js create mode 100644 test/helpers/PausableTokenMock.sol diff --git a/contracts/lifecycle/Pausable.sol b/contracts/lifecycle/Pausable.sol index ad7d660899a..9ebefad153b 100644 --- a/contracts/lifecycle/Pausable.sol +++ b/contracts/lifecycle/Pausable.sol @@ -6,34 +6,36 @@ import "../ownership/Ownable.sol"; /* * Pausable - * Abstract contract that allows children to implement an - * emergency stop mechanism. + * Abstract contract that allows children to implement a + * pause mechanism. */ contract Pausable is Ownable { - bool public stopped; + event Pause(); + event Unpause(); - modifier stopInEmergency { - if (stopped) { - throw; - } + bool public paused = false; + + modifier whenNotPaused() { + if (paused) throw; _; } - - modifier onlyInEmergency { - if (!stopped) { - throw; - } + + modifier whenPaused { + if (!paused) throw; _; } - // called by the owner on emergency, triggers stopped state - function emergencyStop() external onlyOwner { - stopped = true; + // called by the owner to pause, triggers stopped state + function pause() onlyOwner whenNotPaused returns (bool) { + paused = true; + Pause(); + return true; } - // called by the owner on end of emergency, returns to normal state - function release() external onlyOwner onlyInEmergency { - stopped = false; + // called by the owner to unpause, returns to normal state + function unpause() onlyOwner whenPaused returns (bool) { + paused = false; + Unpause(); + return true; } - } diff --git a/contracts/token/PausableToken.sol b/contracts/token/PausableToken.sol new file mode 100644 index 00000000000..f38aad2bae1 --- /dev/null +++ b/contracts/token/PausableToken.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.8; + +import './StandardToken.sol'; +import '../lifecycle/Pausable.sol'; + +/** + * Pausable token + * + * Simple ERC20 Token example, with pausable token creation + * Issue: + * https://github.com/OpenZeppelin/zeppelin-solidity/issues/194 + * Based on code by BCAPtoken: + * https://github.com/BCAPtoken/BCAPToken/blob/5cb5e76338cc47343ba9268663a915337c8b268e/sol/BCAPToken.sol#L27 + **/ + +contract PausableToken is Pausable, StandardToken { + + function transfer(address _to, uint _value) whenNotPaused { + return super.transfer(_to, _value); + } + + function transferFrom(address _from, address _to, uint _value) whenNotPaused { + return super.transferFrom(_from, _to, _value); + } +} \ No newline at end of file diff --git a/docs/source/pausable.rst b/docs/source/pausable.rst index 30c0a805435..fc69ad0b3be 100644 --- a/docs/source/pausable.rst +++ b/docs/source/pausable.rst @@ -1,26 +1,27 @@ Pausable ============================================= -Base contract that provides an emergency stop mechanism. +Base contract that provides a pause mechanism. Inherits from contract Ownable. -emergencyStop( ) external onlyOwner +pause() onlyOwner whenNotPaused returns (bool) """"""""""""""""""""""""""""""""""""" -Triggers the stop mechanism on the contract. After this function is called (by the owner of the contract), any function with modifier stopInEmergency will not run. +Triggers pause mechanism on the contract. After this function is called (by the owner of the contract), any function with modifier whenNotPaused will not run. -modifier stopInEmergency + +modifier whenNotPaused() """"""""""""""""""""""""""""""""""""" -Prevents function from running if stop mechanism is activated. +Prevents function from running if pause mechanism is activated. -modifier onlyInEmergency +modifier whenPaused() """"""""""""""""""""""""""""""""""""" -Only runs if stop mechanism is activated. +Only runs if pause mechanism is activated. -release( ) external onlyOwner onlyInEmergency +unpause() onlyOwner whenPaused returns (bool) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -Deactivates the stop mechanism. \ No newline at end of file +Deactivates the pause mechanism. \ No newline at end of file diff --git a/test/Pausable.js b/test/Pausable.js index 3ac39e55afd..2614b3af3be 100644 --- a/test/Pausable.js +++ b/test/Pausable.js @@ -5,7 +5,7 @@ const PausableMock = artifacts.require('helpers/PausableMock.sol'); contract('Pausable', function(accounts) { - it('can perform normal process in non-emergency', async function() { + it('can perform normal process in non-pause', async function() { let Pausable = await PausableMock.new(); let count0 = await Pausable.count(); assert.equal(count0, 0); @@ -15,9 +15,9 @@ contract('Pausable', function(accounts) { assert.equal(count1, 1); }); - it('can not perform normal process in emergency', async function() { + it('can not perform normal process in pause', async function() { let Pausable = await PausableMock.new(); - await Pausable.emergencyStop(); + await Pausable.pause(); let count0 = await Pausable.count(); assert.equal(count0, 0); @@ -31,7 +31,7 @@ contract('Pausable', function(accounts) { }); - it('can not take drastic measure in non-emergency', async function() { + it('can not take drastic measure in non-pause', async function() { let Pausable = await PausableMock.new(); try { await Pausable.drasticMeasure(); @@ -43,19 +43,19 @@ contract('Pausable', function(accounts) { assert.isFalse(drasticMeasureTaken); }); - it('can take a drastic measure in an emergency', async function() { + it('can take a drastic measure in a pause', async function() { let Pausable = await PausableMock.new(); - await Pausable.emergencyStop(); + await Pausable.pause(); await Pausable.drasticMeasure(); let drasticMeasureTaken = await Pausable.drasticMeasureTaken(); assert.isTrue(drasticMeasureTaken); }); - it('should resume allowing normal process after emergency is over', async function() { + it('should resume allowing normal process after pause is over', async function() { let Pausable = await PausableMock.new(); - await Pausable.emergencyStop(); - await Pausable.release(); + await Pausable.pause(); + await Pausable.unpause(); await Pausable.normalProcess(); let count0 = await Pausable.count(); diff --git a/test/PausableToken.js b/test/PausableToken.js new file mode 100644 index 00000000000..02318999c5a --- /dev/null +++ b/test/PausableToken.js @@ -0,0 +1,73 @@ +'user strict'; + +const assertJump = require('./helpers/assertJump'); +var PausableTokenMock = artifacts.require('./helpers/PausableTokenMock.sol'); + +contract('PausableToken', function(accounts) { + let token; + + beforeEach(async function() { + token = await PausableTokenMock.new(accounts[0], 100); + }); + + it('should return paused false after construction', async function() { + let paused = await token.paused(); + + assert.equal(paused, false); + }); + + it('should return paused true after pause', async function() { + await token.pause(); + let paused = await token.paused(); + + assert.equal(paused, true); + }); + + it('should return paused false after pause and unpause', async function() { + await token.pause(); + await token.unpause(); + let paused = await token.paused(); + + assert.equal(paused, false); + }); + + it('should be able to transfer if transfers are unpaused', async function() { + await token.transfer(accounts[1], 100); + let balance0 = await token.balanceOf(accounts[0]); + assert.equal(balance0, 0); + + let balance1 = await token.balanceOf(accounts[1]); + assert.equal(balance1, 100); + }); + + it('should be able to transfer after transfers are paused and unpaused', async function() { + await token.pause(); + await token.unpause(); + await token.transfer(accounts[1], 100); + let balance0 = await token.balanceOf(accounts[0]); + assert.equal(balance0, 0); + + let balance1 = await token.balanceOf(accounts[1]); + assert.equal(balance1, 100); + }); + + it('should throw an error trying to transfer while transactions are paused', async function() { + await token.pause(); + try { + await token.transfer(accounts[1], 100); + } catch (error) { + return assertJump(error); + } + assert.fail('should have thrown before'); + }); + + it('should throw an error trying to transfer from another account while transactions are paused', async function() { + await token.pause(); + try { + await token.transferFrom(accounts[0], accounts[1], 100); + } catch (error) { + return assertJump(error); + } + assert.fail('should have thrown before'); + }); +}) \ No newline at end of file diff --git a/test/helpers/PausableMock.sol b/test/helpers/PausableMock.sol index acf21c4e867..80fcb7d2d91 100644 --- a/test/helpers/PausableMock.sol +++ b/test/helpers/PausableMock.sol @@ -14,11 +14,11 @@ contract PausableMock is Pausable { count = 0; } - function normalProcess() external stopInEmergency { + function normalProcess() external whenNotPaused { count++; } - function drasticMeasure() external onlyInEmergency { + function drasticMeasure() external whenPaused { drasticMeasureTaken = true; } diff --git a/test/helpers/PausableTokenMock.sol b/test/helpers/PausableTokenMock.sol new file mode 100644 index 00000000000..569590d9b8a --- /dev/null +++ b/test/helpers/PausableTokenMock.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.4.8; + +import '../../contracts/token/PausableToken.sol'; + +// mock class using PausableToken +contract PausableTokenMock is PausableToken { + + function PausableTokenMock(address initialAccount, uint initialBalance) { + balances[initialAccount] = initialBalance; + } + +}