diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0788c85ad..e80d6a4a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ Don't write long tests, write helper functions to make them be as short and conc #### T2 - Tests Must not be Random -Inputs for tests should not be generated randomly. Also, the type and structure of outputs should be checked. +Inputs for tests should not be generated randomly. Accounts used to create test contracts are an exception, those can be random. Also, the type and structure of outputs should be checked. ### Documentation @@ -82,7 +82,7 @@ git checkout -b remove/some-file We expect pull requests to be rebased to the master branch before merging: ```sh -git remote add zep git@github.com:OpenZeppelin/zep-solidity.git +git remote add zep git@github.com:OpenZeppelin/zeppelin-solidity.git git pull --rebase zep master ``` @@ -95,11 +95,11 @@ git push origin feature/some-new-stuff git push origin fix/some-bug ``` -Finally go to [github.com/OpenZeppelin/zep-solidity](https://github.com/OpenZeppelin/zep-solidity) in your web browser and issue a new pull request. +Finally go to [github.com/OpenZeppelin/zeppelin-solidity](https://github.com/OpenZeppelin/zeppelin-solidity) in your web browser and issue a new pull request. Main contributors will review your code and possibly ask for changes before your code is pulled in to the main repository. We'll check that all tests pass, review the coding style, and check for general code correctness. If everything is OK, we'll merge your pull request and your code will be part of Zeppelin. If you have any questions feel free to post them to -[github.com/OpenZeppelin/zep-solidity/issues](https://github.com/OpenZeppelin/zep-solidity/issues). +[github.com/OpenZeppelin/zeppelin-solidity/issues](https://github.com/OpenZeppelin/zeppelin-solidity/issues). Thanks for your time and code! diff --git a/README.md b/README.md index 0959ec0ec..e80f6ea25 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,20 @@ contract MetaCoin is Rejector { } ``` -> 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/zep-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. + +#### 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"; + +contract MetaCoin is Rejector { + ... +} +``` + +For more info see [the Truffle Beta package management tutorial](http://truffleframework.com/tutorials/package-management). ## Security Zeppelin is meant to provide secure, tested and community-audited code, but please use common sense when doing anything that deals with real money! We take no responsibility for your implementation decisions. @@ -45,11 +58,14 @@ Building a distributed application, protocol or organization with Zeppelin? Interested in contributing to Zeppelin? - Framework proposal and roadmap: https://medium.com/zeppelin-blog/zeppelin-framework-proposal-and-development-roadmap-fdfa9a3a32ab#.iain47pak -- Issue tracker: https://github.com/OpenZeppelin/zep-solidity/issues -- Contribution guidelines: https://github.com/OpenZeppelin/zep-solidity/blob/master/CONTRIBUTING.md +- 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) ## Contracts TODO ## License -Code released under the [MIT License](https://github.com/OpenZeppelin/zep-solidity/blob/master/LICENSE). +Code released under the [MIT License](https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/LICENSE). diff --git a/contracts/Bounty.sol b/contracts/Bounty.sol index fcd17917c..3f8bf8d4a 100644 --- a/contracts/Bounty.sol +++ b/contracts/Bounty.sol @@ -1,13 +1,14 @@ -import './PullPaymentCapable.sol'; -import './Token.sol'; +pragma solidity ^0.4.0; +import './PullPayment.sol'; +import './token/SimpleToken.sol'; /* * Bounty - * This bounty will pay out if you can cause a Token's balance + * 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 Bounty is PullPaymentCapable { +contract Bounty is PullPayment { bool public claimed; mapping(address => address) public researchers; @@ -16,16 +17,17 @@ contract Bounty is PullPaymentCapable { if (claimed) throw; } - function createTarget() returns(Token) { - Token target = new Token(0); + function createTarget() returns(SimpleToken) { + SimpleToken target = new SimpleToken(); researchers[target] = msg.sender; return target; } - function claim(Token target) { + function claim(SimpleToken target) { address researcher = researchers[target]; if (researcher == 0) throw; - // check Token contract invariants + // Check SimpleToken contract invariants + // Customize this to the specifics of your contract if (target.totalSupply() == target.balance) { throw; } diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol index e053284db..42b65a617 100644 --- a/contracts/ERC20.sol +++ b/contracts/ERC20.sol @@ -1,12 +1,16 @@ -contract ERC20 { - function totalSupply() constant returns (uint); - function balanceOf(address who) constant returns (uint); - function allowance(address owner, address spender) constant returns (uint); +pragma solidity ^0.4.0; + - function transfer(address to, uint value) returns (bool ok); - function transferFrom(address from, address to, uint value) returns (bool ok); - function approve(address spender, uint value) returns (bool ok); +// see https://github.com/ethereum/EIPs/issues/20 + +contract ERC20 { + uint public totalSupply; + function balanceOf(address who) constant returns (uint); + function allowance(address owner, address spender) constant returns (uint); - event Transfer(address indexed from, address indexed to, uint value); - event Approval(address indexed owner, address indexed spender, uint value); + function transfer(address to, uint value) returns (bool ok); + function transferFrom(address from, address to, uint value) returns (bool ok); + function approve(address spender, uint value) returns (bool ok); + event Transfer(address indexed from, address indexed to, uint value); + event Approval(address indexed owner, address indexed spender, uint value); } diff --git a/contracts/Killable.sol b/contracts/Killable.sol index 9d8a62717..28e5d9e98 100644 --- a/contracts/Killable.sol +++ b/contracts/Killable.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; import "./Ownable.sol"; /* diff --git a/contracts/LimitFunds.sol b/contracts/LimitFunds.sol index 8c8e1c19a..5f3200c33 100644 --- a/contracts/LimitFunds.sol +++ b/contracts/LimitFunds.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; contract LimitFunds { uint LIMIT = 5000; diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol index 132f325c1..608bba8f9 100644 --- a/contracts/Migrations.sol +++ b/contracts/Migrations.sol @@ -1,9 +1,10 @@ +pragma solidity ^0.4.0; contract Migrations { address public owner; uint public last_completed_migration; modifier restricted() { - if (msg.sender == owner) _ + if (msg.sender == owner) _; } function Migrations() { diff --git a/contracts/Ownable.sol b/contracts/Ownable.sol index 86d9483a2..ab8e9583d 100644 --- a/contracts/Ownable.sol +++ b/contracts/Ownable.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; /* * Ownable * Base contract with an owner @@ -11,7 +12,7 @@ contract Ownable { modifier onlyOwner() { if (msg.sender == owner) - _ + _; } function transfer(address newOwner) onlyOwner { diff --git a/contracts/PullPaymentCapable.sol b/contracts/PullPayment.sol similarity index 50% rename from contracts/PullPaymentCapable.sol rename to contracts/PullPayment.sol index 637a24277..b8d094455 100644 --- a/contracts/PullPaymentCapable.sol +++ b/contracts/PullPayment.sol @@ -1,10 +1,11 @@ +pragma solidity ^0.4.0; /* - * PullPaymentCapable + * PullPayment * Base contract supporting async send for pull payments. * Inherit from this contract and use asyncSend instead of send. */ -contract PullPaymentCapable { - mapping(address => uint) payments; +contract PullPayment { + mapping(address => uint) public payments; // store sent amount as credit to be pulled, called by payer function asyncSend(address dest, uint amount) internal { @@ -12,11 +13,16 @@ contract PullPaymentCapable { } // withdraw accumulated balance, called by payee - function withdrawPayments() external { - uint payment = payments[msg.sender]; - payments[msg.sender] = 0; - if (!msg.sender.send(payment)) { - payments[msg.sender] = payment; + function withdrawPayments() { + address payee = msg.sender; + uint payment = payments[payee]; + + if (payment == 0) throw; + if (this.balance < payment) throw; + + payments[payee] = 0; + if (!payee.send(payment)) { + throw; } } } diff --git a/contracts/Rejector.sol b/contracts/Rejector.sol index 3d9d92ca3..05525d5e9 100644 --- a/contracts/Rejector.sol +++ b/contracts/Rejector.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; /* * Rejector * Base contract for rejecting direct deposits. diff --git a/contracts/SafeMath.sol b/contracts/SafeMath.sol new file mode 100644 index 000000000..2d4bebf77 --- /dev/null +++ b/contracts/SafeMath.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.4.0; + +/** + * Math operations with safety checks + */ +contract SafeMath { + function safeMul(uint a, uint b) internal returns (uint) { + uint c = a * b; + assert(a == 0 || c / a == b); + return c; + } + + function safeSub(uint a, uint b) internal returns (uint) { + assert(b <= a); + return a - b; + } + + function safeAdd(uint a, uint b) internal returns (uint) { + uint c = a + b; + assert(c>=a && c>=b); + return c; + } + + function assert(bool assertion) internal { + if (!assertion) throw; + } +} + diff --git a/contracts/StandardToken.sol b/contracts/StandardToken.sol new file mode 100644 index 000000000..69b8e186f --- /dev/null +++ b/contracts/StandardToken.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.4.0; + +import './ERC20.sol'; +import './SafeMath.sol'; + +/** + * ERC20 token + * + * https://github.com/ethereum/EIPs/issues/20 + * Based on code by FirstBlood: + * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + */ +contract StandardToken is ERC20, SafeMath { + + mapping(address => uint) balances; + mapping (address => mapping (address => uint)) allowed; + + function transfer(address _to, uint _value) returns (bool success) { + 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); + return true; + } + + function transferFrom(address _from, address _to, uint _value) returns (bool success) { + var _allowance = allowed[_from][msg.sender]; + if (balances[_from] < _value || + _allowance < _value) { + throw; + } + + balances[_to] = safeAdd(balances[_to], _value); + balances[_from] = safeSub(balances[_from], _value); + allowed[_from][msg.sender] = safeSub(_allowance, _value); + Transfer(_from, _to, _value); + return true; + } + + function balanceOf(address _owner) constant returns (uint balance) { + return balances[_owner]; + } + + function approve(address _spender, uint _value) returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + return true; + } + + function allowance(address _owner, address _spender) constant returns (uint remaining) { + return allowed[_owner][_spender]; + } + +} diff --git a/contracts/Stoppable.sol b/contracts/Stoppable.sol index 8465ddfab..d649ce019 100644 --- a/contracts/Stoppable.sol +++ b/contracts/Stoppable.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; /* * Stoppable * Abstract contract that allows children to implement an @@ -7,8 +8,8 @@ contract Stoppable { address public curator; bool public stopped; - modifier stopInEmergency { if (!stopped) _ } - modifier onlyInEmergency { if (stopped) _ } + modifier stopInEmergency { if (!stopped) _; } + modifier onlyInEmergency { if (stopped) _; } function Stoppable(address _curator) { if (_curator == 0) throw; diff --git a/contracts/Token.sol b/contracts/Token.sol deleted file mode 100644 index 7d7758010..000000000 --- a/contracts/Token.sol +++ /dev/null @@ -1,72 +0,0 @@ -// Source: https://github.com/nexusdev/erc20 -// Flat file implementation of `dappsys/token/base.sol::DSTokenBase` - -// Everything throws instead of returning false on failure. -import './ERC20.sol'; - -contract Token is ERC20 { - - mapping( address => uint ) _balances; - mapping( address => mapping( address => uint ) ) _approvals; - uint _supply; - - function Token( uint initial_balance ) { - _balances[msg.sender] = initial_balance; - _supply = initial_balance; - } - - function totalSupply() constant returns (uint supply) { - return _supply; - } - - function balanceOf( address who ) constant returns (uint value) { - return _balances[who]; - } - - function transfer( address to, uint value) returns (bool ok) { - if( _balances[msg.sender] < value ) { - throw; - } - if( !safeToAdd(_balances[to], value) ) { - throw; - } - _balances[msg.sender] -= value; - _balances[to] += value; - Transfer( msg.sender, to, value ); - return true; - } - - function transferFrom( address from, address to, uint value) returns (bool ok) { - // if you don't have enough balance, throw - if( _balances[from] < value ) { - throw; - } - // if you don't have approval, throw - if( _approvals[from][msg.sender] < value ) { - throw; - } - if( !safeToAdd(_balances[to], value) ) { - throw; - } - // transfer and return true - _approvals[from][msg.sender] -= value; - _balances[from] -= value; - _balances[to] += value; - Transfer( from, to, value ); - return true; - } - - function approve(address spender, uint value) returns (bool ok) { - _approvals[msg.sender][spender] = value; - Approval( msg.sender, spender, value ); - return true; - } - - function allowance(address owner, address spender) constant returns (uint _allowance) { - return _approvals[owner][spender]; - } - - function safeToAdd(uint a, uint b) internal returns (bool) { - return (a + b >= a); - } -} diff --git a/contracts/examples/BadArrayUse.sol b/contracts/examples/BadArrayUse.sol index c698646f9..d76160dfd 100644 --- a/contracts/examples/BadArrayUse.sol +++ b/contracts/examples/BadArrayUse.sol @@ -1,8 +1,9 @@ -import '../PullPaymentCapable.sol'; +pragma solidity ^0.4.0; +import '../PullPayment.sol'; // UNSAFE CODE, DO NOT USE! -contract BadArrayUse is PullPaymentCapable { +contract BadArrayUse is PullPayment { address[] employees; function payBonus() { diff --git a/contracts/examples/BadFailEarly.sol b/contracts/examples/BadFailEarly.sol index 809d91166..801ed4754 100644 --- a/contracts/examples/BadFailEarly.sol +++ b/contracts/examples/BadFailEarly.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; // UNSAFE CODE, DO NOT USE! contract BadFailEarly { diff --git a/contracts/examples/BadPushPayments.sol b/contracts/examples/BadPushPayments.sol index 926200357..c8d6b9d19 100644 --- a/contracts/examples/BadPushPayments.sol +++ b/contracts/examples/BadPushPayments.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; // UNSAFE CODE, DO NOT USE! contract BadPushPayments { diff --git a/contracts/examples/GoodArrayUse.sol b/contracts/examples/GoodArrayUse.sol index 492a9d352..150eb6ba0 100644 --- a/contracts/examples/GoodArrayUse.sol +++ b/contracts/examples/GoodArrayUse.sol @@ -1,6 +1,7 @@ -import '../PullPaymentCapable.sol'; +pragma solidity ^0.4.0; +import '../PullPayment.sol'; -contract GoodArrayUse is PullPaymentCapable { +contract GoodArrayUse is PullPayment { address[] employees; mapping(address => uint) bonuses; diff --git a/contracts/examples/GoodFailEarly.sol b/contracts/examples/GoodFailEarly.sol index 2cc3bb711..1dc1b8d32 100644 --- a/contracts/examples/GoodFailEarly.sol +++ b/contracts/examples/GoodFailEarly.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.0; + contract GoodFailEarly { uint constant DEFAULT_SALARY = 50000; diff --git a/contracts/examples/GoodPullPayments.sol b/contracts/examples/GoodPullPayments.sol index 2ee3bac37..6a03bbc02 100644 --- a/contracts/examples/GoodPullPayments.sol +++ b/contracts/examples/GoodPullPayments.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.0; contract GoodPullPayments { address highestBidder; uint highestBid; diff --git a/contracts/examples/ProofOfExistence.sol b/contracts/examples/ProofOfExistence.sol new file mode 100644 index 000000000..95f2412cb --- /dev/null +++ b/contracts/examples/ProofOfExistence.sol @@ -0,0 +1,39 @@ +pragma solidity ^0.4.0; + +import "../Rejector.sol"; + +/* + * Proof of Existence example contract + * see https://medium.com/zeppelin-blog/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05 + */ +contract ProofOfExistence is Rejector { + + mapping (bytes32 => bool) public proofs; + + // store a proof of existence in the contract state + function storeProof(bytes32 proof) { + proofs[proof] = true; + } + + // calculate and store the proof for a document + function notarize(string document) { + var proof = calculateProof(document); + storeProof(proof); + } + + // helper function to get a document's sha256 + function calculateProof(string document) constant returns (bytes32) { + return sha256(document); + } + + // check if a document has been notarized + function checkDocument(string document) constant returns (bool) { + var proof = calculateProof(document); + return hasProof(proof); + } + + // returns true if proof is stored + function hasProof(bytes32 proof) constant returns (bool) { + return proofs[proof]; + } +} diff --git a/contracts/examples/PullPaymentBid.sol b/contracts/examples/PullPaymentBid.sol index e4e22452a..5cf4fe578 100644 --- a/contracts/examples/PullPaymentBid.sol +++ b/contracts/examples/PullPaymentBid.sol @@ -1,6 +1,8 @@ -import '../PullPaymentCapable.sol'; +pragma solidity ^0.4.0; -contract PullPaymentBid is PullPaymentCapable { +import '../PullPayment.sol'; + +contract PullPaymentBid is PullPayment { address public highestBidder; uint public highestBid; diff --git a/contracts/examples/StoppableBid.sol b/contracts/examples/StoppableBid.sol index 072a06cc2..364c186f8 100644 --- a/contracts/examples/StoppableBid.sol +++ b/contracts/examples/StoppableBid.sol @@ -1,13 +1,15 @@ -import '../PullPaymentCapable.sol'; +pragma solidity ^0.4.0; + +import '../PullPayment.sol'; import '../Stoppable.sol'; -contract StoppableBid is Stoppable, PullPaymentCapable { +contract StoppableBid is Stoppable, PullPayment { address public highestBidder; uint public highestBid; function StoppableBid(address _curator) Stoppable(_curator) - PullPaymentCapable() {} + PullPayment() {} function bid() external stopInEmergency { if (msg.value <= highestBid) throw; diff --git a/contracts/test-helpers/PullPaymentMock.sol b/contracts/test-helpers/PullPaymentMock.sol new file mode 100644 index 000000000..51cb3e107 --- /dev/null +++ b/contracts/test-helpers/PullPaymentMock.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.0; +import '../PullPayment.sol'; + +// mock class using PullPayment +contract PullPaymentMock is PullPayment { + // test helper function to call asyncSend + function callSend(address dest, uint amount) { + asyncSend(dest, amount); + } + +} diff --git a/contracts/token/CrowdsaleToken.sol b/contracts/token/CrowdsaleToken.sol new file mode 100644 index 000000000..7fd363cb0 --- /dev/null +++ b/contracts/token/CrowdsaleToken.sol @@ -0,0 +1,34 @@ +pragma solidity ^0.4.0; + +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; + + // 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()); + + 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; + } +} diff --git a/contracts/token/SimpleToken.sol b/contracts/token/SimpleToken.sol new file mode 100644 index 000000000..ccb34769f --- /dev/null +++ b/contracts/token/SimpleToken.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.0; + +import "../StandardToken.sol"; + +/* + * Very simple ERC20 Token example, where all tokens are pre-assigned + * to the creator. Note they can later distribute these tokens + * as they wish using `transfer` and other `StandardToken` functions. + */ +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; + } + +} diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 8db14c0b9..b29a532cc 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,6 +1,7 @@ module.exports = function(deployer) { deployer.deploy(PullPaymentBid); deployer.deploy(BadArrayUse); + deployer.deploy(ProofOfExistence); deployer.deploy(Bounty); deployer.deploy(Ownable); deployer.deploy(LimitFunds); diff --git a/package.json b/package.json index b496d54e9..b9c0b7a11 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "zeppelin-solidity", - "version": "0.0.6", + "version": "0.0.10", "description": "Secure Smart Contract library for Solidity", "main": "truffle.js", "devDependencies": {}, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "truffle test", "install": "scripts/install.sh" }, "repository": { diff --git a/test/ownable.js b/test/Ownable.js similarity index 55% rename from test/ownable.js rename to test/Ownable.js index 163e985e3..83ae4b93e 100644 --- a/test/ownable.js +++ b/test/Ownable.js @@ -2,36 +2,36 @@ contract('Ownable', function(accounts) { it("should have an owner", function(done) { var ownable = Ownable.deployed(); return ownable.owner() - .then(function(owner) { - assert.isTrue(owner != 0); - }) - .then(done) + .then(function(owner) { + assert.isTrue(owner != 0); + }) + .then(done) }); it("changes owner after transfer", function(done) { var ownable = Ownable.deployed(); var other = accounts[1]; return ownable.transfer(other) - .then(function() { - return ownable.owner(); - }) - .then(function(owner) { - assert.isTrue(owner === other); - }) - .then(done) + .then(function() { + return ownable.owner(); + }) + .then(function(owner) { + assert.isTrue(owner === other); + }) + .then(done) }); 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() { - return ownable.owner(); - }) - .then(function(owner) { - assert.isFalse(owner === other); - }) - .then(done) + .then(function() { + return ownable.owner(); + }) + .then(function(owner) { + assert.isFalse(owner === other); + }) + .then(done) }); }); diff --git a/test/PullPayment.js b/test/PullPayment.js new file mode 100644 index 000000000..8da3bf2ff --- /dev/null +++ b/test/PullPayment.js @@ -0,0 +1,102 @@ +contract('PullPayment', function(accounts) { + + it("can't call asyncSend externally", function(done) { + return PullPaymentMock.new() + .then(function(ppc) { + assert.isUndefined(ppc.asyncSend); + }) + .then(done); + }); + + it("can record an async payment correctly", function(done) { + var ppce; + var AMOUNT = 100; + return PullPaymentMock.new() + .then(function(_ppce) { + ppce = _ppce; + ppce.callSend(accounts[0], AMOUNT) + }) + .then(function() { + return ppce.payments(accounts[0]); + }) + .then(function(paymentsToAccount0) { + assert.equal(paymentsToAccount0, AMOUNT); + }) + .then(done); + }); + + it("can add multiple balances on one account", function(done) { + var ppce; + return PullPaymentMock.new() + .then(function(_ppce) { + ppce = _ppce; + return ppce.callSend(accounts[0], 200) + }) + .then(function() { + return ppce.callSend(accounts[0], 300) + }) + .then(function() { + return ppce.payments(accounts[0]); + }) + .then(function(paymentsToAccount0) { + assert.equal(paymentsToAccount0, 500); + }) + .then(done); + }); + + it("can add balances on multiple accounts", function(done) { + var ppce; + return PullPaymentMock.new() + .then(function(_ppce) { + ppce = _ppce; + return ppce.callSend(accounts[0], 200) + }) + .then(function() { + return ppce.callSend(accounts[1], 300) + }) + .then(function() { + return ppce.payments(accounts[0]); + }) + .then(function(paymentsToAccount0) { + assert.equal(paymentsToAccount0, 200); + }) + .then(function() { + return ppce.payments(accounts[1]); + }) + .then(function(paymentsToAccount0) { + assert.equal(paymentsToAccount0, 300); + }) + .then(done); + }); + + it("can withdraw payment", function(done) { + var ppce; + var AMOUNT = 17*1e18; + var payee = accounts[1]; + var initialBalance = web3.eth.getBalance(payee); + return PullPaymentMock.new({value: AMOUNT}) + .then(function(_ppce) { + ppce = _ppce; + return ppce.callSend(payee, AMOUNT); + }) + .then(function() { + return ppce.payments(payee); + }) + .then(function(paymentsToAccount0) { + assert.equal(paymentsToAccount0, AMOUNT); + }) + .then(function() { + return ppce.withdrawPayments({from: payee}); + }) + .then(function() { + return ppce.payments(payee); + }) + .then(function(paymentsToAccount0) { + assert.equal(paymentsToAccount0, 0); + var balance = web3.eth.getBalance(payee); + assert(Math.abs(balance-initialBalance-AMOUNT) < 1e16); + }) + .then(done); + }); + +}); diff --git a/test/TestOwnable.sol b/test/TestOwnable.sol new file mode 100644 index 000000000..1a5433598 --- /dev/null +++ b/test/TestOwnable.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.0; +import "truffle/Assert.sol"; +import "truffle/DeployedAddresses.sol"; +import "../contracts/Ownable.sol"; + +contract TestOwnable { + Ownable ownable = new Ownable(); + + function testHasOwner() { + Assert.isNotZero(ownable.owner(), "Ownable should have an owner upon creation."); + } + + function testChangesOwner() { + address originalOwner = ownable.owner(); + ownable.transfer(0x0); + Assert.notEqual(originalOwner, ownable.owner(), "Ownable should change owners after transfer."); + } + + function testOnlyOwnerCanChangeOwner() { + Ownable deployedOwnable = Ownable(DeployedAddresses.Ownable()); + address originalOwner = deployedOwnable.owner(); + deployedOwnable.transfer(0x0); + Assert.equal(originalOwner, deployedOwnable.owner(), "Ownable should prevent non-owners from transfering"); + } +}