Browse Source

Merge pull request #2 from OpenZeppelin/master

Merge
Arseniy Klempner 9 years ago
parent
commit
3caaaaff81
47 changed files with 940 additions and 192 deletions
  1. 86 6
      README.md
  2. 50 0
      contracts/Bounty.sol
  3. 26 0
      contracts/Claimable.sol
  4. 2 2
      contracts/Killable.sol
  5. 18 0
      contracts/LimitBalance.sol
  6. 0 12
      contracts/LimitFunds.sol
  7. 1 1
      contracts/Migrations.sol
  8. 3 2
      contracts/Ownable.sol
  9. 1 1
      contracts/PullPayment.sol
  10. 0 9
      contracts/Rejector.sol
  11. 1 1
      contracts/SafeMath.sol
  12. 9 10
      contracts/Stoppable.sol
  13. 0 38
      contracts/bounties/CrowdsaleTokenBounty.sol
  14. 0 38
      contracts/bounties/SimpleTokenBounty.sol
  15. 1 1
      contracts/examples/BadArrayUse.sol
  16. 1 1
      contracts/examples/BadFailEarly.sol
  17. 1 1
      contracts/examples/BadPushPayments.sol
  18. 1 1
      contracts/examples/GoodArrayUse.sol
  19. 1 1
      contracts/examples/GoodFailEarly.sol
  20. 1 1
      contracts/examples/GoodPullPayments.sol
  21. 2 4
      contracts/examples/ProofOfExistence.sol
  22. 1 1
      contracts/examples/PullPaymentBid.sol
  23. 2 6
      contracts/examples/StoppableBid.sol
  24. 12 0
      contracts/test-helpers/BasicTokenMock.sol
  25. 15 0
      contracts/test-helpers/InsecureTargetBounty.sol
  26. 10 0
      contracts/test-helpers/LimitBalanceMock.sol
  27. 1 1
      contracts/test-helpers/PullPaymentMock.sol
  28. 15 0
      contracts/test-helpers/SecureTargetBounty.sol
  29. 12 0
      contracts/test-helpers/StandardTokenMock.sol
  30. 22 0
      contracts/test-helpers/StoppableMock.sol
  31. 27 0
      contracts/token/BasicToken.sol
  32. 23 23
      contracts/token/CrowdsaleToken.sol
  33. 1 1
      contracts/token/ERC20.sol
  34. 9 0
      contracts/token/ERC20Basic.sol
  35. 11 11
      contracts/token/SimpleToken.sol
  36. 2 2
      contracts/token/StandardToken.sol
  37. 5 2
      migrations/2_deploy_contracts.js
  38. 1 1
      package.json
  39. 49 0
      test/BasicToken.js
  40. 141 0
      test/Bounty.js
  41. 71 0
      test/Claimable.js
  42. 64 0
      test/LimitBalance.js
  43. 24 3
      test/Ownable.js
  44. 108 0
      test/StandardToken.js
  45. 108 0
      test/Stoppable.js
  46. 1 1
      test/TestOwnable.sol
  47. 0 10
      truffle.js

+ 86 - 6
README.md

@@ -22,22 +22,90 @@ npm i zeppelin-solidity
 After that, you'll get all the library's contracts in the `contracts/zeppelin` folder. You can use the contracts in the library like so:
 After that, you'll get all the library's contracts in the `contracts/zeppelin` folder. You can use the contracts in the library like so:
 
 
 ```js
 ```js
-import "./zeppelin/Rejector.sol";
+import "./zeppelin/Ownable.sol";
 
 
-contract MetaCoin is Rejector { 
+contract MyContract is Ownable {
   ...
   ...
 }
 }
 ```
 ```
 
 
 > NOTE: The current distribution channel is npm, which is not ideal. [We're looking into providing a better tool for code distribution](https://github.com/OpenZeppelin/zeppelin-solidity/issues/13), and ideas are welcome.
 > NOTE: The current distribution channel is npm, which is not ideal. [We're looking into providing a better tool for code distribution](https://github.com/OpenZeppelin/zeppelin-solidity/issues/13), and ideas are welcome.
 
 
+## Add your own bounty contract
+
+To create a bounty for your contract, inherit from the base Bounty contract and provide an implementation for `deployContract()` returning the new contract address.
+
+```
+import "./zeppelin/Bounty.sol";
+import "./YourContract.sol";
+
+contract YourBounty is Bounty {
+  function deployContract() internal returns(address) {
+    return new YourContract()
+  }
+}
+```
+
+### Implement invariant logic into your smart contract
+
+At contracts/YourContract.sol
+
+```
+contract YourContract {
+  function checkInvariant() returns(bool) {
+    // Implement your logic to make sure that none of the state is broken.
+  }
+}
+```
+
+### Deploy your bounty contract as usual
+
+At `migrations/2_deploy_contracts.js`
+
+```
+module.exports = function(deployer) {
+  deployer.deploy(YourContract);
+  deployer.deploy(YourBounty);
+};
+```
+
+### Add a reward to the bounty contract
+
+After deploying the contract, send rewards money into the bounty contract.
+
+From `truffle console`
+
+```
+address = 'your account address'
+reward = 'reward to pay to a researcher'
+
+web3.eth.sendTransaction({
+  from:address,
+  to:bounty.address,
+  value: web3.toWei(reward, "ether")
+}
+
+```
+
+### Researchers hack the contract and claim their reward.
+
+For each researcher who wants to hack the contract and claims the reward, refer to our [test](./test/Bounty.js) for the detail.
+
+### Ends the contract
+
+If you manage to protect your contract from security researchers and wants to end the bounty, kill the contract so that all the rewards go back to the owner of the bounty contract.
+
+```
+bounty.kill()
+```
+
 #### Truffle Beta Support
 #### Truffle Beta Support
 We also support Truffle Beta npm integration. If you're using Truffle Beta, the contracts in `node_modules` will be enough, so feel free to delete the copies at your `contracts` folder. If you're using Truffle Beta, you can use Zeppelin contracts like so:
 We also support Truffle Beta npm integration. If you're using Truffle Beta, the contracts in `node_modules` will be enough, so feel free to delete the copies at your `contracts` folder. If you're using Truffle Beta, you can use Zeppelin contracts like so:
 
 
 ```js
 ```js
-import "zeppelin-solidity/contracts/Rejector.sol";
+import "zeppelin-solidity/contracts/Ownable.sol";
 
 
-contract MetaCoin is Rejector { 
+contract MyContract is Ownable {
   ...
   ...
 }
 }
 ```
 ```
@@ -61,8 +129,20 @@ Interested in contributing to Zeppelin?
 - Issue tracker: https://github.com/OpenZeppelin/zeppelin-solidity/issues
 - Issue tracker: https://github.com/OpenZeppelin/zeppelin-solidity/issues
 - Contribution guidelines: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/CONTRIBUTING.md
 - Contribution guidelines: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/CONTRIBUTING.md
 
 
-## Projects using Zeppelin
-- [Blockparty](https://github.com/makoto/blockparty)
+## Collaborating organizations and audits by Zeppelin
+- [Golem](https://golem.network/)
+- [Mediachain](https://golem.network/)
+- [Truffle](http://truffleframework.com/)
+- [Firstblood](http://firstblood.io/)
+- [Rootstock](http://www.rsk.co/)
+- [Consensys](https://consensys.net/)
+- [DigixGlobal](https://www.dgx.io/)
+- [Coinfund](https://coinfund.io/)
+- [DemocracyEarth](http://democracy.earth/)
+- [Signatura](https://signatura.co/)
+- [Ether.camp](http://www.ether.camp/)
+
+among others...
 
 
 ## Contracts
 ## Contracts
 TODO
 TODO

+ 50 - 0
contracts/Bounty.sol

@@ -0,0 +1,50 @@
+pragma solidity ^0.4.4;
+import './PullPayment.sol';
+import './Killable.sol';
+
+/*
+ * Bounty
+ * This bounty will pay out to a researcher if he/she breaks invariant logic of
+ * the contract you bet reward against.
+ */
+
+contract Target {
+  function checkInvariant() returns(bool);
+}
+
+contract Bounty is PullPayment, Killable {
+  Target target;
+  bool public claimed;
+  mapping(address => address) public researchers;
+
+  event TargetCreated(address createdAddress);
+
+  function() payable {
+    if (claimed) throw;
+  }
+
+  function createTarget() returns(Target) {
+    target = Target(deployContract());
+    researchers[target] = msg.sender;
+    TargetCreated(target);
+    return target;
+  }
+
+  function deployContract() internal returns(address);
+
+  function checkInvariant() returns(bool){
+    return target.checkInvariant();
+  }
+
+  function claim(Target target) {
+    address researcher = researchers[target];
+    if (researcher == 0) throw;
+    // Check Target contract invariants
+    if (target.checkInvariant()) {
+      throw;
+    }
+    asyncSend(researcher, this.balance);
+    claimed = true;
+  }
+
+}

+ 26 - 0
contracts/Claimable.sol

@@ -0,0 +1,26 @@
+pragma solidity ^0.4.0;
+import './Ownable.sol';
+
+/*
+ * Claimable
+ * Extension for the Ownable contract, where the ownership needs to be claimed
+ */
+
+contract Claimable is Ownable {
+  address public pendingOwner;
+
+  modifier onlyPendingOwner() {
+    if (msg.sender == pendingOwner)
+      _;
+  }
+
+  function transfer(address newOwner) onlyOwner {
+    pendingOwner = newOwner;
+  }
+
+  function claimOwnership() onlyPendingOwner {
+    owner = pendingOwner;
+    pendingOwner = 0x0;
+  }
+
+}

+ 2 - 2
contracts/Killable.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 import "./Ownable.sol";
 import "./Ownable.sol";
 
 
 /*
 /*
@@ -7,6 +7,6 @@ import "./Ownable.sol";
  */
  */
 contract Killable is Ownable {
 contract Killable is Ownable {
   function kill() {
   function kill() {
-    if (msg.sender == owner) suicide(owner);
+    if (msg.sender == owner) selfdestruct(owner);
   }
   }
 }
 }

+ 18 - 0
contracts/LimitBalance.sol

@@ -0,0 +1,18 @@
+pragma solidity ^0.4.4;
+contract LimitBalance {
+
+  uint public limit;
+
+  function LimitBalance(uint _limit) {
+    limit = _limit;
+  }
+
+  modifier limitedPayable() { 
+    if (this.balance > limit) {
+      throw;
+    }
+    _;
+    
+  }
+
+}

+ 0 - 12
contracts/LimitFunds.sol

@@ -1,12 +0,0 @@
-pragma solidity ^0.4.0;
-contract LimitFunds {
-
-  uint LIMIT = 5000;
-
-  function() { throw; }
-
-  function deposit() {
-    if (this.balance > LIMIT) throw;
-  }
-
-}

+ 1 - 1
contracts/Migrations.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 contract Migrations {
 contract Migrations {
   address public owner;
   address public owner;
   uint public last_completed_migration;
   uint public last_completed_migration;

+ 3 - 2
contracts/Ownable.sol

@@ -1,4 +1,5 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
+
 /*
 /*
  * Ownable
  * Ownable
  * Base contract with an owner
  * Base contract with an owner
@@ -16,7 +17,7 @@ contract Ownable {
   }
   }
 
 
   function transfer(address newOwner) onlyOwner {
   function transfer(address newOwner) onlyOwner {
-    owner = newOwner;
+    if (newOwner != address(0)) owner = newOwner;
   }
   }
 
 
 }
 }

+ 1 - 1
contracts/PullPayment.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 /*
 /*
  * PullPayment
  * PullPayment
  * Base contract supporting async send for pull payments.
  * Base contract supporting async send for pull payments.

+ 0 - 9
contracts/Rejector.sol

@@ -1,9 +0,0 @@
-pragma solidity ^0.4.0;
-/*
- * Rejector
- * Base contract for rejecting direct deposits.
- * Fallback function throws immediately.
- */
-contract Rejector {
-  function() { throw; }
-}

+ 1 - 1
contracts/SafeMath.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
 /**
 /**
  * Math operations with safety checks
  * Math operations with safety checks

+ 9 - 10
contracts/Stoppable.sol

@@ -1,24 +1,23 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
+
+import "./Ownable.sol";
 /*
 /*
  * Stoppable
  * Stoppable
  * Abstract contract that allows children to implement an
  * Abstract contract that allows children to implement an
- * emergency stop mechanism. 
+ * emergency stop mechanism.
  */
  */
-contract Stoppable {
-  address public curator;
+contract Stoppable is Ownable {
   bool public stopped;
   bool public stopped;
 
 
   modifier stopInEmergency { if (!stopped) _; }
   modifier stopInEmergency { if (!stopped) _; }
   modifier onlyInEmergency { if (stopped) _; }
   modifier onlyInEmergency { if (stopped) _; }
 
 
-  function Stoppable(address _curator) {
-    if (_curator == 0) throw;
-    curator = _curator;
+  function emergencyStop() external onlyOwner {
+    stopped = true;
   }
   }
 
 
-  function emergencyStop() external {
-    if (msg.sender != curator) throw;
-    stopped = true;
+  function release() external onlyOwner onlyInEmergency {
+    stopped = false;
   }
   }
 
 
 }
 }

+ 0 - 38
contracts/bounties/CrowdsaleTokenBounty.sol

@@ -1,38 +0,0 @@
-pragma solidity ^0.4.0;
-import '../PullPayment.sol';
-import '../token/CrowdsaleToken.sol';
-
-/*
- * Bounty
- * This bounty will pay out if you can cause a CrowdsaleToken's balance
- * to be lower than its totalSupply, which would mean that it doesn't 
- * have sufficient ether for everyone to withdraw.
- */
-contract CrowdsaleTokenBounty is PullPayment {
-
-  bool public claimed;
-  mapping(address => address) public researchers;
-
-  function() {
-    if (claimed) throw;
-  }
-
-  function createTarget() returns(CrowdsaleToken) {
-    CrowdsaleToken target = new CrowdsaleToken();
-    researchers[target] = msg.sender;
-    return target;
-  }
-
-  function claim(CrowdsaleToken target) {
-    address researcher = researchers[target];
-    if (researcher == 0) throw;
-    // Check CrowdsaleToken contract invariants
-    // Customize this to the specifics of your contract
-    if (target.totalSupply() == target.balance) {
-      throw;
-    }
-    asyncSend(researcher, this.balance);
-    claimed = true;
-  }
-
-}

+ 0 - 38
contracts/bounties/SimpleTokenBounty.sol

@@ -1,38 +0,0 @@
-pragma solidity ^0.4.0;
-import '../PullPayment.sol';
-import '../token/SimpleToken.sol';
-
-/*
- * Bounty
- * This bounty will pay out if you can cause a SimpleToken's balance
- * to be lower than its totalSupply, which would mean that it doesn't 
- * have sufficient ether for everyone to withdraw.
- */
-contract SimpleTokenBounty is PullPayment {
-
-  bool public claimed;
-  mapping(address => address) public researchers;
-
-  function() {
-    if (claimed) throw;
-  }
-
-  function createTarget() returns(SimpleToken) {
-    SimpleToken target = new SimpleToken();
-    researchers[target] = msg.sender;
-    return target;
-  }
-
-  function claim(SimpleToken target) {
-    address researcher = researchers[target];
-    if (researcher == 0) throw;
-    // Check SimpleToken contract invariants
-    // Customize this to the specifics of your contract
-    if (target.totalSupply() == target.balance) {
-      throw;
-    }
-    asyncSend(researcher, this.balance);
-    claimed = true;
-  }
-
-}

+ 1 - 1
contracts/examples/BadArrayUse.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 import '../PullPayment.sol';
 import '../PullPayment.sol';
 
 
 // UNSAFE CODE, DO NOT USE!
 // UNSAFE CODE, DO NOT USE!

+ 1 - 1
contracts/examples/BadFailEarly.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 // UNSAFE CODE, DO NOT USE!
 // UNSAFE CODE, DO NOT USE!
 
 
 contract BadFailEarly {
 contract BadFailEarly {

+ 1 - 1
contracts/examples/BadPushPayments.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 // UNSAFE CODE, DO NOT USE!
 // UNSAFE CODE, DO NOT USE!
 
 
 contract BadPushPayments {
 contract BadPushPayments {

+ 1 - 1
contracts/examples/GoodArrayUse.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 import '../PullPayment.sol';
 import '../PullPayment.sol';
 
 
 contract GoodArrayUse is PullPayment {
 contract GoodArrayUse is PullPayment {

+ 1 - 1
contracts/examples/GoodFailEarly.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
 contract GoodFailEarly {
 contract GoodFailEarly {
 
 

+ 1 - 1
contracts/examples/GoodPullPayments.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 contract GoodPullPayments {
 contract GoodPullPayments {
   address highestBidder;
   address highestBidder;
   uint highestBid;
   uint highestBid;

+ 2 - 4
contracts/examples/ProofOfExistence.sol

@@ -1,12 +1,10 @@
-pragma solidity ^0.4.0;
-
-import "../Rejector.sol";
+pragma solidity ^0.4.4;
 
 
 /*
 /*
  * Proof of Existence example contract
  * Proof of Existence example contract
  * see https://medium.com/zeppelin-blog/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05
  * see https://medium.com/zeppelin-blog/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05
  */
  */
-contract ProofOfExistence is Rejector {
+contract ProofOfExistence {
 
 
   mapping (bytes32 => bool) public proofs;
   mapping (bytes32 => bool) public proofs;
 
 

+ 1 - 1
contracts/examples/PullPaymentBid.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
 import '../PullPayment.sol';
 import '../PullPayment.sol';
 
 

+ 2 - 6
contracts/examples/StoppableBid.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
 import '../PullPayment.sol';
 import '../PullPayment.sol';
 import '../Stoppable.sol';
 import '../Stoppable.sol';
@@ -7,10 +7,6 @@ contract StoppableBid is Stoppable, PullPayment {
   address public highestBidder;
   address public highestBidder;
   uint public highestBid;
   uint public highestBid;
 
 
-  function StoppableBid(address _curator)
-    Stoppable(_curator)
-    PullPayment() {}
-
   function bid() external stopInEmergency {
   function bid() external stopInEmergency {
     if (msg.value <= highestBid) throw;
     if (msg.value <= highestBid) throw;
     
     
@@ -22,7 +18,7 @@ contract StoppableBid is Stoppable, PullPayment {
   }
   }
 
 
   function withdraw() onlyInEmergency {
   function withdraw() onlyInEmergency {
-    suicide(curator);
+    selfdestruct(owner);
   }
   }
 
 
 }
 }

+ 12 - 0
contracts/test-helpers/BasicTokenMock.sol

@@ -0,0 +1,12 @@
+pragma solidity ^0.4.4;
+import '../token/BasicToken.sol';
+
+// mock class using BasicToken
+contract BasicTokenMock is BasicToken {
+
+  function BasicTokenMock(address initialAccount, uint initialBalance) {
+    balances[initialAccount] = initialBalance;
+    totalSupply = initialBalance;
+  }
+
+}

+ 15 - 0
contracts/test-helpers/InsecureTargetBounty.sol

@@ -0,0 +1,15 @@
+pragma solidity ^0.4.4;
+
+import "../Bounty.sol";
+
+contract InsecureTargetMock {
+  function checkInvariant() returns(bool){
+    return false;
+  }
+}
+
+contract InsecureTargetBounty is Bounty {
+  function deployContract() internal returns (address) {
+    return new InsecureTargetMock();
+  }
+}

+ 10 - 0
contracts/test-helpers/LimitBalanceMock.sol

@@ -0,0 +1,10 @@
+pragma solidity ^0.4.4;
+import '../LimitBalance.sol';
+
+// mock class using LimitBalance
+contract LimitBalanceMock is LimitBalance(1000) {
+
+  function limitedDeposit() payable limitedPayable {
+  }
+
+}

+ 1 - 1
contracts/test-helpers/PullPaymentMock.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 import '../PullPayment.sol';
 import '../PullPayment.sol';
 
 
 // mock class using PullPayment
 // mock class using PullPayment

+ 15 - 0
contracts/test-helpers/SecureTargetBounty.sol

@@ -0,0 +1,15 @@
+pragma solidity ^0.4.4;
+
+import "../Bounty.sol";
+
+contract SecureTargetMock {
+  function checkInvariant() returns(bool){
+    return true;
+  }
+}
+
+contract SecureTargetBounty is Bounty {
+  function deployContract() internal returns (address) {
+    return new SecureTargetMock();
+  }
+}

+ 12 - 0
contracts/test-helpers/StandardTokenMock.sol

@@ -0,0 +1,12 @@
+pragma solidity ^0.4.4;
+import '../token/StandardToken.sol';
+
+// mock class using StandardToken
+contract StandardTokenMock is StandardToken {
+
+  function StandardTokenMock(address initialAccount, uint initialBalance) {
+    balances[initialAccount] = initialBalance;
+    totalSupply = initialBalance;
+  }
+
+}

+ 22 - 0
contracts/test-helpers/StoppableMock.sol

@@ -0,0 +1,22 @@
+pragma solidity ^0.4.4;
+import '../Stoppable.sol';
+
+// mock class using Stoppable
+contract StoppableMock is Stoppable {
+  bool public drasticMeasureTaken;
+  uint public count;
+
+  function StoppableMock() {
+    drasticMeasureTaken = false;
+    count = 0;
+  }
+
+  function normalProcess() external stopInEmergency {
+    count++;
+  }
+
+  function drasticMeasure() external onlyInEmergency {
+    drasticMeasureTaken = true;
+  }
+
+}

+ 27 - 0
contracts/token/BasicToken.sol

@@ -0,0 +1,27 @@
+pragma solidity ^0.4.4;
+
+import './ERC20Basic.sol';
+import '../SafeMath.sol';
+
+/**
+ * Basic token
+ * Basic version of StandardToken, with no allowances
+ */
+contract BasicToken is ERC20Basic, SafeMath {
+
+  mapping(address => uint) balances;
+
+  function transfer(address _to, uint _value) {
+    if (balances[msg.sender] < _value) {
+      throw;
+    } 
+    balances[msg.sender] = safeSub(balances[msg.sender], _value);
+    balances[_to] = safeAdd(balances[_to], _value);
+    Transfer(msg.sender, _to, _value);
+  }
+
+  function balanceOf(address _owner) constant returns (uint balance) {
+    return balances[_owner];
+  }
+  
+}

+ 23 - 23
contracts/token/CrowdsaleToken.sol

@@ -1,34 +1,34 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
-import "../StandardToken.sol";
+import "./StandardToken.sol";
 
 
 /*
 /*
  * Simple ERC20 Token example, with crowdsale token creation
  * Simple ERC20 Token example, with crowdsale token creation
  */
  */
 contract CrowdsaleToken is StandardToken {
 contract CrowdsaleToken is StandardToken {
 
 
-    string public name = "CrowdsaleToken";
-    string public symbol = "CRW";
-    uint public decimals = 18;
+  string public name = "CrowdsaleToken";
+  string public symbol = "CRW";
+  uint public decimals = 18;
 
 
-    // 1 ether = 500 example tokens 
-    uint PRICE = 500;
+  // 1 ether = 500 example tokens 
+  uint PRICE = 500;
 
 
-    function () payable {
-        createTokens(msg.sender);
-    }
-    
-    function createTokens(address recipient) payable {
-        if (msg.value == 0) throw;
-    
-        uint tokens = safeMul(msg.value, getPrice());
+  function () payable {
+    createTokens(msg.sender);
+  }
+  
+  function createTokens(address recipient) payable {
+    if (msg.value == 0) throw;
 
 
-        totalSupply = safeAdd(totalSupply, tokens);
-        balances[recipient] = safeAdd(balances[recipient], tokens);
-    }
-    
-    // replace this with any other price function
-    function getPrice() constant returns (uint result){
-      return PRICE;
-    }
+    uint tokens = safeMul(msg.value, getPrice());
+
+    totalSupply = safeAdd(totalSupply, tokens);
+    balances[recipient] = safeAdd(balances[recipient], tokens);
+  }
+  
+  // replace this with any other price function
+  function getPrice() constant returns (uint result){
+    return PRICE;
+  }
 }
 }

+ 1 - 1
contracts/ERC20.sol → contracts/token/ERC20.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
 
 
 // see https://github.com/ethereum/EIPs/issues/20
 // see https://github.com/ethereum/EIPs/issues/20

+ 9 - 0
contracts/token/ERC20Basic.sol

@@ -0,0 +1,9 @@
+pragma solidity ^0.4.4;
+
+
+contract ERC20Basic {
+  uint public totalSupply;
+  function balanceOf(address who) constant returns (uint);
+  function transfer(address to, uint value);
+  event Transfer(address indexed from, address indexed to, uint value);
+}

+ 11 - 11
contracts/token/SimpleToken.sol

@@ -1,6 +1,6 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
-import "../StandardToken.sol";
+import "./StandardToken.sol";
 
 
 /*
 /*
  * Very simple ERC20 Token example, where all tokens are pre-assigned
  * Very simple ERC20 Token example, where all tokens are pre-assigned
@@ -9,14 +9,14 @@ import "../StandardToken.sol";
  */
  */
 contract SimpleToken is StandardToken {
 contract SimpleToken is StandardToken {
 
 
-    string public name = "SimpleToken";
-    string public symbol = "SIM";
-    uint public decimals = 18;
-    uint public INITIAL_SUPPLY = 10000;
-    
-    function SimpleToken() {
-      totalSupply = INITIAL_SUPPLY;
-      balances[msg.sender] = INITIAL_SUPPLY;
-    }
+  string public name = "SimpleToken";
+  string public symbol = "SIM";
+  uint public decimals = 18;
+  uint public INITIAL_SUPPLY = 10000;
+  
+  function SimpleToken() {
+    totalSupply = INITIAL_SUPPLY;
+    balances[msg.sender] = INITIAL_SUPPLY;
+  }
 
 
 }
 }

+ 2 - 2
contracts/StandardToken.sol → contracts/token/StandardToken.sol

@@ -1,7 +1,7 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 
 
 import './ERC20.sol';
 import './ERC20.sol';
-import './SafeMath.sol';
+import '../SafeMath.sol';
 
 
 /**
 /**
  * ERC20 token
  * ERC20 token

+ 5 - 2
migrations/2_deploy_contracts.js

@@ -2,8 +2,11 @@ module.exports = function(deployer) {
   deployer.deploy(PullPaymentBid);
   deployer.deploy(PullPaymentBid);
   deployer.deploy(BadArrayUse);
   deployer.deploy(BadArrayUse);
   deployer.deploy(ProofOfExistence);
   deployer.deploy(ProofOfExistence);
-  deployer.deploy(SimpleTokenBounty);
-  deployer.deploy(CrowdsaleTokenBounty);
   deployer.deploy(Ownable);
   deployer.deploy(Ownable);
+  deployer.deploy(Claimable);
   deployer.deploy(LimitFunds);
   deployer.deploy(LimitFunds);
+  if(deployer.network == 'test'){
+    deployer.deploy(SecureTargetBounty);
+    deployer.deploy(InsecureTargetBounty);
+  };
 };
 };

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "zeppelin-solidity",
   "name": "zeppelin-solidity",
-  "version": "0.0.10",
+  "version": "0.0.11",
   "description": "Secure Smart Contract library for Solidity",
   "description": "Secure Smart Contract library for Solidity",
   "main": "truffle.js",
   "main": "truffle.js",
   "devDependencies": {},
   "devDependencies": {},

+ 49 - 0
test/BasicToken.js

@@ -0,0 +1,49 @@
+contract('BasicToken', function(accounts) {
+
+  it("should return the correct totalSupply after construction", function(done) {
+    return BasicTokenMock.new(accounts[0], 100)
+      .then(function(token) {
+        return token.totalSupply();
+      })
+      .then(function(totalSupply) {
+        assert.equal(totalSupply, 100);
+      })
+      .then(done);
+  })
+
+  it("should return correct balances after transfer", function(done) {
+    var token;
+    return BasicTokenMock.new(accounts[0], 100)
+      .then(function(_token) {
+        token = _token;
+        return token.transfer(accounts[1], 100);
+      })
+      .then(function() {
+        return token.balanceOf(accounts[0]);
+      })
+      .then(function(balance) {
+        assert.equal(balance, 0);
+      })
+      .then(function() {
+        return token.balanceOf(accounts[1]);
+      })
+      .then(function(balance) {
+        assert.equal(balance, 100);
+      })
+      .then(done);
+  });
+
+  it("should throw an error when trying to transfer more than balance", function(done) {
+    var token;
+    return BasicTokenMock.new(accounts[0], 100)
+      .then(function(_token) {
+        token = _token;
+        return token.transfer(accounts[1], 101);
+      })
+      .catch(function(error) {
+        if (error.message.search('invalid JUMP') == -1) throw error
+      })
+      .then(done);
+  });
+
+});

+ 141 - 0
test/Bounty.js

@@ -0,0 +1,141 @@
+var sendReward = function(sender, receiver, value){
+  web3.eth.sendTransaction({
+    from:sender,
+    to:receiver,
+    value: value
+  })
+}
+
+contract('Bounty', function(accounts) {
+
+  it("sets reward", function(done){
+    var owner = accounts[0];
+    var reward = web3.toWei(1, "ether");
+
+    SecureTargetBounty.new().
+      then(function(bounty){
+        sendReward(owner, bounty.address, reward);
+        assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber())
+      }).
+      then(done);
+  })
+
+  it("empties itself when killed", function(done){
+    var owner = accounts[0];
+    var reward = web3.toWei(1, "ether");
+    var bounty;
+
+    SecureTargetBounty.new().
+      then(function(_bounty){
+        bounty = _bounty;
+        sendReward(owner, bounty.address, reward);
+        assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber())
+        return bounty.kill()
+      }).
+      then(function(){
+        assert.equal(0, web3.eth.getBalance(bounty.address).toNumber())
+      }).
+      then(done);
+  })
+
+  describe("Against secure contract", function(){
+    it("checkInvariant returns true", function(done){
+      var bounty;
+
+      SecureTargetBounty.new().
+        then(function(_bounty) {
+          bounty = _bounty;
+          return bounty.createTarget();
+        }).
+        then(function() {
+          return bounty.checkInvariant.call()
+        }).
+        then(function(result) {
+          assert.isTrue(result);
+        }).
+        then(done);
+    })
+
+    it("cannot claim reward", function(done){
+      var owner = accounts[0];
+      var researcher = accounts[1];
+      var reward = web3.toWei(1, "ether");
+
+      SecureTargetBounty.new().
+        then(function(bounty) {
+          var event = bounty.TargetCreated({});
+          event.watch(function(err, result) {
+            event.stopWatching();
+            if (err) { throw err }
+            var targetAddress = result.args.createdAddress;
+            sendReward(owner, bounty.address, reward);
+            assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber())
+            bounty.claim(targetAddress, {from:researcher}).
+              then(function(){ throw("should not come here")}).
+              catch(function() {
+                return bounty.claimed.call();
+              }).
+              then(function(result) {
+                assert.isFalse(result);
+                bounty.withdrawPayments({from:researcher}).
+                  then(function(){ throw("should not come here")}).
+                  catch(function() {
+                    assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber())
+                    done();
+                  })
+              })
+          })
+          bounty.createTarget({from:researcher});
+        })
+    })
+  })
+
+  describe("Against broken contract", function(){
+    it("checkInvariant returns false", function(done){
+      var bounty;
+
+      InsecureTargetBounty.new().
+        then(function(_bounty) {
+          bounty = _bounty;
+          return bounty.createTarget();
+        }).
+        then(function() {
+          return bounty.checkInvariant.call()
+        }).
+        then(function(result) {
+          assert.isFalse(result);
+        }).
+        then(done);
+    })
+
+    it("claims reward", function(done){
+      var owner = accounts[0];
+      var researcher = accounts[1];
+      var reward = web3.toWei(1, "ether");
+
+      InsecureTargetBounty.new().
+        then(function(bounty) {
+          var event = bounty.TargetCreated({});
+          event.watch(function(err, result) {
+            event.stopWatching();
+            if (err) { throw err }
+            var targetAddress = result.args.createdAddress;
+            sendReward(owner, bounty.address, reward);
+            assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber())
+            bounty.claim(targetAddress, {from:researcher}).
+              then(function() {
+                return bounty.claimed.call();
+              }).
+              then(function(result) {
+                assert.isTrue(result);
+                return bounty.withdrawPayments({from:researcher})
+              }).
+              then(function() {
+                assert.equal(0, web3.eth.getBalance(bounty.address).toNumber())
+              }).then(done);
+          })
+          bounty.createTarget({from:researcher});
+        })
+    })
+  })
+});

+ 71 - 0
test/Claimable.js

@@ -0,0 +1,71 @@
+contract('Claimable', function(accounts) {
+  var claimable;
+
+  beforeEach(function() {
+    return Claimable.new().then(function(deployed) {
+      claimable = deployed;
+    });
+  });
+
+  it("should have an owner", function(done) {
+    return claimable.owner()
+      .then(function(owner) {
+        assert.isTrue(owner != 0);
+      })
+      .then(done)
+  });
+
+  it("changes pendingOwner after transfer", function(done) {
+    var newOwner = accounts[1];
+    return claimable.transfer(newOwner)
+      .then(function() {
+        return claimable.pendingOwner();
+      })
+      .then(function(pendingOwner) {
+        assert.isTrue(pendingOwner === newOwner);
+      })
+      .then(done)
+  });
+
+  it("should prevent to claimOwnership from no pendingOwner", function(done) {
+    return claimable.claimOwnership({from: accounts[2]})
+      .then(function() {
+        return claimable.owner();
+      })
+      .then(function(owner) {
+        assert.isTrue(owner != accounts[2]);
+      })
+      .then(done)
+  });
+
+  it("should prevent non-owners from transfering" ,function(done) {
+    return claimable.transfer(accounts[2], {from: accounts[2]})
+      .then(function() {
+        return claimable.pendingOwner();
+      })
+      .then(function(pendingOwner) {
+        assert.isFalse(pendingOwner === accounts[2]);
+      })
+      .then(done)
+  });
+
+  describe("after initiating a transfer", function () {
+    var newOwner;
+
+    beforeEach(function () {
+      newOwner = accounts[1];
+      return claimable.transfer(newOwner);
+    });
+
+    it("changes allow pending owner to claim ownership", function(done) {
+      return claimable.claimOwnership({from: newOwner})
+        .then(function() {
+          return claimable.owner();
+        })
+        .then(function(owner) {
+          assert.isTrue(owner === newOwner);
+        })
+        .then(done)
+    });
+  });
+});

+ 64 - 0
test/LimitBalance.js

@@ -0,0 +1,64 @@
+contract('LimitBalance', function(accounts) {
+  var lb;
+
+  beforeEach(function() {
+    return LimitBalanceMock.new().then(function(deployed) {
+      lb = deployed;
+    });
+  });
+
+  var LIMIT = 1000;
+
+  it("should expose limit", function(done) {
+    return lb.limit()
+      .then(function(limit) { 
+        assert.equal(limit, LIMIT);
+      })
+      .then(done)
+  });
+
+  it("should allow sending below limit", function(done) {
+    var amount = 1;
+    return lb.limitedDeposit({value: amount})
+      .then(function() { 
+        assert.equal(web3.eth.getBalance(lb.address), amount);
+      })
+      .then(done)
+  });
+
+  it("shouldnt allow sending above limit", function(done) {
+    var amount = 1100;
+    return lb.limitedDeposit({value: amount})
+      .catch(function(error) {
+        if (error.message.search('invalid JUMP') == -1) throw error
+      })
+      .then(done)
+  });
+
+  it("should allow multiple sends below limit", function(done) {
+    var amount = 500;
+    return lb.limitedDeposit({value: amount})
+      .then(function() { 
+        assert.equal(web3.eth.getBalance(lb.address), amount);
+        return lb.limitedDeposit({value: amount})
+      })
+      .then(function() { 
+        assert.equal(web3.eth.getBalance(lb.address), amount*2);
+      })
+      .then(done)
+  });
+
+  it("shouldnt allow multiple sends above limit", function(done) {
+    var amount = 500;
+    return lb.limitedDeposit({value: amount})
+      .then(function() { 
+        assert.equal(web3.eth.getBalance(lb.address), amount);
+        return lb.limitedDeposit({value: amount+1})
+      })
+      .catch(function(error) {
+        if (error.message.search('invalid JUMP') == -1) throw error;
+      })
+      .then(done)
+  });
+
+});

+ 24 - 3
test/Ownable.js

@@ -1,6 +1,13 @@
 contract('Ownable', function(accounts) {
 contract('Ownable', function(accounts) {
+  var ownable;
+
+  beforeEach(function() {
+    return Ownable.new().then(function(deployed) {
+      ownable = deployed;
+    });
+  });
+
   it("should have an owner", function(done) {
   it("should have an owner", function(done) {
-    var ownable = Ownable.deployed();
     return ownable.owner()
     return ownable.owner()
       .then(function(owner) {
       .then(function(owner) {
         assert.isTrue(owner != 0);
         assert.isTrue(owner != 0);
@@ -9,7 +16,6 @@ contract('Ownable', function(accounts) {
   });
   });
 
 
   it("changes owner after transfer", function(done) {
   it("changes owner after transfer", function(done) {
-    var ownable = Ownable.deployed();
     var other = accounts[1];
     var other = accounts[1];
     return ownable.transfer(other)
     return ownable.transfer(other)
       .then(function() {
       .then(function() {
@@ -22,7 +28,6 @@ contract('Ownable', function(accounts) {
   });
   });
 
 
   it("should prevent non-owners from transfering" ,function(done) {
   it("should prevent non-owners from transfering" ,function(done) {
-    var ownable = Ownable.deployed();
     var other = accounts[2];
     var other = accounts[2];
     return ownable.transfer(other, {from: accounts[2]})
     return ownable.transfer(other, {from: accounts[2]})
       .then(function() {
       .then(function() {
@@ -34,4 +39,20 @@ contract('Ownable', function(accounts) {
       .then(done)
       .then(done)
   });
   });
 
 
+  it("should guard ownership against stuck state" ,function(done) {
+    var ownable = Ownable.deployed();
+
+    return ownable.owner()
+      .then(function (originalOwner) {
+        return ownable.transfer(null, {from: originalOwner})
+          .then(function() {
+            return ownable.owner();
+          })
+          .then(function(newOwner) {
+            assert.equal(originalOwner, newOwner);
+          })
+          .then(done);
+      });
+  });
+
 });
 });

+ 108 - 0
test/StandardToken.js

@@ -0,0 +1,108 @@
+contract('StandardToken', function(accounts) {
+
+  it("should return the correct totalSupply after construction", function(done) {
+    return StandardTokenMock.new(accounts[0], 100)
+      .then(function(token) {
+        return token.totalSupply();
+      })
+      .then(function(totalSupply) {
+        assert.equal(totalSupply, 100);
+      })
+      .then(done);
+  })
+
+  it("should return the correct allowance amount after approval", function(done) {
+    var token;
+    return StandardTokenMock.new()
+      .then(function(_token) {
+        token = _token;
+        return token.approve(accounts[1], 100);
+      })
+      .then(function() {
+        return token.allowance(accounts[0], accounts[1]);
+      })
+      .then(function(allowance) {
+        assert.equal(allowance, 100);
+      })
+      .then(done);
+  });
+
+  it("should return correct balances after transfer", function(done) {
+    var token;
+    return StandardTokenMock.new(accounts[0], 100)
+      .then(function(_token) {
+        token = _token;
+        return token.transfer(accounts[1], 100);
+      })
+      .then(function() {
+        return token.balanceOf(accounts[0]);
+      })
+      .then(function(balance) {
+        assert.equal(balance, 0);
+      })
+      .then(function() {
+        return token.balanceOf(accounts[1]);
+      })
+      .then(function(balance) {
+        assert.equal(balance, 100);
+      })
+      .then(done);
+  });
+
+  it("should throw an error when trying to transfer more than balance", function(done) {
+    var token;
+    return StandardTokenMock.new(accounts[0], 100)
+      .then(function(_token) {
+        token = _token;
+        return token.transfer(accounts[1], 101);
+      })
+      .catch(function(error) {
+        if (error.message.search('invalid JUMP') == -1) throw error
+      })
+      .then(done);
+  });
+
+  it("should return correct balances after transfering from another account", function(done) {
+    var token;
+    return StandardTokenMock.new(accounts[0], 100)
+      .then(function(_token) {
+        token = _token;
+        return token.approve(accounts[1], 100);
+      })
+      .then(function() {
+        return token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]});
+      })
+      .then(function() {
+        return token.balanceOf(accounts[0]);
+      })
+      .then(function(balance) {
+        assert.equal(balance, 0);
+        return token.balanceOf(accounts[2]);
+      })
+      .then(function(balance) {
+        assert.equal(balance, 100)
+        return token.balanceOf(accounts[1]);
+      })
+      .then(function(balance) {
+        assert.equal(balance, 0);
+      })
+      .then(done);
+  });
+
+  it("should throw an error when trying to transfer more than allowed", function(done) {
+    var token;
+    return StandardTokenMock.new(accounts[0], 100)
+      .then(function(_token) {
+        token = _token;
+        return token.approve(accounts[1], 99);
+      })
+      .then(function() {
+        return token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]});
+      })
+      .catch(function(error) {
+        if (error.message.search('invalid JUMP') == -1) throw error
+      })
+      .then(done);
+  });
+
+});

+ 108 - 0
test/Stoppable.js

@@ -0,0 +1,108 @@
+contract('Stoppable', function(accounts) {
+
+  it("can perform normal process in non-emergency", function(done) {
+    var stoppable;
+    return StoppableMock.new()
+      .then(function(_stoppable) {
+        stoppable = _stoppable;
+        return stoppable.count();
+      })
+      .then(function(count) {
+        assert.equal(count, 0);
+      })
+      .then(function () {
+        return stoppable.normalProcess();
+      })
+      .then(function() {
+        return stoppable.count();
+      })
+      .then(function(count) {
+        assert.equal(count, 1);
+      })
+      .then(done);
+  });
+
+  it("can not perform normal process in emergency", function(done) {
+    var stoppable;
+    return StoppableMock.new()
+      .then(function(_stoppable) {
+        stoppable = _stoppable;
+        return stoppable.emergencyStop();
+      })
+      .then(function () {
+        return stoppable.count();
+      })
+      .then(function(count) {
+        assert.equal(count, 0);
+      })
+      .then(function () {
+        return stoppable.normalProcess();
+      })
+      .then(function() {
+        return stoppable.count();
+      })
+      .then(function(count) {
+        assert.equal(count, 0);
+      })
+      .then(done);
+  });
+
+
+  it("can not take drastic measure in non-emergency", function(done) {
+    var stoppable;
+    return StoppableMock.new()
+      .then(function(_stoppable) {
+        stoppable = _stoppable;
+        return stoppable.drasticMeasure();
+      })
+      .then(function() {
+        return stoppable.drasticMeasureTaken();
+      })
+      .then(function(taken) {
+        assert.isFalse(taken);
+      })
+      .then(done);
+  });
+
+  it("can take a drastic measure in an emergency", function(done) {
+    var stoppable;
+    return StoppableMock.new()
+      .then(function(_stoppable) {
+        stoppable = _stoppable;
+        return stoppable.emergencyStop();
+      })
+      .then(function() {
+        return stoppable.drasticMeasure();
+      })
+      .then(function() {
+        return stoppable.drasticMeasureTaken();
+      })
+      .then(function(taken) {
+        assert.isTrue(taken);
+      })
+      .then(done);
+  });
+
+  it("should resume allowing normal process after emergency is over", function(done) {
+    var stoppable;
+    return StoppableMock.new()
+      .then(function(_stoppable) {
+        stoppable = _stoppable;
+        return stoppable.emergencyStop();
+      })
+      .then(function () {
+        return stoppable.release();
+      })
+      .then(function() {
+        return stoppable.normalProcess();
+      })
+      .then(function() {
+        return stoppable.count();
+      })
+      .then(function(count) {
+        assert.equal(count, 1);
+      })
+      .then(done);
+  });
+
+});

+ 1 - 1
test/TestOwnable.sol

@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.4.4;
 import "truffle/Assert.sol";
 import "truffle/Assert.sol";
 import "truffle/DeployedAddresses.sol";
 import "truffle/DeployedAddresses.sol";
 import "../contracts/Ownable.sol";
 import "../contracts/Ownable.sol";

+ 0 - 10
truffle.js

@@ -1,14 +1,4 @@
 module.exports = {
 module.exports = {
-  build: {
-    "index.html": "index.html",
-    "app.js": [
-      "javascripts/app.js"
-    ],
-    "app.css": [
-      "stylesheets/app.css"
-    ],
-    "images/": "images/"
-  },
   rpc: {
   rpc: {
     host: "localhost",
     host: "localhost",
     port: 8545
     port: 8545