diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 389c02c26..606e033b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,109 +1,59 @@ -Contributing to Zeppelin +Contributing to OpenZeppelin ======= -## Design Guidelines +We really appreciate and value contributions to OpenZeppelin. Please take 5' to review the items listed below to make sure that your contributions are merged as soon as possible. -These are some global design goals in Zeppelin. +## Contribution guidelines -### D0 - Security in Depth -We strive to provide secure, tested, audited code. To achieve this, we need to match intention with function. Thus, documentation, code clarity, community review and security discussions are fundamental. +Smart contracts manage value and are highly vulnerable to errors and attacks. We have very strict guidelines, please make sure to review them: ["Contribution guidelines wiki entry"](https://github.com/OpenZeppelin/zeppelin-solidity/wiki/Contrbuting-guidelines). -### D1 - Simple and Modular -Simpler code means easier audits, and better understanding of what each component does. We look for small files, small contracts, and small functions. If you can separate a contract into two independent functionalities you should probably do it. +## Creating Pull Requests (PRs) -### D2 - Naming Matters +As a contributor, you are expected to fork this repository, work on your own fork and then submit pull requests. The pull requests will be reviewed and eventually merged into the main repo. See ["Fork-a-Repo"](https://help.github.com/articles/fork-a-repo/) for how this works. -We take our time with picking names. Code is going to be written once, and read hundreds of times. Renaming for clarity is encouraged. +*IMPORTANT* +* Please use `rebase` instead of `merge` when updating your fork. +* Please see ["Git flow wiki entry"](https://github.com/OpenZeppelin/zeppelin-solidity/wiki/Git-flow) for understanding how to use branches in this repository. -### D3 - Tests +## A typical workflow -Write tests for all your code. We encourage Test Driven Development so we know when our code is right. Even though not all code in the repository is tested at the moment, we aim to test every line of code in the future. +1) Make sure your fork is up to date with the main repository: -### D4 - Check preconditions and post-conditions +``` +cd zeppelin-solidity +git fetch upstream +git checkout development +git pull --rebase upstream development +``` +NOTE: The directory `zeppelin-solidity` represents your fork's local copy. -A very important way to prevent vulnerabilities is to catch a contract’s inconsistent state as early as possible. This is why we want functions to check pre- and post-conditions for executing its logic. When writing code, ask yourself what you are expecting to be true before and after the function runs, and express it in code. - -### D5 - Code Consistency - -Consistency on the way classes are used is paramount to an easier understanding of the library. The codebase should be as unified as possible. Read existing code and get inspired before you write your own. Follow the style guidelines. Don’t hesitate to ask for help on how to best write a specific piece of code. - -### D6 - Regular Audits -Following good programming practices is a way to reduce the risk of vulnerabilities, but professional code audits are still needed. We will perform regular code audits on major releases, and hire security professionals to provide independent review. - -## Style Guidelines - -The design guidelines have quite a high abstraction level. These style guidelines are more concrete and easier to apply, and also more opinionated. - -### General - -#### G0 - Default to Solidity's official style guide. - -Follow the official Solidity style guide: https://solidity.readthedocs.io/en/latest/style-guide.html - -#### G1 - No Magic Constants - -Avoid constants in the code as much as possible. Magic strings are also magic constants. - -#### G2 - Code that Fails Early - -We ask our code to fail as soon as possible when an unexpected input was provided or unexpected state was found. - -#### G3 - Internal Amounts Must be Signed Integers and Represent the Smallest Units. - -Avoid representation errors by always dealing with weis when handling ether. GUIs can convert to more human-friendly representations. Use Signed Integers (int) to prevent underflow problems. - - -### Testing - -#### T1 - Tests Must be Written Elegantly - -Style guidelines are not relaxed for tests. Tests are a good way to show how to use the library, and maintaining them is extremely necessary. - -Don't write long tests, write helper functions to make them be as short and concise as possible (they should take just a few lines each), and use good variable names. - -#### T2 - Tests Must not be Random - -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 - -TODO - -## Pull Request Workflow - -Our workflow is based on GitHub's pull requests. We use feature branches, prepended with: `test`, `feature`, `fix`, `refactor`, or `remove` according to the change the branch introduces. Some examples for such branches are: -```sh -git checkout -b test/some-module -git checkout -b feature/some-new-stuff -git checkout -b fix/some-bug -git checkout -b remove/some-file +2) Branch out from `development` into `fix/some-bug-#123`: +(Postfixing #123 will associate your PR with the issue #123 and make everyone's life easier =D) +``` +git checkout -b fix/some-bug-#123 ``` -If your branch is planned to fix an open issue, postfix your branch name with the issue number like `fix/some-bug-#123`. +3) Make your changes, add your files, commit and push to your fork. -We expect pull requests to be rebased to the master branch before merging: -```sh -git remote add zep git@github.com:OpenZeppelin/zeppelin-solidity.git -git pull --rebase zep master +``` +git add SomeFile.js +git commit "Fix some bug #123" +git push origin fix/some-bug-#123 ``` -Note that we require rebasing your branch instead of merging it, for commit readability reasons. +4) Go to [github.com/OpenZeppelin/zeppelin-solidity](https://github.com/OpenZeppelin/zeppelin-solidity) in your web browser and issue a new pull request. -After that, you can push the changes to your fork, by doing: -```sh -git push origin your_branch_name -git push origin feature/some-new-stuff -git push origin fix/some-bug -``` +*IMPORTANT* Read the PR template very carefully and make sure to follow all the instructions. These instructions +refer to some very important conditions that your PR must meet in order to be accepted, such as making sure that all tests pass, JS linting tests pass, solidity linting tests pass, etc. -Finally go to [github.com/OpenZeppelin/zeppelin-solidity](https://github.com/OpenZeppelin/zeppelin-solidity) in your web browser and issue a new pull request. +5) Maintainers 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 OpenZeppelin. -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. +*IMPORTANT* Please pay attention to the maintainer's feedback, since its a necessary step to keep up with the standards OpenZeppelin attains to. -If you have any questions feel free to post them to -[github.com/OpenZeppelin/zeppelin-solidity/issues](https://github.com/OpenZeppelin/zeppelin-solidity/issues). +## All set! -Finally, if you're looking to collaborate and want to find easy tasks to start, [look at the issues we marked as easy](https://github.com/OpenZeppelin/zeppelin-solidity/labels/easy). +If you have any questions feel free to post them to github.com/OpenZeppelin/zeppelin-solidity/issues. + +Finally, if you're looking to collaborate and want to find easy tasks to start, look at the issues we marked as ["Good first issue"](https://github.com/OpenZeppelin/zeppelin-solidity/labels/good%20first%20issue). Thanks for your time and code! diff --git a/README.md b/README.md index 0cae1a745..d5f555139 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,14 @@ mkdir myproject && cd myproject truffle init ``` -To install the OpenZeppelin library, run: +To install the OpenZeppelin library, run the following in your Solidity project root directory: ```sh -npm init -npm install zeppelin-solidity +npm init -y +npm install -E zeppelin-solidity ``` +**Note that OpenZeppelin does not currently follow semantic versioning.** You may encounter breaking changes upon a minor version bump. We recommend pinning the version of OpenZeppelin you use, as done by the `-E` (`--save-exact`) option. + After that, you'll get all the library's contracts in the `node_modules/zeppelin-solidity/contracts` folder. You can use the contracts in the library like so: ```js @@ -56,6 +58,7 @@ Interested in contributing to OpenZeppelin? - Framework proposal and roadmap: https://medium.com/zeppelin-blog/zeppelin-framework-proposal-and-development-roadmap-fdfa9a3a32ab#.iain47pak - Issue tracker: https://github.com/OpenZeppelin/zeppelin-solidity/issues - Contribution guidelines: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/CONTRIBUTING.md +- Wiki: https://github.com/OpenZeppelin/zeppelin-solidity/wiki ## Collaborating organizations and audits by OpenZeppelin - [Golem](https://golem.network/) diff --git a/contracts/examples/SimpleSavingsWallet.sol b/contracts/examples/SimpleSavingsWallet.sol new file mode 100644 index 000000000..80a70af19 --- /dev/null +++ b/contracts/examples/SimpleSavingsWallet.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.4.11; + +import "../ownership/Heritable.sol"; + + +/** + * @title SimpleSavingsWallet + * @dev Simplest form of savings wallet whose ownership can be claimed by a heir + * if owner dies. + * In this example, we take a very simple savings wallet providing two operations + * (to send and receive funds) and extend its capabilities by making it Heritable. + * The account that creates the contract is set as owner, who has the authority to + * choose an heir account. Heir account can reclaim the contract ownership in the + * case that the owner dies. + */ +contract SimpleSavingsWallet is Heritable { + + event Sent(address indexed payee, uint256 amount, uint256 balance); + event Received(address indexed payer, uint256 amount, uint256 balance); + + + function SimpleSavingsWallet(uint256 _heartbeatTimeout) Heritable(_heartbeatTimeout) public {} + + /** + * @dev wallet can receive funds. + */ + function () public payable { + Received(msg.sender, msg.value, this.balance); + } + + /** + * @dev wallet can send funds + */ + function sendTo(address payee, uint256 amount) public onlyOwner { + require(payee != 0 && payee != address(this)); + require(amount > 0); + payee.transfer(amount); + Sent(payee, amount, this.balance); + } +} diff --git a/contracts/lifecycle/TokenDestructible.sol b/contracts/lifecycle/TokenDestructible.sol index 0bca30e7b..131e57a57 100644 --- a/contracts/lifecycle/TokenDestructible.sol +++ b/contracts/lifecycle/TokenDestructible.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; import "../ownership/Ownable.sol"; -import "../token/ERC20Basic.sol"; +import "../token/ERC20/ERC20Basic.sol"; /** diff --git a/contracts/mocks/DetailedERC20Mock.sol b/contracts/mocks/DetailedERC20Mock.sol index 83fc96b38..81aca044a 100644 --- a/contracts/mocks/DetailedERC20Mock.sol +++ b/contracts/mocks/DetailedERC20Mock.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; import "../token/StandardToken.sol"; -import "../token/DetailedERC20.sol"; +import "../token/ERC20/DetailedERC20.sol"; contract DetailedERC20Mock is StandardToken, DetailedERC20 { diff --git a/contracts/mocks/ERC23TokenMock.sol b/contracts/mocks/ERC223TokenMock.sol similarity index 57% rename from contracts/mocks/ERC23TokenMock.sol rename to contracts/mocks/ERC223TokenMock.sol index d785a72d9..259faf2ab 100644 --- a/contracts/mocks/ERC23TokenMock.sol +++ b/contracts/mocks/ERC223TokenMock.sol @@ -4,20 +4,19 @@ pragma solidity ^0.4.18; import "../token/BasicToken.sol"; -contract ERC23ContractInterface { +contract ERC223ContractInterface { function tokenFallback(address _from, uint256 _value, bytes _data) external; } +contract ERC223TokenMock is BasicToken { -contract ERC23TokenMock is BasicToken { - - function ERC23TokenMock(address initialAccount, uint256 initialBalance) public { + function ERC223TokenMock(address initialAccount, uint256 initialBalance) public { balances[initialAccount] = initialBalance; totalSupply = initialBalance; } - // ERC23 compatible transfer function (except the name) - function transferERC23(address _to, uint256 _value, bytes _data) public + // ERC223 compatible transfer function (except the name) + function transferERC223(address _to, uint256 _value, bytes _data) public returns (bool success) { transfer(_to, _value); @@ -26,7 +25,7 @@ contract ERC23TokenMock is BasicToken { is_contract := not(iszero(extcodesize(_to))) } if (is_contract) { - ERC23ContractInterface receiver = ERC23ContractInterface(_to); + ERC223ContractInterface receiver = ERC223ContractInterface(_to); receiver.tokenFallback(msg.sender, _value, _data); } return true; diff --git a/contracts/mocks/ERC827TokenMock.sol b/contracts/mocks/ERC827TokenMock.sol index 8408d80e0..9ac16cc63 100644 --- a/contracts/mocks/ERC827TokenMock.sol +++ b/contracts/mocks/ERC827TokenMock.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.13; -import "../token/ERC827Token.sol"; +import "../token/ERC827/ERC827Token.sol"; // mock class using ERC827 Token diff --git a/contracts/mocks/SafeERC20Helper.sol b/contracts/mocks/SafeERC20Helper.sol index d41e7a5e1..1a9bcdfc2 100644 --- a/contracts/mocks/SafeERC20Helper.sol +++ b/contracts/mocks/SafeERC20Helper.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; -import "../token/ERC20.sol"; -import "../token/SafeERC20.sol"; +import "../token/ERC20/ERC20.sol"; +import "../token/ERC20/SafeERC20.sol"; contract ERC20FailingMock is ERC20 { diff --git a/contracts/ownership/CanReclaimToken.sol b/contracts/ownership/CanReclaimToken.sol index ab85e1736..69fb95c6e 100644 --- a/contracts/ownership/CanReclaimToken.sol +++ b/contracts/ownership/CanReclaimToken.sol @@ -1,8 +1,8 @@ pragma solidity ^0.4.18; import "./Ownable.sol"; -import "../token/ERC20Basic.sol"; -import "../token/SafeERC20.sol"; +import "../token/ERC20/ERC20Basic.sol"; +import "../token/ERC20/SafeERC20.sol"; /** diff --git a/contracts/ownership/HasNoTokens.sol b/contracts/ownership/HasNoTokens.sol index 9a4e7aabe..ee9531bb7 100644 --- a/contracts/ownership/HasNoTokens.sol +++ b/contracts/ownership/HasNoTokens.sol @@ -6,14 +6,14 @@ import "./CanReclaimToken.sol"; /** * @title Contracts that should not own Tokens * @author Remco Bloemen - * @dev This blocks incoming ERC23 tokens to prevent accidental loss of tokens. + * @dev This blocks incoming ERC223 tokens to prevent accidental loss of tokens. * Should tokens (any ERC20Basic compatible) end up in the contract, it allows the * owner to reclaim the tokens. */ contract HasNoTokens is CanReclaimToken { /** - * @dev Reject all ERC23 compatible tokens + * @dev Reject all ERC223 compatible tokens * @param from_ address The address that is transferring the tokens * @param value_ uint256 the amount of the specified token * @param data_ Bytes The data passed from the caller. diff --git a/contracts/ownership/Heritable.sol b/contracts/ownership/Heritable.sol new file mode 100644 index 000000000..de57fec63 --- /dev/null +++ b/contracts/ownership/Heritable.sol @@ -0,0 +1,99 @@ +pragma solidity ^0.4.11; + + +import "./Ownable.sol"; + + +/** + * @title Heritable + * @dev The Heritable contract provides ownership transfer capabilities, in the + * case that the current owner stops "heartbeating". Only the heir can pronounce the + * owner's death. + */ +contract Heritable is Ownable { + address public heir; + + // Time window the owner has to notify they are alive. + uint256 public heartbeatTimeout; + + // Timestamp of the owner's death, as pronounced by the heir. + uint256 public timeOfDeath; + + event HeirChanged(address indexed owner, address indexed newHeir); + event OwnerHeartbeated(address indexed owner); + event OwnerProclaimedDead(address indexed owner, address indexed heir, uint256 timeOfDeath); + event HeirOwnershipClaimed(address indexed previousOwner, address indexed newOwner); + + + /** + * @dev Throw an exception if called by any account other than the heir's. + */ + modifier onlyHeir() { + require(msg.sender == heir); + _; + } + + + /** + * @notice Create a new Heritable Contract with heir address 0x0. + * @param _heartbeatTimeout time available for the owner to notify they are alive, + * before the heir can take ownership. + */ + function Heritable(uint256 _heartbeatTimeout) public { + setHeartbeatTimeout(_heartbeatTimeout); + } + + function setHeir(address newHeir) public onlyOwner { + require(newHeir != owner); + heartbeat(); + HeirChanged(owner, newHeir); + heir = newHeir; + } + + /** + * @dev set heir = 0x0 + */ + function removeHeir() public onlyOwner { + heartbeat(); + heir = 0; + } + + /** + * @dev Heir can pronounce the owners death. To claim the ownership, they will + * have to wait for `heartbeatTimeout` seconds. + */ + function proclaimDeath() public onlyHeir { + require(ownerLives()); + OwnerProclaimedDead(owner, heir, timeOfDeath); + timeOfDeath = now; + } + + /** + * @dev Owner can send a heartbeat if they were mistakenly pronounced dead. + */ + function heartbeat() public onlyOwner { + OwnerHeartbeated(owner); + timeOfDeath = 0; + } + + /** + * @dev Allows heir to transfer ownership only if heartbeat has timed out. + */ + function claimHeirOwnership() public onlyHeir { + require(!ownerLives()); + require(now >= timeOfDeath + heartbeatTimeout); + OwnershipTransferred(owner, heir); + HeirOwnershipClaimed(owner, heir); + owner = heir; + timeOfDeath = 0; + } + + function setHeartbeatTimeout(uint256 newHeartbeatTimeout) internal onlyOwner { + require(ownerLives()); + heartbeatTimeout = newHeartbeatTimeout; + } + + function ownerLives() internal view returns (bool) { + return timeOfDeath == 0; + } +} diff --git a/contracts/token/BasicToken.sol b/contracts/token/BasicToken.sol index 469fedbcc..46e8bf870 100644 --- a/contracts/token/BasicToken.sol +++ b/contracts/token/BasicToken.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; -import "./ERC20Basic.sol"; +import "./ERC20/ERC20Basic.sol"; import "../math/SafeMath.sol"; diff --git a/contracts/token/DetailedERC20.sol b/contracts/token/ERC20/DetailedERC20.sol similarity index 100% rename from contracts/token/DetailedERC20.sol rename to contracts/token/ERC20/DetailedERC20.sol diff --git a/contracts/token/ERC20.sol b/contracts/token/ERC20/ERC20.sol similarity index 100% rename from contracts/token/ERC20.sol rename to contracts/token/ERC20/ERC20.sol diff --git a/contracts/token/ERC20Basic.sol b/contracts/token/ERC20/ERC20Basic.sol similarity index 100% rename from contracts/token/ERC20Basic.sol rename to contracts/token/ERC20/ERC20Basic.sol diff --git a/contracts/token/SafeERC20.sol b/contracts/token/ERC20/SafeERC20.sol similarity index 100% rename from contracts/token/SafeERC20.sol rename to contracts/token/ERC20/SafeERC20.sol diff --git a/contracts/token/ERC827.sol b/contracts/token/ERC827/ERC827.sol similarity index 95% rename from contracts/token/ERC827.sol rename to contracts/token/ERC827/ERC827.sol index f0793e607..31a17cd80 100644 --- a/contracts/token/ERC827.sol +++ b/contracts/token/ERC827/ERC827.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.13; -import "./ERC20.sol"; +import "../ERC20/ERC20.sol"; /** diff --git a/contracts/token/ERC827Token.sol b/contracts/token/ERC827/ERC827Token.sol similarity index 99% rename from contracts/token/ERC827Token.sol rename to contracts/token/ERC827/ERC827Token.sol index 173acf98f..f027c80ff 100644 --- a/contracts/token/ERC827Token.sol +++ b/contracts/token/ERC827/ERC827Token.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.13; import "./ERC827.sol"; -import "./StandardToken.sol"; +import "../StandardToken.sol"; /** @title ERC827, an extension of ERC20 token standard diff --git a/contracts/token/StandardToken.sol b/contracts/token/StandardToken.sol index 1aa10d3f9..a3a1c3445 100644 --- a/contracts/token/StandardToken.sol +++ b/contracts/token/StandardToken.sol @@ -2,7 +2,7 @@ pragma solidity ^0.4.18; import "./BasicToken.sol"; -import "./ERC20.sol"; +import "./ERC20/ERC20.sol"; /** diff --git a/contracts/token/TokenTimelock.sol b/contracts/token/TokenTimelock.sol index 3fd0103a5..2562b0940 100644 --- a/contracts/token/TokenTimelock.sol +++ b/contracts/token/TokenTimelock.sol @@ -1,7 +1,6 @@ pragma solidity ^0.4.18; -import "./ERC20Basic.sol"; -import "../token/SafeERC20.sol"; +import "./ERC20/SafeERC20.sol"; /** diff --git a/contracts/token/TokenVesting.sol b/contracts/token/TokenVesting.sol index 01bdff34e..a8b45c01e 100644 --- a/contracts/token/TokenVesting.sol +++ b/contracts/token/TokenVesting.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; -import "./ERC20Basic.sol"; -import "./SafeERC20.sol"; +import "./ERC20/ERC20Basic.sol"; +import "./ERC20/SafeERC20.sol"; import "../ownership/Ownable.sol"; import "../math/SafeMath.sol"; diff --git a/test/Heritable.test.js b/test/Heritable.test.js new file mode 100644 index 000000000..033fe217d --- /dev/null +++ b/test/Heritable.test.js @@ -0,0 +1,110 @@ +import increaseTime from './helpers/increaseTime'; +import expectThrow from './helpers/expectThrow'; + +const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; + +const Heritable = artifacts.require('../contracts/ownership/Heritable.sol'); + +contract('Heritable', function (accounts) { + let heritable; + let owner; + + beforeEach(async function () { + heritable = await Heritable.new(4141); + owner = await heritable.owner(); + }); + + it('should start off with an owner, but without heir', async function () { + const heir = await heritable.heir(); + + assert.equal(typeof (owner), 'string'); + assert.equal(typeof (heir), 'string'); + assert.notStrictEqual( + owner, NULL_ADDRESS, + 'Owner shouldn\'t be the null address' + ); + assert.isTrue( + heir === NULL_ADDRESS, + 'Heir should be the null address' + ); + }); + + it('only owner should set heir', async function () { + const newHeir = accounts[1]; + const someRandomAddress = accounts[2]; + assert.isTrue(owner !== someRandomAddress); + + await heritable.setHeir(newHeir, { from: owner }); + await expectThrow(heritable.setHeir(newHeir, { from: someRandomAddress })); + }); + + it('owner can remove heir', async function () { + const newHeir = accounts[1]; + await heritable.setHeir(newHeir, { from: owner }); + let heir = await heritable.heir(); + + assert.notStrictEqual(heir, NULL_ADDRESS); + await heritable.removeHeir(); + heir = await heritable.heir(); + assert.isTrue(heir === NULL_ADDRESS); + }); + + it('heir can claim ownership only if owner is dead and timeout was reached', async function () { + const heir = accounts[1]; + await heritable.setHeir(heir, { from: owner }); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + + await heritable.proclaimDeath({ from: heir }); + await increaseTime(1); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + + await increaseTime(4141); + await heritable.claimHeirOwnership({ from: heir }); + assert.isTrue(await heritable.heir() === heir); + }); + + it('heir can\'t claim ownership if owner heartbeats', async function () { + const heir = accounts[1]; + await heritable.setHeir(heir, { from: owner }); + + await heritable.proclaimDeath({ from: heir }); + await heritable.heartbeat({ from: owner }); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + + await heritable.proclaimDeath({ from: heir }); + await increaseTime(4141); + await heritable.heartbeat({ from: owner }); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + }); + + it('should log events appropriately', async function () { + const heir = accounts[1]; + + const setHeirLogs = (await heritable.setHeir(heir, { from: owner })).logs; + const setHeirEvent = setHeirLogs.find(e => e.event === 'HeirChanged'); + + assert.isTrue(setHeirEvent.args.owner === owner); + assert.isTrue(setHeirEvent.args.newHeir === heir); + + const heartbeatLogs = (await heritable.heartbeat({ from: owner })).logs; + const heartbeatEvent = heartbeatLogs.find(e => e.event === 'OwnerHeartbeated'); + + assert.isTrue(heartbeatEvent.args.owner === owner); + + const proclaimDeathLogs = (await heritable.proclaimDeath({ from: heir })).logs; + const ownerDeadEvent = proclaimDeathLogs.find(e => e.event === 'OwnerProclaimedDead'); + + assert.isTrue(ownerDeadEvent.args.owner === owner); + assert.isTrue(ownerDeadEvent.args.heir === heir); + + await increaseTime(4141); + const claimHeirOwnershipLogs = (await heritable.claimHeirOwnership({ from: heir })).logs; + const ownershipTransferredEvent = claimHeirOwnershipLogs.find(e => e.event === 'OwnershipTransferred'); + const heirOwnershipClaimedEvent = claimHeirOwnershipLogs.find(e => e.event === 'HeirOwnershipClaimed'); + + assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner); + assert.isTrue(ownershipTransferredEvent.args.newOwner === heir); + assert.isTrue(heirOwnershipClaimedEvent.args.previousOwner === owner); + assert.isTrue(heirOwnershipClaimedEvent.args.newOwner === heir); + }); +}); diff --git a/test/SimpleSavingsWallet.test.js b/test/SimpleSavingsWallet.test.js new file mode 100644 index 000000000..007363a18 --- /dev/null +++ b/test/SimpleSavingsWallet.test.js @@ -0,0 +1,33 @@ + +import expectThrow from './helpers/expectThrow'; + +const SimpleSavingsWallet = artifacts.require('../contracts/examples/SimpleSavingsWallet.sol'); + +contract('SimpleSavingsWallet', function (accounts) { + let savingsWallet; + let owner; + + const paymentAmount = 4242; + + beforeEach(async function () { + savingsWallet = await SimpleSavingsWallet.new(4141); + owner = await savingsWallet.owner(); + }); + + it('should receive funds', async function () { + await web3.eth.sendTransaction({ from: owner, to: savingsWallet.address, value: paymentAmount }); + assert.isTrue((new web3.BigNumber(paymentAmount)).equals(web3.eth.getBalance(savingsWallet.address))); + }); + + it('owner can send funds', async function () { + // Receive payment so we have some money to spend. + await web3.eth.sendTransaction({ from: accounts[9], to: savingsWallet.address, value: 1000000 }); + await expectThrow(savingsWallet.sendTo(0, paymentAmount, { from: owner })); + await expectThrow(savingsWallet.sendTo(savingsWallet.address, paymentAmount, { from: owner })); + await expectThrow(savingsWallet.sendTo(accounts[1], 0, { from: owner })); + + const balance = web3.eth.getBalance(accounts[1]); + await savingsWallet.sendTo(accounts[1], paymentAmount, { from: owner }); + assert.isTrue(balance.plus(paymentAmount).equals(web3.eth.getBalance(accounts[1]))); + }); +}); diff --git a/test/SimpleToken.test.js b/test/examples/SimpleToken.test.js similarity index 90% rename from test/SimpleToken.test.js rename to test/examples/SimpleToken.test.js index 9a1870f20..8d4c33091 100644 --- a/test/SimpleToken.test.js +++ b/test/examples/SimpleToken.test.js @@ -1,5 +1,5 @@ -import decodeLogs from './helpers/decodeLogs'; -const SimpleToken = artifacts.require('examples/SimpleToken.sol'); +import decodeLogs from '../helpers/decodeLogs'; +const SimpleToken = artifacts.require('SimpleToken.sol'); contract('SimpleToken', accounts => { let token; @@ -23,7 +23,7 @@ contract('SimpleToken', accounts => { const decimals = await token.decimals(); assert(decimals.eq(18)); }); - + it('assigns the initial total supply to the creator', async function () { const totalSupply = await token.totalSupply(); const creatorBalance = await token.balanceOf(creator); diff --git a/test/ECRecovery.test.js b/test/library/ECRecovery.test.js similarity index 92% rename from test/ECRecovery.test.js rename to test/library/ECRecovery.test.js index 927441f4b..ae69f42cf 100644 --- a/test/ECRecovery.test.js +++ b/test/library/ECRecovery.test.js @@ -1,7 +1,7 @@ -var ECRecoveryMock = artifacts.require('../contracts/mocks/ECRecoveryMock.sol'); -var ECRecoveryLib = artifacts.require('../contracts/ECRecovery.sol'); +var ECRecoveryMock = artifacts.require('ECRecoveryMock.sol'); +var ECRecoveryLib = artifacts.require('ECRecovery.sol'); -var hashMessage = require('./helpers/hashMessage.js'); +var hashMessage = require('../helpers/hashMessage.js'); contract('ECRecovery', function (accounts) { let ecrecovery; diff --git a/test/Math.test.js b/test/library/Math.test.js similarity index 89% rename from test/Math.test.js rename to test/library/Math.test.js index 7378610b6..b112646a0 100644 --- a/test/Math.test.js +++ b/test/library/Math.test.js @@ -1,4 +1,4 @@ -var MathMock = artifacts.require('./mocks/MathMock.sol'); +var MathMock = artifacts.require('../mocks/MathMock.sol'); contract('Math', function (accounts) { let math; diff --git a/test/MerkleProof.test.js b/test/library/MerkleProof.test.js similarity index 94% rename from test/MerkleProof.test.js rename to test/library/MerkleProof.test.js index f55d6ebc8..9d464401f 100644 --- a/test/MerkleProof.test.js +++ b/test/library/MerkleProof.test.js @@ -1,8 +1,8 @@ -import MerkleTree from './helpers/merkleTree.js'; +import MerkleTree from '../helpers/merkleTree.js'; import { sha3, bufferToHex } from 'ethereumjs-util'; -var MerkleProof = artifacts.require('./MerkleProof.sol'); +var MerkleProof = artifacts.require('MerkleProof.sol'); contract('MerkleProof', function (accounts) { let merkleProof; diff --git a/test/ownership/HasNoTokens.test.js b/test/ownership/HasNoTokens.test.js index e6622a16d..ac3690fd3 100644 --- a/test/ownership/HasNoTokens.test.js +++ b/test/ownership/HasNoTokens.test.js @@ -2,7 +2,7 @@ import expectThrow from '../helpers/expectThrow'; const HasNoTokens = artifacts.require('../contracts/lifecycle/HasNoTokens.sol'); -const ERC23TokenMock = artifacts.require('mocks/ERC23TokenMock.sol'); +const ERC223TokenMock = artifacts.require('mocks/ERC223TokenMock.sol'); contract('HasNoTokens', function (accounts) { let hasNoTokens = null; @@ -11,7 +11,7 @@ contract('HasNoTokens', function (accounts) { beforeEach(async () => { // Create contract and token hasNoTokens = await HasNoTokens.new(); - token = await ERC23TokenMock.new(accounts[0], 100); + token = await ERC223TokenMock.new(accounts[0], 100); // Force token into contract await token.transfer(hasNoTokens.address, 10); @@ -19,8 +19,8 @@ contract('HasNoTokens', function (accounts) { assert.equal(startBalance, 10); }); - it('should not accept ERC23 tokens', async function () { - await expectThrow(token.transferERC23(hasNoTokens.address, 10, '')); + it('should not accept ERC223 tokens', async function () { + await expectThrow(token.transferERC223(hasNoTokens.address, 10, '')); }); it('should allow owner to reclaim tokens', async function () { diff --git a/test/token/DetailedERC20.test.js b/test/token/ERC20/DetailedERC20.test.js similarity index 91% rename from test/token/DetailedERC20.test.js rename to test/token/ERC20/DetailedERC20.test.js index 16155a4b8..cfb1d1e71 100644 --- a/test/token/DetailedERC20.test.js +++ b/test/token/ERC20/DetailedERC20.test.js @@ -5,7 +5,7 @@ require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); -const DetailedERC20Mock = artifacts.require('mocks/DetailedERC20Mock.sol'); +const DetailedERC20Mock = artifacts.require('DetailedERC20Mock.sol'); contract('DetailedERC20', accounts => { let detailedERC20 = null; diff --git a/test/token/SafeERC20.test.js b/test/token/ERC20/SafeERC20.test.js similarity index 90% rename from test/token/SafeERC20.test.js rename to test/token/ERC20/SafeERC20.test.js index 0602e08ff..40a43e1a7 100644 --- a/test/token/SafeERC20.test.js +++ b/test/token/ERC20/SafeERC20.test.js @@ -1,10 +1,10 @@ -import EVMThrow from '../helpers/EVMThrow'; +import EVMThrow from '../../helpers/EVMThrow'; require('chai') .use(require('chai-as-promised')) .should(); -const SafeERC20Helper = artifacts.require('mocks/SafeERC20Helper.sol'); +const SafeERC20Helper = artifacts.require('SafeERC20Helper.sol'); contract('SafeERC20', function () { beforeEach(async function () { diff --git a/test/ERC827Token.js b/test/token/ERC827/ERC827Token.js similarity index 98% rename from test/ERC827Token.js rename to test/token/ERC827/ERC827Token.js index a88540ba6..302cca7dd 100644 --- a/test/ERC827Token.js +++ b/test/token/ERC827/ERC827Token.js @@ -1,7 +1,7 @@ -import EVMRevert from './helpers/EVMRevert'; -var Message = artifacts.require('./mock/MessageHelper.sol'); -var ERC827TokenMock = artifacts.require('./mock/ERC827TokenMock.sol'); +import EVMRevert from '../../helpers/EVMRevert'; +var Message = artifacts.require('MessageHelper.sol'); +var ERC827TokenMock = artifacts.require('ERC827TokenMock.sol'); var BigNumber = web3.BigNumber; var _ = require('lodash');