Merge pull request #2 from OpenZeppelin/master

Merge
This commit is contained in:
Arseniy Klempner
2016-11-20 19:25:51 -08:00
committed by GitHub
47 changed files with 941 additions and 193 deletions

View File

@ -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:
```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.
## 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
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
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
- 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
TODO

50
contracts/Bounty.sol Normal file
View File

@ -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
contracts/Claimable.sol Normal file
View File

@ -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;
}
}

View File

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

View File

@ -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;
}
_;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,23 @@
pragma solidity ^0.4.0;
pragma solidity ^0.4.4;
import "./Ownable.sol";
/*
* Stoppable
* 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;
modifier stopInEmergency { if (!stopped) _; }
modifier onlyInEmergency { if (stopped) _; }
function Stoppable(address _curator) {
if (_curator == 0) throw;
curator = _curator;
}
function emergencyStop() external {
if (msg.sender != curator) throw;
function emergencyStop() external onlyOwner {
stopped = true;
}
function release() external onlyOwner onlyInEmergency {
stopped = false;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,10 @@
pragma solidity ^0.4.0;
import "../Rejector.sol";
pragma solidity ^0.4.4;
/*
* Proof of Existence example contract
* 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;

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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 {
}
}

View File

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

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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
*/
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;
}
}

View File

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

View File

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

View File

@ -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
@ -9,14 +9,14 @@ import "../StandardToken.sol";
*/
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;
}
}

View File

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

View File

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

View File

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

49
test/BasicToken.js Normal file
View File

@ -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
test/Bounty.js Normal file
View File

@ -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
test/Claimable.js Normal file
View File

@ -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
test/LimitBalance.js Normal file
View File

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

View File

@ -1,6 +1,13 @@
contract('Ownable', function(accounts) {
var ownable;
beforeEach(function() {
return Ownable.new().then(function(deployed) {
ownable = deployed;
});
});
it("should have an owner", function(done) {
var ownable = Ownable.deployed();
return ownable.owner()
.then(function(owner) {
assert.isTrue(owner != 0);
@ -9,7 +16,6 @@ contract('Ownable', function(accounts) {
});
it("changes owner after transfer", function(done) {
var ownable = Ownable.deployed();
var other = accounts[1];
return ownable.transfer(other)
.then(function() {
@ -22,7 +28,6 @@ contract('Ownable', function(accounts) {
});
it("should prevent non-owners from transfering" ,function(done) {
var ownable = Ownable.deployed();
var other = accounts[2];
return ownable.transfer(other, {from: accounts[2]})
.then(function() {
@ -34,4 +39,20 @@ contract('Ownable', function(accounts) {
.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
test/StandardToken.js Normal file
View File

@ -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
test/Stoppable.js Normal file
View File

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

View File

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

View File

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