Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15169b245b | |||
| cc222c02a9 | |||
| 29ab824e5c | |||
| 8d828bc789 | |||
| f038cdc9c9 | |||
| 72d31c2471 | |||
| b31c92c27c | |||
| eb2cb5dd06 | |||
| 52d0df7961 | |||
| 12aadbed51 | |||
| 23703280ee | |||
| 5e50090da0 | |||
| b9257a1092 | |||
| 8cd58303f9 | |||
| b134645637 | |||
| d41cc437b3 | |||
| 26335e669e | |||
| 52b66c744f | |||
| 65c9711754 | |||
| 3d6988cf90 | |||
| c10a2cf15c | |||
| 44082c029f | |||
| 8ba0f008df | |||
| fcabd9957e | |||
| 9f12e870a9 | |||
| d24a2ed9a8 | |||
| b1cb7b5975 | |||
| 9ab997d788 | |||
| 90654cb2c9 | |||
| 9bd51db2b2 | |||
| 7e7193ae61 | |||
| a69333e2b8 | |||
| 1f35727442 | |||
| cbc18e6dcc | |||
| 60b48b0235 | |||
| f4624837a3 | |||
| 0a5af4b8ac | |||
| 74dfd3511f | |||
| ee56abcc8a | |||
| b67f60929c | |||
| 0b71dcded2 | |||
| f305382ef4 | |||
| 917b129517 | |||
| abc646a95c | |||
| 1697518da8 | |||
| 93e7984c61 | |||
| 8d9e12eda3 | |||
| fb0a96332c | |||
| ba7a0f7649 | |||
| a72de719c9 | |||
| ec6e728c0f | |||
| 70fbc7fe27 | |||
| ced4e2da3f | |||
| ae73f1a564 | |||
| 26127ee36a | |||
| 2f0471fff2 | |||
| ad833fb40d | |||
| a544f1517c | |||
| 67ad6abdaa | |||
| 0dfb27ff5f | |||
| ca8f2f2362 | |||
| 2de7fec6dc | |||
| 342e7bf9ff | |||
| ac6f9288a8 | |||
| d54e56f970 | |||
| d974924045 | |||
| e310e6728b | |||
| 74adaeedf2 | |||
| a5602e7e7b | |||
| 2a75a2c9f4 | |||
| 6fa9e048da | |||
| c21006e6d3 | |||
| aa438fa053 | |||
| 1529e19b09 | |||
| 813c1ff0cd | |||
| 8aa06bda38 | |||
| 36fa2a72cf | |||
| 11cf9f8752 | |||
| 9c81dd29bb | |||
| bb22cd62ac | |||
| e5eaa919b1 | |||
| ba37d91878 | |||
| e8459148a8 | |||
| f292643543 | |||
| 80cc1808fc | |||
| d38941062b | |||
| f79ad4e2f6 | |||
| 5567e73730 | |||
| 2ddfb80a5e | |||
| 6a64d98a34 | |||
| b1b66ce165 | |||
| e6c50b3318 | |||
| 260edd8702 | |||
| 920e3ec940 | |||
| 2299cd1c39 | |||
| 31a01ebde1 | |||
| ec07dce76f | |||
| 8ba40d066f | |||
| ed6df57f88 | |||
| 02f7f4c49d | |||
| 77e732218e | |||
| 41fdd5d4c6 | |||
| e859d53b6c | |||
| 9d56414b75 | |||
| fe184fdbdf | |||
| c15fa95bea | |||
| 0e58f7b992 | |||
| 423cec41e3 | |||
| c390e94da6 | |||
| 0ec09f4131 | |||
| ab93a8e3e8 | |||
| c7eb6736ee | |||
| 42ccbb2276 | |||
| 38545eb648 | |||
| eb41a81faa | |||
| 688106e9c3 | |||
| f2142545c7 | |||
| c920c40507 | |||
| 475cb5dc1f | |||
| 85a4013f49 | |||
| 0aa4d02044 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
*.swo
|
||||
node_modules/
|
||||
build/
|
||||
.DS_Store/
|
||||
|
||||
48
.jshintrc
Normal file
48
.jshintrc
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
|
||||
"browser": true, // Standard browser globals e.g. `window`, `document`.
|
||||
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
|
||||
"curly": true, // Require {} for every new block or scope.
|
||||
"devel": false, // Allow development statements e.g. `console.log();`.
|
||||
"eqeqeq": true, // Require triple equals i.e. `===`.
|
||||
"esnext": true, // Allow ES.next specific features such as `const` and `let`.
|
||||
"freeze": true, // Forbid overwriting prototypes of native objects such as Array, Date and so on.
|
||||
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||
"indent": 2, // Specify indentation spacing
|
||||
"latedef": true, // Prohibit variable use before definition.
|
||||
"newcap": false, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||
"noempty": true, // Prohibit use of empty blocks.
|
||||
"nonew": true, // Prohibits the use of constructor functions for side-effects
|
||||
"quotmark": "single", // Define quotes to string values.
|
||||
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||
"smarttabs": false, // Supress warnings about mixed tabs and spaces
|
||||
"strict": true, // Require `use strict` pragma in every file.
|
||||
"trailing": true, // Prohibit trailing whitespaces.
|
||||
"undef": true, // Require all non-global variables be declared before they are used.
|
||||
"unused": true, // Warn unused variables.
|
||||
|
||||
"maxparams": 4, // Maximum number of parameters for a function
|
||||
"maxstatements": 15, // Maximum number of statements in a function
|
||||
"maxcomplexity": 10, // Cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity)
|
||||
"maxdepth": 4, // Maximum depth of nested control structures
|
||||
"maxlen": 120, // Maximum number of cols in a line
|
||||
"multistr": true, // Allow use of multiline EOL escaping
|
||||
"experimental": ["asyncawait", "asyncreqawait"],
|
||||
|
||||
"predef": [ // Extra globals.
|
||||
"after",
|
||||
"afterEach",
|
||||
"before",
|
||||
"beforeEach",
|
||||
"define",
|
||||
"describe",
|
||||
"exports",
|
||||
"it",
|
||||
"web3",
|
||||
"artifacts",
|
||||
"contract",
|
||||
"assert"
|
||||
]
|
||||
}
|
||||
1
.soliumignore
Normal file
1
.soliumignore
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
||||
22
.soliumrc.json
Normal file
22
.soliumrc.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"custom-rules-filename": null,
|
||||
"rules": {
|
||||
"imports-on-top": true,
|
||||
"variable-declarations": true,
|
||||
"array-declarations": true,
|
||||
"operator-whitespace": true,
|
||||
"lbrace": true,
|
||||
"mixedcase": false,
|
||||
"camelcase": true,
|
||||
"uppercase": true,
|
||||
"no-with": true,
|
||||
"no-empty-blocks": true,
|
||||
"no-unused-vars": true,
|
||||
"double-quotes": true,
|
||||
"blank-lines": true,
|
||||
"indentation": true,
|
||||
"whitespace": true,
|
||||
"deprecated-suicide": true,
|
||||
"pragma-on-top": true
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,8 @@ language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
before_install:
|
||||
- npm install truffle@3.1.9 -g
|
||||
- npm i -g ethereumjs-testrpc
|
||||
- npm i -g truffle
|
||||
script:
|
||||
- testrpc&
|
||||
- npm test
|
||||
- testrpc > /dev/null &
|
||||
- truffle test
|
||||
|
||||
@ -102,4 +102,6 @@ Main contributors will review your code and possibly ask for changes before your
|
||||
If you have any questions feel free to post them to
|
||||
[github.com/OpenZeppelin/zeppelin-solidity/issues](https://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 easy](https://github.com/OpenZeppelin/zeppelin-solidity/labels/easy).
|
||||
|
||||
Thanks for your time and code!
|
||||
|
||||
233
README.md
233
README.md
@ -1,12 +1,14 @@
|
||||
# Zeppelin Solidity
|
||||
[](https://www.npmjs.org/package/zeppelin-solidity)
|
||||
[](https://travis-ci.org/bitpay/bitcore-lib)
|
||||
[](https://travis-ci.org/OpenZeppelin/zeppelin-solidity)
|
||||
|
||||
Zeppelin is a library for writing secure Smart Contracts on Ethereum.
|
||||
Zeppelin is a library for writing secure [Smart Contracts](https://en.wikipedia.org/wiki/Smart_contract) on Ethereum.
|
||||
|
||||
With Zeppelin, you can build distributed applications, protocols and organizations:
|
||||
- using common contract security patterns (See [Onward with Ethereum Smart Contract Security](https://medium.com/bitcorps-blog/onward-with-ethereum-smart-contract-security-97a827e47702#.y3kvdetbz))
|
||||
- in the Solidity language.
|
||||
- in the [Solidity language](http://solidity.readthedocs.io/en/develop/).
|
||||
|
||||
> NOTE: New to smart contract development? Check our [introductory guide](https://medium.com/zeppelin-blog/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05#.cox40d2ut).
|
||||
|
||||
## Getting Started
|
||||
|
||||
@ -53,228 +55,12 @@ Zeppelin is meant to provide secure, tested and community-audited code, but plea
|
||||
|
||||
If you find a security issue, please email [security@openzeppelin.org](mailto:security@openzeppelin.org).
|
||||
|
||||
## Contracts
|
||||
|
||||
### Ownable
|
||||
Base contract with an owner.
|
||||
|
||||
#### Ownable( )
|
||||
Sets the address of the creator of the contract as the owner.
|
||||
|
||||
#### modifier onlyOwner( )
|
||||
Prevents function from running if it is called by anyone other than the owner.
|
||||
|
||||
#### transfer(address newOwner) onlyOwner
|
||||
Transfers ownership of the contract to the passed address.
|
||||
|
||||
---
|
||||
### Stoppable
|
||||
Base contract that provides an emergency stop mechanism.
|
||||
|
||||
Inherits from contract Ownable.
|
||||
|
||||
#### emergencyStop( ) external onlyOwner
|
||||
Triggers the stop mechanism on the contract. After this function is called (by the owner of the contract), any function with modifier stopInEmergency will not run.
|
||||
|
||||
#### modifier stopInEmergency
|
||||
Prevents function from running if stop mechanism is activated.
|
||||
|
||||
#### modifier onlyInEmergency
|
||||
Only runs if stop mechanism is activated.
|
||||
|
||||
#### release( ) external onlyOwner onlyInEmergency
|
||||
Deactivates the stop mechanism.
|
||||
|
||||
---
|
||||
### Killable
|
||||
Base contract that can be killed by owner.
|
||||
|
||||
Inherits from contract Ownable.
|
||||
|
||||
#### kill( ) onlyOwner
|
||||
Destroys the contract and sends funds back to the owner.
|
||||
___
|
||||
### Claimable
|
||||
Extension for the Ownable contract, where the ownership needs to be claimed
|
||||
|
||||
#### transfer(address newOwner) onlyOwner
|
||||
Sets the passed address as the pending owner.
|
||||
|
||||
#### modifier onlyPendingOwner
|
||||
Function only runs if called by pending owner.
|
||||
|
||||
#### claimOwnership( ) onlyPendingOwner
|
||||
Completes transfer of ownership by setting pending owner as the new owner.
|
||||
___
|
||||
### Migrations
|
||||
Base contract that allows for a new instance of itself to be created at a different address.
|
||||
|
||||
Inherits from contract Ownable.
|
||||
|
||||
#### upgrade(address new_address) onlyOwner
|
||||
Creates a new instance of the contract at the passed address.
|
||||
|
||||
#### setCompleted(uint completed) onlyOwner
|
||||
Sets the last time that a migration was completed.
|
||||
|
||||
___
|
||||
### SafeMath
|
||||
Provides functions of mathematical operations with safety checks.
|
||||
|
||||
#### assert(bool assertion) internal
|
||||
Throws an error if the passed result is false. Used in this contract by checking mathematical expressions.
|
||||
|
||||
#### safeMul(uint a, uint b) internal returns (uint)
|
||||
Multiplies two unisgned integers. Asserts that dividing the product by the non-zero multiplicand results in the multiplier.
|
||||
|
||||
#### safeSub(uint a, unit b) internal returns (uint)
|
||||
Checks that b is not greater than a before subtracting.
|
||||
|
||||
#### safeAdd(unit a, unit b) internal returns (uint)
|
||||
Checks that the result is greater than both a and b.
|
||||
|
||||
___
|
||||
### LimitBalance
|
||||
|
||||
Base contract that provides mechanism for limiting the amount of funds a contract can hold.
|
||||
|
||||
#### LimitBalance(unit _limit)
|
||||
Constructor takes an unisgned integer and sets it as the limit of funds this contract can hold.
|
||||
|
||||
#### modifier limitedPayable()
|
||||
Throws an error if this contract's balance is already above the limit.
|
||||
|
||||
___
|
||||
### PullPayment
|
||||
Base contract supporting async send for pull payments.
|
||||
Inherit from this contract and use asyncSend instead of send.
|
||||
|
||||
#### asyncSend(address dest, uint amount) internal
|
||||
Adds sent amount to available balance that payee can pull from this contract, called by payer.
|
||||
|
||||
#### withdrawPayments( )
|
||||
Sends designated balance to payee calling the contract. Throws error if designated balance is 0, if contract does not hold enough funds ot pay the payee, or if the send transaction is not successful.
|
||||
|
||||
___
|
||||
### StandardToken
|
||||
Based on code by FirstBlood: [FirstBloodToken.sol]
|
||||
|
||||
Inherits from contract SafeMath. Implementation of abstract contract ERC20 (see https://github.com/ethereum/EIPs/issues/20)
|
||||
|
||||
[FirstBloodToken.sol]: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
|
||||
|
||||
#### approve(address _spender, uint _value) returns (bool success)
|
||||
Sets the amount of the sender's token balance that the passed address is approved to use.
|
||||
|
||||
###allowance(address _owner, address _spender) constant returns (uint remaining)
|
||||
Returns the approved amount of the owner's balance that the spender can use.
|
||||
|
||||
###balanceOf(address _owner) constant returns (uint balance)
|
||||
Returns the token balance of the passed address.
|
||||
|
||||
###transferFrom(address _from, address _to, uint _value) returns (bool success)
|
||||
Transfers tokens from an account that the sender is approved to transfer from. Amount must not be greater than the approved amount or the account's balance.
|
||||
|
||||
###function transfer(address _to, uint _value) returns (bool success)
|
||||
Transfers tokens from sender's account. Amount must not be greater than sender's balance.
|
||||
|
||||
___
|
||||
### BasicToken
|
||||
Simpler version of StandardToken, with no allowances
|
||||
|
||||
#### balanceOf(address _owner) constant returns (uint balance)
|
||||
Returns the token balance of the passed address.
|
||||
|
||||
###function transfer(address _to, uint _value) returns (bool success)
|
||||
Transfers tokens from sender's account. Amount must not be greater than sender's balance.
|
||||
|
||||
___
|
||||
### CrowdsaleToken
|
||||
Simple ERC20 Token example, with crowdsale token creation.
|
||||
|
||||
Inherits from contract StandardToken.
|
||||
|
||||
#### createTokens(address recipient) payable
|
||||
Creates tokens based on message value and credits to the recipient.
|
||||
|
||||
#### getPrice() constant returns (uint result)
|
||||
Returns the amount of tokens per 1 ether.
|
||||
|
||||
|
||||
___
|
||||
### Bounty
|
||||
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 {Bounty, Target} from "./zeppelin/Bounty.sol";
|
||||
import "./YourContract.sol";
|
||||
|
||||
contract YourBounty is Bounty {
|
||||
function deployContract() internal returns(address) {
|
||||
return new YourContract()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next, implement invariant logic into your smart contract.
|
||||
Your main contract should inherit from the Target class and implement the checkInvariant method. This is a function that should check everything your contract assumes to be true all the time. If this function returns false, it means your contract was broken in some way and is in an inconsistent state. This is what security researchers will try to acomplish when trying to get the bounty.
|
||||
|
||||
At contracts/YourContract.sol
|
||||
|
||||
```
|
||||
import {Bounty, Target} from "./zeppelin/Bounty.sol";
|
||||
contract YourContract is Target {
|
||||
function checkInvariant() returns(bool) {
|
||||
// Implement your logic to make sure that none of the invariants are broken.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next, deploy your bounty contract along with your main contract to the network.
|
||||
|
||||
At `migrations/2_deploy_contracts.js`
|
||||
|
||||
```
|
||||
module.exports = function(deployer) {
|
||||
deployer.deploy(YourContract);
|
||||
deployer.deploy(YourBounty);
|
||||
};
|
||||
```
|
||||
|
||||
Next, add a reward to the bounty contract
|
||||
|
||||
After deploying the contract, send reward funds into the bounty contract.
|
||||
|
||||
From `truffle console`
|
||||
|
||||
```
|
||||
bounty = YourBounty.deployed();
|
||||
address = 0xb9f68f96cde3b895cc9f6b14b856081b41cb96f1; // your account address
|
||||
reward = 5; // reward to pay to a researcher who breaks your contract
|
||||
|
||||
web3.eth.sendTransaction({
|
||||
from: address,
|
||||
to: bounty.address,
|
||||
value: web3.toWei(reward, "ether")
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
If researchers break the contract, they can 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.
|
||||
|
||||
Finally, if you manage to protect your contract from security researchers, you can reclaim the bounty funds. To end the bounty, kill the contract so that all the rewards go back to the owner.
|
||||
|
||||
```
|
||||
bounty.kill();
|
||||
```
|
||||
|
||||
|
||||
## More Developer Resources
|
||||
## Developer Resources
|
||||
|
||||
Building a distributed application, protocol or organization with Zeppelin?
|
||||
|
||||
- Read documentation: http://zeppelin-solidity.readthedocs.io/en/latest/
|
||||
|
||||
- Ask for help and follow progress at: https://zeppelin-slackin.herokuapp.com/
|
||||
|
||||
Interested in contributing to Zeppelin?
|
||||
@ -285,7 +71,7 @@ Interested in contributing to Zeppelin?
|
||||
|
||||
## Collaborating organizations and audits by Zeppelin
|
||||
- [Golem](https://golem.network/)
|
||||
- [Mediachain](https://golem.network/)
|
||||
- [Mediachain](http://www.mediachain.io/)
|
||||
- [Truffle](http://truffleframework.com/)
|
||||
- [Firstblood](http://firstblood.io/)
|
||||
- [Rootstock](http://www.rsk.co/)
|
||||
@ -295,6 +81,7 @@ Interested in contributing to Zeppelin?
|
||||
- [DemocracyEarth](http://democracy.earth/)
|
||||
- [Signatura](https://signatura.co/)
|
||||
- [Ether.camp](http://www.ether.camp/)
|
||||
- [Aragon](https://aragon.one/)
|
||||
|
||||
among others...
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import './PullPayment.sol';
|
||||
import './Killable.sol';
|
||||
import './payment/PullPayment.sol';
|
||||
import './lifecycle/Killable.sol';
|
||||
|
||||
|
||||
/*
|
||||
@ -11,18 +11,19 @@ import './Killable.sol';
|
||||
* This bounty will pay out to a researcher if they break invariant logic of the contract.
|
||||
*/
|
||||
contract Bounty is PullPayment, Killable {
|
||||
Target target;
|
||||
bool public claimed;
|
||||
mapping(address => address) public researchers;
|
||||
|
||||
event TargetCreated(address createdAddress);
|
||||
|
||||
function() payable {
|
||||
if (claimed) throw;
|
||||
if (claimed) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
function createTarget() returns(Target) {
|
||||
target = Target(deployContract());
|
||||
Target target = Target(deployContract());
|
||||
researchers[target] = msg.sender;
|
||||
TargetCreated(target);
|
||||
return target;
|
||||
@ -30,13 +31,11 @@ contract Bounty is PullPayment, Killable {
|
||||
|
||||
function deployContract() internal returns(address);
|
||||
|
||||
function checkInvariant() returns(bool){
|
||||
return target.checkInvariant();
|
||||
}
|
||||
|
||||
function claim(Target target) {
|
||||
address researcher = researchers[target];
|
||||
if (researcher == 0) throw;
|
||||
if (researcher == 0) {
|
||||
throw;
|
||||
}
|
||||
// Check Target contract invariants
|
||||
if (target.checkInvariant()) {
|
||||
throw;
|
||||
@ -47,6 +46,7 @@ contract Bounty is PullPayment, Killable {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Target
|
||||
*
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import './Shareable.sol';
|
||||
import './ownership/Shareable.sol';
|
||||
|
||||
|
||||
/*
|
||||
@ -11,56 +11,38 @@ import './Shareable.sol';
|
||||
* on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method
|
||||
* uses is specified in the modifier.
|
||||
*/
|
||||
contract DayLimit is Shareable {
|
||||
// FIELDS
|
||||
contract DayLimit {
|
||||
|
||||
uint public dailyLimit;
|
||||
uint public spentToday;
|
||||
uint public lastDay;
|
||||
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple modifier for daily limit.
|
||||
modifier limitedDaily(uint _value) {
|
||||
if (underLimit(_value))
|
||||
_;
|
||||
}
|
||||
|
||||
|
||||
// CONSTRUCTOR
|
||||
// stores initial daily limit and records the present day's index.
|
||||
function DayLimit(uint _limit) {
|
||||
dailyLimit = _limit;
|
||||
lastDay = today();
|
||||
}
|
||||
|
||||
|
||||
// METHODS
|
||||
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
// sets the daily limit. doesn't alter the amount already spent today
|
||||
function _setDailyLimit(uint _newLimit) internal {
|
||||
dailyLimit = _newLimit;
|
||||
}
|
||||
|
||||
// resets the amount already spent today. needs many of the owners to confirm
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
// resets the amount already spent today.
|
||||
function _resetSpentToday() internal {
|
||||
spentToday = 0;
|
||||
}
|
||||
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyOwner returns (bool) {
|
||||
function underLimit(uint _value) internal returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > lastDay) {
|
||||
spentToday = 0;
|
||||
lastDay = today();
|
||||
}
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
// overflow protection // dailyLimit check
|
||||
if (spentToday + _value >= spentToday && spentToday + _value <= dailyLimit) {
|
||||
spentToday += _value;
|
||||
return true;
|
||||
@ -72,4 +54,13 @@ contract DayLimit is Shareable {
|
||||
function today() private constant returns (uint) {
|
||||
return now / 1 days;
|
||||
}
|
||||
|
||||
|
||||
// simple modifier for daily limit.
|
||||
modifier limitedDaily(uint _value) {
|
||||
if (!underLimit(_value)) {
|
||||
throw;
|
||||
}
|
||||
_;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,13 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/**
|
||||
* LimitBalance
|
||||
* Simple contract to limit the balance of child contract.
|
||||
* Note this doesn't prevent other contracts to send funds
|
||||
* by using selfdestruct(address);
|
||||
* See: https://github.com/ConsenSys/smart-contract-best-practices#remember-that-ether-can-be-forcibly-sent-to-an-account
|
||||
*/
|
||||
contract LimitBalance {
|
||||
|
||||
uint public limit;
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import "./Multisig.sol";
|
||||
import "./Shareable.sol";
|
||||
import "./ownership/Multisig.sol";
|
||||
import "./ownership/Shareable.sol";
|
||||
import "./DayLimit.sol";
|
||||
|
||||
|
||||
@ -13,27 +13,17 @@ import "./DayLimit.sol";
|
||||
* Wallet(w).from(anotherOwner).confirm(h);
|
||||
*/
|
||||
contract MultisigWallet is Multisig, Shareable, DayLimit {
|
||||
// TYPES
|
||||
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
|
||||
// CONSTRUCTOR
|
||||
|
||||
// just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function MultisigWallet(address[] _owners, uint _required, uint _daylimit)
|
||||
Shareable(_owners, _required)
|
||||
function MultisigWallet(address[] _owners, uint _required, uint _daylimit)
|
||||
Shareable(_owners, _required)
|
||||
DayLimit(_daylimit) { }
|
||||
|
||||
|
||||
// METHODS
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
suicide(_to);
|
||||
@ -83,6 +73,14 @@ contract MultisigWallet is Multisig, Shareable, DayLimit {
|
||||
}
|
||||
}
|
||||
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
_setDailyLimit(_newLimit);
|
||||
}
|
||||
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
_resetSpentToday();
|
||||
}
|
||||
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/**
|
||||
@ -11,6 +11,13 @@ contract SafeMath {
|
||||
return c;
|
||||
}
|
||||
|
||||
function safeDiv(uint a, uint b) internal returns (uint) {
|
||||
assert(b > 0);
|
||||
uint c = a / b;
|
||||
assert(a == b * c + a % b);
|
||||
return c;
|
||||
}
|
||||
|
||||
function safeSub(uint a, uint b) internal returns (uint) {
|
||||
assert(b <= a);
|
||||
return a - b;
|
||||
@ -22,8 +29,25 @@ contract SafeMath {
|
||||
return c;
|
||||
}
|
||||
|
||||
function max64(uint64 a, uint64 b) internal constant returns (uint64) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
function min64(uint64 a, uint64 b) internal constant returns (uint64) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
function max256(uint256 a, uint256 b) internal constant returns (uint256) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
function min256(uint256 a, uint256 b) internal constant returns (uint256) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
function assert(bool assertion) internal {
|
||||
if (!assertion) throw;
|
||||
if (!assertion) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../PullPayment.sol';
|
||||
|
||||
// UNSAFE CODE, DO NOT USE!
|
||||
|
||||
contract BadArrayUse is PullPayment {
|
||||
address[] employees;
|
||||
|
||||
function payBonus() {
|
||||
for (var i = 0; i < employees.length; i++) {
|
||||
address employee = employees[i];
|
||||
uint bonus = calculateBonus(employee);
|
||||
asyncSend(employee, bonus);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateBonus(address employee) returns (uint) {
|
||||
// some expensive computation...
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
// UNSAFE CODE, DO NOT USE!
|
||||
|
||||
contract BadFailEarly {
|
||||
|
||||
uint constant DEFAULT_SALARY = 50000;
|
||||
mapping(string => uint) nameToSalary;
|
||||
|
||||
function getSalary(string name) constant returns (uint) {
|
||||
if (bytes(name).length != 0 && nameToSalary[name] != 0) {
|
||||
return nameToSalary[name];
|
||||
} else {
|
||||
return DEFAULT_SALARY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
// UNSAFE CODE, DO NOT USE!
|
||||
|
||||
contract BadPushPayments {
|
||||
|
||||
address highestBidder;
|
||||
uint highestBid;
|
||||
|
||||
function bid() {
|
||||
if (msg.value < highestBid) throw;
|
||||
|
||||
if (highestBidder != 0) {
|
||||
// return bid to previous winner
|
||||
if (!highestBidder.send(highestBid)) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
highestBidder = msg.sender;
|
||||
highestBid = msg.value;
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../PullPayment.sol';
|
||||
|
||||
contract GoodArrayUse is PullPayment {
|
||||
address[] employees;
|
||||
mapping(address => uint) bonuses;
|
||||
|
||||
function payBonus() {
|
||||
for (uint i = 0; i < employees.length; i++) {
|
||||
address employee = employees[i];
|
||||
uint bonus = bonuses[employee];
|
||||
asyncSend(employee, bonus);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateBonus(address employee) returns (uint) {
|
||||
uint bonus = 0;
|
||||
// some expensive computation...
|
||||
bonuses[employee] = bonus;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
|
||||
contract GoodFailEarly {
|
||||
|
||||
uint constant DEFAULT_SALARY = 50000;
|
||||
mapping(string => uint) nameToSalary;
|
||||
|
||||
function getSalary(string name) constant returns (uint) {
|
||||
if (bytes(name).length == 0) throw;
|
||||
if (nameToSalary[name] == 0) throw;
|
||||
|
||||
return nameToSalary[name];
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
contract GoodPullPayments {
|
||||
address highestBidder;
|
||||
uint highestBid;
|
||||
mapping(address => uint) refunds;
|
||||
|
||||
function bid() external {
|
||||
if (msg.value < highestBid) throw;
|
||||
|
||||
if (highestBidder != 0) {
|
||||
refunds[highestBidder] += highestBid;
|
||||
}
|
||||
|
||||
highestBidder = msg.sender;
|
||||
highestBid = msg.value;
|
||||
}
|
||||
|
||||
function withdrawBid() external {
|
||||
uint refund = refunds[msg.sender];
|
||||
refunds[msg.sender] = 0;
|
||||
if (!msg.sender.send(refund)) {
|
||||
refunds[msg.sender] = refund;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
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 {
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
|
||||
import '../PullPayment.sol';
|
||||
|
||||
contract PullPaymentBid is PullPayment {
|
||||
address public highestBidder;
|
||||
uint public highestBid;
|
||||
|
||||
function bid() external {
|
||||
if (msg.value <= highestBid) throw;
|
||||
|
||||
if (highestBidder != 0) {
|
||||
asyncSend(highestBidder, highestBid);
|
||||
}
|
||||
highestBidder = msg.sender;
|
||||
highestBid = msg.value;
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
pragma solidity ^0.4.4;
|
||||
|
||||
import '../PullPayment.sol';
|
||||
import '../Stoppable.sol';
|
||||
|
||||
contract StoppableBid is Stoppable, PullPayment {
|
||||
address public highestBidder;
|
||||
uint public highestBid;
|
||||
|
||||
function bid() external stopInEmergency {
|
||||
if (msg.value <= highestBid) throw;
|
||||
|
||||
if (highestBidder != 0) {
|
||||
asyncSend(highestBidder, highestBid);
|
||||
}
|
||||
highestBidder = msg.sender;
|
||||
highestBid = msg.value;
|
||||
}
|
||||
|
||||
function withdraw() onlyInEmergency {
|
||||
selfdestruct(owner);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import "./Ownable.sol";
|
||||
import "../ownership/Ownable.sol";
|
||||
|
||||
|
||||
/*
|
||||
@ -1,5 +1,8 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import './Ownable.sol';
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import '../ownership/Ownable.sol';
|
||||
|
||||
|
||||
contract Migrations is Ownable {
|
||||
uint public lastCompletedMigration;
|
||||
@ -1,19 +1,28 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import "./Ownable.sol";
|
||||
import "../ownership/Ownable.sol";
|
||||
|
||||
|
||||
/*
|
||||
* Stoppable
|
||||
* Pausable
|
||||
* Abstract contract that allows children to implement an
|
||||
* emergency stop mechanism.
|
||||
*/
|
||||
contract Stoppable is Ownable {
|
||||
contract Pausable is Ownable {
|
||||
bool public stopped;
|
||||
|
||||
modifier stopInEmergency { if (!stopped) _; }
|
||||
modifier onlyInEmergency { if (stopped) _; }
|
||||
modifier stopInEmergency {
|
||||
if (!stopped) {
|
||||
_;
|
||||
}
|
||||
}
|
||||
|
||||
modifier onlyInEmergency {
|
||||
if (stopped) {
|
||||
_;
|
||||
}
|
||||
}
|
||||
|
||||
// called by the owner on emergency, triggers stopped state
|
||||
function emergencyStop() external onlyOwner {
|
||||
@ -1,24 +1,25 @@
|
||||
pragma solidity ^0.4.0;
|
||||
|
||||
|
||||
|
||||
import './Ownable.sol';
|
||||
|
||||
|
||||
/*
|
||||
* Claimable
|
||||
*
|
||||
* Extension for the Ownable contract, where the ownership needs to be claimed. This allows the new owner to accept the transfer.
|
||||
*
|
||||
* Extension for the Ownable contract, where the ownership needs to be claimed. This allows the new owner to accept the transfer.
|
||||
*/
|
||||
contract Claimable is Ownable {
|
||||
address public pendingOwner;
|
||||
|
||||
modifier onlyPendingOwner() {
|
||||
if (msg.sender == pendingOwner)
|
||||
_;
|
||||
if (msg.sender != pendingOwner) {
|
||||
throw;
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
function transfer(address newOwner) onlyOwner {
|
||||
function transferOwnership(address newOwner) onlyOwner {
|
||||
pendingOwner = newOwner;
|
||||
}
|
||||
|
||||
16
contracts/ownership/Contactable.sol
Normal file
16
contracts/ownership/Contactable.sol
Normal file
@ -0,0 +1,16 @@
|
||||
pragma solidity ^0.4.0;
|
||||
|
||||
import './Ownable.sol';
|
||||
/*
|
||||
* Contactable token
|
||||
* Basic version of a contactable contract
|
||||
*/
|
||||
contract Contactable is Ownable{
|
||||
|
||||
string public contactInformation;
|
||||
|
||||
function setContactInformation(string info) onlyOwner{
|
||||
contactInformation = info;
|
||||
}
|
||||
|
||||
}
|
||||
32
contracts/ownership/DelayedClaimable.sol
Normal file
32
contracts/ownership/DelayedClaimable.sol
Normal file
@ -0,0 +1,32 @@
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import './Ownable.sol';
|
||||
import './Claimable.sol';
|
||||
|
||||
|
||||
/*
|
||||
* DelayedClaimable
|
||||
* Extension for the Claimable contract, where the ownership needs to be claimed before/after certain block number
|
||||
*/
|
||||
contract DelayedClaimable is Ownable, Claimable {
|
||||
|
||||
uint public end;
|
||||
uint public start;
|
||||
|
||||
function setLimits(uint _start, uint _end) onlyOwner {
|
||||
if (_start > _end)
|
||||
throw;
|
||||
end = _end;
|
||||
start = _start;
|
||||
}
|
||||
|
||||
function claimOwnership() onlyPendingOwner {
|
||||
if ((block.number > end) || (block.number < start))
|
||||
throw;
|
||||
owner = pendingOwner;
|
||||
pendingOwner = 0x0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/*
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/*
|
||||
@ -14,13 +14,17 @@ contract Ownable {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
modifier onlyOwner() {
|
||||
if (msg.sender == owner)
|
||||
_;
|
||||
modifier onlyOwner() {
|
||||
if (msg.sender != owner) {
|
||||
throw;
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
function transfer(address newOwner) onlyOwner {
|
||||
if (newOwner != address(0)) owner = newOwner;
|
||||
function transferOwnership(address newOwner) onlyOwner {
|
||||
if (newOwner != address(0)) {
|
||||
owner = newOwner;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/*
|
||||
* Shareable
|
||||
*
|
||||
* Based on https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol
|
||||
* Based on https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol
|
||||
*
|
||||
* inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a single, or, crucially, each of a number of, designated owners.
|
||||
*
|
||||
@ -12,8 +12,6 @@ pragma solidity ^0.4.4;
|
||||
* use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by some number (specified in constructor) of the set of owners (specified in the constructor) before the interior is executed.
|
||||
*/
|
||||
contract Shareable {
|
||||
// TYPES
|
||||
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
@ -21,15 +19,11 @@ contract Shareable {
|
||||
uint index;
|
||||
}
|
||||
|
||||
|
||||
// FIELDS
|
||||
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public required;
|
||||
|
||||
// list of owners
|
||||
uint[256] owners;
|
||||
uint constant c_maxOwners = 250;
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) ownerIndex;
|
||||
// the ongoing operations.
|
||||
@ -37,33 +31,29 @@ contract Shareable {
|
||||
bytes32[] pendingsIndex;
|
||||
|
||||
|
||||
// EVENTS
|
||||
|
||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyOwner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
if (!isOwner(msg.sender)) {
|
||||
throw;
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
if (confirmAndCheck(_operation)) {
|
||||
_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CONSTRUCTOR
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function Shareable(address[] _owners, uint _required) {
|
||||
@ -76,14 +66,13 @@ contract Shareable {
|
||||
required = _required;
|
||||
}
|
||||
|
||||
|
||||
// METHODS
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external {
|
||||
uint index = ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (index == 0) return;
|
||||
if (index == 0) {
|
||||
return;
|
||||
}
|
||||
uint ownerIndexBit = 2**index;
|
||||
var pending = pendings[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
@ -98,7 +87,7 @@ contract Shareable {
|
||||
return address(owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) returns (bool) {
|
||||
function isOwner(address _addr) constant returns (bool) {
|
||||
return ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
@ -107,20 +96,22 @@ contract Shareable {
|
||||
uint index = ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (index == 0) return false;
|
||||
if (index == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**index;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint index = ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (index == 0) return;
|
||||
if (index == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pending = pendings[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
@ -143,23 +134,22 @@ contract Shareable {
|
||||
delete pendingsIndex[pendings[_operation].index];
|
||||
delete pendings[_operation];
|
||||
return true;
|
||||
} else {
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = pendingsIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
if (pendingsIndex[i] != 0)
|
||||
delete pendings[pendingsIndex[i]];
|
||||
for (uint i = 0; i < length; ++i) {
|
||||
if (pendingsIndex[i] != 0) {
|
||||
delete pendings[pendingsIndex[i]];
|
||||
}
|
||||
}
|
||||
delete pendingsIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/*
|
||||
@ -19,10 +19,16 @@ contract PullPayment {
|
||||
address payee = msg.sender;
|
||||
uint payment = payments[payee];
|
||||
|
||||
if (payment == 0) throw;
|
||||
if (this.balance < payment) throw;
|
||||
if (payment == 0) {
|
||||
throw;
|
||||
}
|
||||
|
||||
if (this.balance < payment) {
|
||||
throw;
|
||||
}
|
||||
|
||||
payments[payee] = 0;
|
||||
|
||||
if (!payee.send(payment)) {
|
||||
throw;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import './ERC20Basic.sol';
|
||||
@ -14,9 +14,6 @@ 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);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import "./StandardToken.sol";
|
||||
@ -23,7 +23,9 @@ contract CrowdsaleToken is StandardToken {
|
||||
}
|
||||
|
||||
function createTokens(address recipient) payable {
|
||||
if (msg.value == 0) throw;
|
||||
if (msg.value == 0) {
|
||||
throw;
|
||||
}
|
||||
|
||||
uint tokens = safeMul(msg.value, getPrice());
|
||||
|
||||
@ -32,7 +34,7 @@ contract CrowdsaleToken is StandardToken {
|
||||
}
|
||||
|
||||
// replace this with any other price function
|
||||
function getPrice() constant returns (uint result){
|
||||
function getPrice() constant returns (uint result) {
|
||||
return PRICE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import "./StandardToken.sol";
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import './ERC20.sol';
|
||||
import '../SafeMath.sol';
|
||||
|
||||
|
||||
/**
|
||||
* ERC20 token
|
||||
* Standard ERC20 token
|
||||
*
|
||||
* https://github.com/ethereum/EIPs/issues/20
|
||||
* Based on code by FirstBlood:
|
||||
@ -24,7 +26,10 @@ contract StandardToken is ERC20, SafeMath {
|
||||
|
||||
function transferFrom(address _from, address _to, uint _value) returns (bool success) {
|
||||
var _allowance = allowed[_from][msg.sender];
|
||||
|
||||
|
||||
// Check is not needed because safeSub(_allowance, _value) will already throw if this condition is not met
|
||||
// if (_value > _allowance) throw;
|
||||
|
||||
balances[_to] = safeAdd(balances[_to], _value);
|
||||
balances[_from] = safeSub(balances[_from], _value);
|
||||
allowed[_from][msg.sender] = safeSub(_allowance, _value);
|
||||
|
||||
143
contracts/token/VestedToken.sol
Normal file
143
contracts/token/VestedToken.sol
Normal file
@ -0,0 +1,143 @@
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import "./StandardToken.sol";
|
||||
|
||||
|
||||
contract VestedToken is StandardToken {
|
||||
struct TokenGrant {
|
||||
address granter;
|
||||
uint256 value;
|
||||
uint64 cliff;
|
||||
uint64 vesting;
|
||||
uint64 start;
|
||||
}
|
||||
|
||||
mapping (address => TokenGrant[]) public grants;
|
||||
|
||||
modifier canTransfer(address _sender, uint _value) {
|
||||
if (_value > transferableTokens(_sender, uint64(now))) throw;
|
||||
_;
|
||||
}
|
||||
|
||||
function transfer(address _to, uint _value) canTransfer(msg.sender, _value) returns (bool success) {
|
||||
return super.transfer(_to, _value);
|
||||
}
|
||||
|
||||
function transferFrom(address _from, address _to, uint _value) canTransfer(_from, _value) returns (bool success) {
|
||||
return super.transferFrom(_from, _to, _value);
|
||||
}
|
||||
|
||||
function grantVestedTokens(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint64 _start,
|
||||
uint64 _cliff,
|
||||
uint64 _vesting) {
|
||||
|
||||
if (_cliff < _start) {
|
||||
throw;
|
||||
}
|
||||
if (_vesting < _start) {
|
||||
throw;
|
||||
}
|
||||
if (_vesting < _cliff) {
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
TokenGrant memory grant = TokenGrant(msg.sender, _value, _cliff, _vesting, _start);
|
||||
grants[_to].push(grant);
|
||||
|
||||
transfer(_to, _value);
|
||||
}
|
||||
|
||||
function revokeTokenGrant(address _holder, uint _grantId) {
|
||||
TokenGrant grant = grants[_holder][_grantId];
|
||||
|
||||
if (grant.granter != msg.sender) {
|
||||
throw;
|
||||
}
|
||||
uint256 nonVested = nonVestedTokens(grant, uint64(now));
|
||||
|
||||
// remove grant from array
|
||||
delete grants[_holder][_grantId];
|
||||
grants[_holder][_grantId] = grants[_holder][grants[_holder].length - 1];
|
||||
grants[_holder].length -= 1;
|
||||
|
||||
balances[msg.sender] = safeAdd(balances[msg.sender], nonVested);
|
||||
balances[_holder] = safeSub(balances[_holder], nonVested);
|
||||
Transfer(_holder, msg.sender, nonVested);
|
||||
}
|
||||
|
||||
function tokenGrantsCount(address _holder) constant returns (uint index) {
|
||||
return grants[_holder].length;
|
||||
}
|
||||
|
||||
function tokenGrant(address _holder, uint _grantId) constant returns (address granter, uint256 value, uint256 vested, uint64 start, uint64 cliff, uint64 vesting) {
|
||||
TokenGrant grant = grants[_holder][_grantId];
|
||||
|
||||
granter = grant.granter;
|
||||
value = grant.value;
|
||||
start = grant.start;
|
||||
cliff = grant.cliff;
|
||||
vesting = grant.vesting;
|
||||
|
||||
vested = vestedTokens(grant, uint64(now));
|
||||
}
|
||||
|
||||
function vestedTokens(TokenGrant grant, uint64 time) private constant returns (uint256) {
|
||||
return calculateVestedTokens(
|
||||
grant.value,
|
||||
uint256(time),
|
||||
uint256(grant.start),
|
||||
uint256(grant.cliff),
|
||||
uint256(grant.vesting)
|
||||
);
|
||||
}
|
||||
|
||||
function calculateVestedTokens(
|
||||
uint256 tokens,
|
||||
uint256 time,
|
||||
uint256 start,
|
||||
uint256 cliff,
|
||||
uint256 vesting) constant returns (uint256 vestedTokens)
|
||||
{
|
||||
|
||||
if (time < cliff) {
|
||||
return 0;
|
||||
}
|
||||
if (time > vesting) {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
uint256 cliffTokens = safeDiv(safeMul(tokens, safeSub(cliff, start)), safeSub(vesting, start));
|
||||
vestedTokens = cliffTokens;
|
||||
|
||||
uint256 vestingTokens = safeSub(tokens, cliffTokens);
|
||||
|
||||
vestedTokens = safeAdd(vestedTokens, safeDiv(safeMul(vestingTokens, safeSub(time, cliff)), safeSub(vesting, start)));
|
||||
}
|
||||
|
||||
function nonVestedTokens(TokenGrant grant, uint64 time) private constant returns (uint256) {
|
||||
return safeSub(grant.value, vestedTokens(grant, time));
|
||||
}
|
||||
|
||||
function lastTokenIsTransferableDate(address holder) constant public returns (uint64 date) {
|
||||
date = uint64(now);
|
||||
uint256 grantIndex = grants[holder].length;
|
||||
for (uint256 i = 0; i < grantIndex; i++) {
|
||||
date = max64(grants[holder][i].vesting, date);
|
||||
}
|
||||
}
|
||||
|
||||
function transferableTokens(address holder, uint64 time) constant public returns (uint256 nonVested) {
|
||||
uint256 grantIndex = grants[holder].length;
|
||||
|
||||
for (uint256 i = 0; i < grantIndex; i++) {
|
||||
nonVested = safeAdd(nonVested, nonVestedTokens(grants[holder][i], time));
|
||||
}
|
||||
|
||||
return safeSub(balances[holder], nonVested);
|
||||
}
|
||||
}
|
||||
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = zeppelin-solidity
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
12
docs/source/basictoken.rst
Normal file
12
docs/source/basictoken.rst
Normal file
@ -0,0 +1,12 @@
|
||||
BasicToken
|
||||
=============================================
|
||||
|
||||
Simpler version of StandardToken, with no allowances
|
||||
|
||||
balanceOf(address _owner) constant returns (uint balance)
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Returns the token balance of the passed address.
|
||||
|
||||
function transfer(address _to, uint _value) returns (bool success)
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Transfers tokens from sender's account. Amount must not be greater than sender's balance.
|
||||
60
docs/source/bounty.rst
Normal file
60
docs/source/bounty.rst
Normal file
@ -0,0 +1,60 @@
|
||||
Bounty
|
||||
=============================================
|
||||
|
||||
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 {Bounty, Target} from "./zeppelin/Bounty.sol";
|
||||
import "./YourContract.sol";
|
||||
|
||||
contract YourBounty is Bounty {
|
||||
function deployContract() internal returns(address) {
|
||||
return new YourContract()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Next, implement invariant logic into your smart contract.
|
||||
Your main contract should inherit from the Target class and implement the checkInvariant method. This is a function that should check everything your contract assumes to be true all the time. If this function returns false, it means your contract was broken in some way and is in an inconsistent state. This is what security researchers will try to acomplish when trying to get the bounty.
|
||||
|
||||
At contracts/YourContract.sol::
|
||||
|
||||
|
||||
import {Bounty, Target} from "./zeppelin/Bounty.sol";
|
||||
contract YourContract is Target {
|
||||
function checkInvariant() returns(bool) {
|
||||
// Implement your logic to make sure that none of the invariants are broken.
|
||||
}
|
||||
}
|
||||
|
||||
Next, deploy your bounty contract along with your main contract to the network.
|
||||
|
||||
At ```migrations/2_deploy_contracts.js```::
|
||||
|
||||
module.exports = function(deployer) {
|
||||
deployer.deploy(YourContract);
|
||||
deployer.deploy(YourBounty);
|
||||
};
|
||||
|
||||
Next, add a reward to the bounty contract
|
||||
|
||||
After deploying the contract, send reward funds into the bounty contract.
|
||||
|
||||
From ```truffle console```::
|
||||
|
||||
bounty = YourBounty.deployed();
|
||||
address = 0xb9f68f96cde3b895cc9f6b14b856081b41cb96f1; // your account address
|
||||
reward = 5; // reward to pay to a researcher who breaks your contract
|
||||
|
||||
web3.eth.sendTransaction({
|
||||
from: address,
|
||||
to: bounty.address,
|
||||
value: web3.toWei(reward, "ether")
|
||||
})
|
||||
|
||||
If researchers break the contract, they can claim their reward.
|
||||
|
||||
For each researcher who wants to hack the contract and claims the reward, refer to our `Test <https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/Bounty.js/>`_ for the detail.
|
||||
|
||||
Finally, if you manage to protect your contract from security researchers, you can reclaim the bounty funds. To end the bounty, kill the contract so that all the rewards go back to the owner.::
|
||||
|
||||
bounty.kill();
|
||||
20
docs/source/claimable.rst
Normal file
20
docs/source/claimable.rst
Normal file
@ -0,0 +1,20 @@
|
||||
Claimable
|
||||
=============================================
|
||||
|
||||
Extension for the Ownable contract, where the ownership needs to be claimed
|
||||
|
||||
transfer(address newOwner) onlyOwner
|
||||
""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Sets the passed address as the pending owner.
|
||||
|
||||
modifier onlyPendingOwner
|
||||
""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Function only runs if called by pending owner.
|
||||
|
||||
claimOwnership( ) onlyPendingOwner
|
||||
""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Completes transfer of ownership by setting pending owner as the new owner.
|
||||
|
||||
160
docs/source/conf.py
Normal file
160
docs/source/conf.py
Normal file
@ -0,0 +1,160 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# zeppelin-solidity documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Dec 13 11:35:05 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'zeppelin-solidity'
|
||||
copyright = u'2016, Smart Contract Solutions, Inc'
|
||||
author = u'Smart Contract Solutions, Inc'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'1.0.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'1.0.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = []
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
import sphinx_rtd_theme
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'zeppelin-soliditydoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'zeppelin-solidity.tex', u'zeppelin-solidity Documentation',
|
||||
u'Zeppelin', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'zeppelin-solidity', u'zeppelin-solidity Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'zeppelin-solidity', u'zeppelin-solidity Documentation',
|
||||
author, 'zeppelin-solidity', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
4
docs/source/contract-security-patterns.rst
Normal file
4
docs/source/contract-security-patterns.rst
Normal file
@ -0,0 +1,4 @@
|
||||
Common Contract Security Patterns
|
||||
=============================================
|
||||
|
||||
Zeppelin smart contracts are developed using industry standard contract security patterns and best practices. To learn more, please see `Onward with Ethereum Smart Contract Security <https://medium.com/zeppelin-blog/onward-with-ethereum-smart-contract-security-97a827e47702#.ybvzeyz0k/>`_.
|
||||
14
docs/source/crowdsaletoken.rst
Normal file
14
docs/source/crowdsaletoken.rst
Normal file
@ -0,0 +1,14 @@
|
||||
CrowdsaleToken
|
||||
=============================================
|
||||
|
||||
Simple ERC20 Token example, with crowdsale token creation.
|
||||
|
||||
Inherits from contract StandardToken.
|
||||
|
||||
createTokens(address recipient) payable
|
||||
"""""""""""""""""""""""""""""""""""""""""
|
||||
Creates tokens based on message value and credits to the recipient.
|
||||
|
||||
getPrice() constant returns (uint result)
|
||||
"""""""""""""""""""""""""""""""""""""""""
|
||||
Returns the amount of tokens per 1 ether.
|
||||
12
docs/source/developer-resources.rst
Normal file
12
docs/source/developer-resources.rst
Normal file
@ -0,0 +1,12 @@
|
||||
Developer Resources
|
||||
=============================================
|
||||
|
||||
Building a distributed application, protocol or organization with Zeppelin?
|
||||
|
||||
Ask for help and follow progress at: https://zeppelin-slackin.herokuapp.com/
|
||||
|
||||
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/zeppelin-solidity/issues
|
||||
* Contribution guidelines: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/CONTRIBUTING.md
|
||||
36
docs/source/getting-started.rst
Normal file
36
docs/source/getting-started.rst
Normal file
@ -0,0 +1,36 @@
|
||||
Getting Started
|
||||
=============================================
|
||||
|
||||
Zeppelin integrates with `Truffle <https://github.com/ConsenSys/truffle/>`_, an Ethereum development environment. Please install Truffle and initialize your project with ``truffle init``::
|
||||
|
||||
npm install -g truffle
|
||||
mkdir myproject && cd myproject
|
||||
truffle init
|
||||
|
||||
To install the Zeppelin library, run::
|
||||
|
||||
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::
|
||||
|
||||
import "./zeppelin/Ownable.sol";
|
||||
|
||||
contract MyContract is Ownable {
|
||||
...
|
||||
}
|
||||
|
||||
.. epigraph::
|
||||
|
||||
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::
|
||||
|
||||
import "zeppelin-solidity/contracts/Ownable.sol";
|
||||
|
||||
contract MyContract is Ownable {
|
||||
...
|
||||
}
|
||||
|
||||
For more info see the `Truffle Beta package management tutorial <http://truffleframework.com/tutorials/package-management/>`_.
|
||||
47
docs/source/index.rst
Normal file
47
docs/source/index.rst
Normal file
@ -0,0 +1,47 @@
|
||||
.. zeppelin-solidity documentation master file, created by
|
||||
sphinx-quickstart on Tue Dec 13 11:35:05 2016.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to Zeppelin-Solidity
|
||||
=============================================
|
||||
|
||||
Zeppelin is a library for writing secure Smart Contracts on Ethereum.
|
||||
|
||||
With Zeppelin, you can build distributed applications, protocols and organizations:
|
||||
|
||||
* using :doc:`contract-security-patterns`
|
||||
* in the `Solidity language <https://solidity.readthedocs.io/en/develop/>`_.
|
||||
|
||||
The code is open-source, and `available on github <https://github.com/OpenZeppelin/zeppelin-solidity>`_.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
getting-started
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Smart Contracts
|
||||
|
||||
ownable
|
||||
Pausable
|
||||
killable
|
||||
claimable
|
||||
migrations
|
||||
safemath
|
||||
limitbalance
|
||||
pullpayment
|
||||
standardtoken
|
||||
basictoken
|
||||
crowdsaletoken
|
||||
bounty
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Developer Resources
|
||||
|
||||
contract-security-patterns
|
||||
developer-resources
|
||||
license
|
||||
11
docs/source/killable.rst
Normal file
11
docs/source/killable.rst
Normal file
@ -0,0 +1,11 @@
|
||||
Killable
|
||||
=============================================
|
||||
|
||||
Base contract that can be killed by owner.
|
||||
|
||||
Inherits from contract Ownable.
|
||||
|
||||
kill( ) onlyOwner
|
||||
"""""""""""""""""""
|
||||
|
||||
Destroys the contract and sends funds back to the owner.
|
||||
23
docs/source/license.rst
Normal file
23
docs/source/license.rst
Normal file
@ -0,0 +1,23 @@
|
||||
The MIT License (MIT)
|
||||
=============================================
|
||||
|
||||
Copyright (c) 2016 Smart Contract Solutions, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
12
docs/source/limitbalance.rst
Normal file
12
docs/source/limitbalance.rst
Normal file
@ -0,0 +1,12 @@
|
||||
LimitBalance
|
||||
=============================================
|
||||
|
||||
Base contract that provides mechanism for limiting the amount of funds a contract can hold.
|
||||
|
||||
LimitBalance(unit _limit)
|
||||
""""""""""""""""""""""""""""
|
||||
Constructor takes an unisgned integer and sets it as the limit of funds this contract can hold.
|
||||
|
||||
modifier limitedPayable()
|
||||
""""""""""""""""""""""""""""
|
||||
Throws an error if this contract's balance is already above the limit.
|
||||
16
docs/source/migrations.rst
Normal file
16
docs/source/migrations.rst
Normal file
@ -0,0 +1,16 @@
|
||||
Migrations
|
||||
=============================================
|
||||
|
||||
Base contract that allows for a new instance of itself to be created at a different address.
|
||||
|
||||
Inherits from contract Ownable.
|
||||
|
||||
upgrade(address new_address) onlyOwner
|
||||
""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Creates a new instance of the contract at the passed address.
|
||||
|
||||
setCompleted(uint completed) onlyOwner**
|
||||
""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Sets the last time that a migration was completed.
|
||||
16
docs/source/ownable.rst
Normal file
16
docs/source/ownable.rst
Normal file
@ -0,0 +1,16 @@
|
||||
Ownable
|
||||
=============================================
|
||||
|
||||
Base contract with an owner.
|
||||
|
||||
Ownable( )
|
||||
""""""""""""""""""""""""""""""""""""""
|
||||
Sets the address of the creator of the contract as the owner.
|
||||
|
||||
modifier onlyOwner( )
|
||||
""""""""""""""""""""""""""""""""""""""
|
||||
Prevents function from running if it is called by anyone other than the owner.
|
||||
|
||||
transfer(address newOwner) onlyOwner
|
||||
""""""""""""""""""""""""""""""""""""""
|
||||
Transfers ownership of the contract to the passed address.
|
||||
26
docs/source/pausable.rst
Normal file
26
docs/source/pausable.rst
Normal file
@ -0,0 +1,26 @@
|
||||
Pausable
|
||||
=============================================
|
||||
|
||||
Base contract that provides an emergency stop mechanism.
|
||||
|
||||
Inherits from contract Ownable.
|
||||
|
||||
emergencyStop( ) external onlyOwner
|
||||
"""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Triggers the stop mechanism on the contract. After this function is called (by the owner of the contract), any function with modifier stopInEmergency will not run.
|
||||
|
||||
modifier stopInEmergency
|
||||
"""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Prevents function from running if stop mechanism is activated.
|
||||
|
||||
modifier onlyInEmergency
|
||||
"""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Only runs if stop mechanism is activated.
|
||||
|
||||
release( ) external onlyOwner onlyInEmergency
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Deactivates the stop mechanism.
|
||||
12
docs/source/pullpayment.rst
Normal file
12
docs/source/pullpayment.rst
Normal file
@ -0,0 +1,12 @@
|
||||
PullPayment
|
||||
=============================================
|
||||
|
||||
Base contract supporting async send for pull payments. Inherit from this contract and use asyncSend instead of send.
|
||||
|
||||
asyncSend(address dest, uint amount) internal
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Adds sent amount to available balance that payee can pull from this contract, called by payer.
|
||||
|
||||
withdrawPayments( )
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Sends designated balance to payee calling the contract. Throws error if designated balance is 0, if contract does not hold enough funds ot pay the payee, or if the send transaction is not successful.
|
||||
24
docs/source/safemath.rst
Normal file
24
docs/source/safemath.rst
Normal file
@ -0,0 +1,24 @@
|
||||
SafeMath
|
||||
=============================================
|
||||
|
||||
Provides functions of mathematical operations with safety checks.
|
||||
|
||||
assert(bool assertion) internal
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Throws an error if the passed result is false. Used in this contract by checking mathematical expressions.
|
||||
|
||||
safeMul(uint a, uint b) internal returns (uint)
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Multiplies two unisgned integers. Asserts that dividing the product by the non-zero multiplicand results in the multiplier.
|
||||
|
||||
safeSub(uint a, uint b) internal returns (uint)
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Checks that b is not greater than a before subtracting.
|
||||
|
||||
safeAdd(uint a, uint b) internal returns (uint)
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Checks that the result is greater than both a and b.
|
||||
26
docs/source/standardtoken.rst
Normal file
26
docs/source/standardtoken.rst
Normal file
@ -0,0 +1,26 @@
|
||||
StandardToken
|
||||
=============================================
|
||||
|
||||
Based on code by FirstBlood: `Link FirstBloodToken.sol <https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol/>`_
|
||||
|
||||
Inherits from contract SafeMath. Implementation of abstract contract ERC20 (see https://github.com/ethereum/EIPs/issues/20)
|
||||
|
||||
approve(address _spender, uint _value) returns (bool success)
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Sets the amount of the sender's token balance that the passed address is approved to use.
|
||||
|
||||
allowance(address _owner, address _spender) constant returns (uint remaining)
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Returns the approved amount of the owner's balance that the spender can use.
|
||||
|
||||
balanceOf(address _owner) constant returns (uint balance)
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Returns the token balance of the passed address.
|
||||
|
||||
transferFrom(address _from, address _to, uint _value) returns (bool success)
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Transfers tokens from an account that the sender is approved to transfer from. Amount must not be greater than the approved amount or the account's balance.
|
||||
|
||||
function transfer(address _to, uint _value) returns (bool success)
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
Transfers tokens from sender's account. Amount must not be greater than sender's balance.
|
||||
17
ethpm.json
Normal file
17
ethpm.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"package_name": "zeppelin",
|
||||
"version": "1.0.4",
|
||||
"description": "Secure Smart Contract library for Solidity",
|
||||
"authors": [
|
||||
"Manuel Araoz <manuelaraoz@gmail.com>"
|
||||
],
|
||||
"keywords": [
|
||||
"solidity",
|
||||
"ethereum",
|
||||
"smart",
|
||||
"contracts",
|
||||
"security",
|
||||
"zeppelin"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
var Migrations = artifacts.require("./Migrations.sol");
|
||||
|
||||
module.exports = function(deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
//var Ownable = artifacts.require("ownership/Ownable.sol");
|
||||
|
||||
module.exports = function(deployer) {
|
||||
deployer.deploy(PullPaymentBid);
|
||||
deployer.deploy(BadArrayUse);
|
||||
deployer.deploy(ProofOfExistence);
|
||||
deployer.deploy(Ownable);
|
||||
deployer.deploy(Claimable);
|
||||
deployer.deploy(LimitBalance);
|
||||
if(deployer.network == 'test'){
|
||||
deployer.deploy(SecureTargetBounty);
|
||||
deployer.deploy(InsecureTargetBounty);
|
||||
};
|
||||
//deployer.deploy(Ownable);
|
||||
};
|
||||
|
||||
22
package.json
22
package.json
@ -1,14 +1,11 @@
|
||||
{
|
||||
"name": "zeppelin-solidity",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.4",
|
||||
"description": "Secure Smart Contract library for Solidity",
|
||||
"main": "truffle.js",
|
||||
"devDependencies": {
|
||||
"ethereumjs-testrpc": "^3.0.2",
|
||||
"truffle": "^2.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "truffle test",
|
||||
"test": "scripts/test.sh",
|
||||
"console": "truffle console",
|
||||
"install": "scripts/install.sh"
|
||||
},
|
||||
"repository": {
|
||||
@ -28,5 +25,16 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/OpenZeppelin/zeppelin-solidity/issues"
|
||||
},
|
||||
"homepage": "https://github.com/OpenZeppelin/zeppelin-solidity"
|
||||
"homepage": "https://github.com/OpenZeppelin/zeppelin-solidity",
|
||||
"dependencies": {
|
||||
"truffle-hdwallet-provider": "0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-stage-2": "^6.18.0",
|
||||
"babel-preset-stage-3": "^6.17.0",
|
||||
"babel-register": "^6.23.0",
|
||||
"ethereumjs-testrpc": "^3.0.2",
|
||||
"truffle": "https://github.com/ConsenSys/truffle.git#3.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
13
scripts/test.sh
Executable file
13
scripts/test.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#! /bin/bash
|
||||
|
||||
output=$(nc -z localhost 8545; echo $?)
|
||||
[ $output -eq "0" ] && trpc_running=true
|
||||
if [ ! $trpc_running ]; then
|
||||
echo "Starting our own testrpc node instance"
|
||||
testrpc > /dev/null &
|
||||
trpc_pid=$!
|
||||
fi
|
||||
./node_modules/truffle/cli.js test
|
||||
if [ ! $trpc_running ]; then
|
||||
kill -9 $trpc_pid
|
||||
fi
|
||||
@ -1,49 +1,35 @@
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
|
||||
var BasicTokenMock = artifacts.require("./helpers/BasicTokenMock.sol");
|
||||
|
||||
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 the correct totalSupply after construction", async function() {
|
||||
let token = await BasicTokenMock.new(accounts[0], 100);
|
||||
let totalSupply = await token.totalSupply();
|
||||
|
||||
assert.equal(totalSupply, 100);
|
||||
})
|
||||
|
||||
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 return correct balances after transfer", async function(){
|
||||
let token = await BasicTokenMock.new(accounts[0], 100);
|
||||
let transfer = await token.transfer(accounts[1], 100);
|
||||
|
||||
let firstAccountBalance = await token.balanceOf(accounts[0]);
|
||||
assert.equal(firstAccountBalance, 0);
|
||||
|
||||
let secondAccountBalance = await token.balanceOf(accounts[1]);
|
||||
assert.equal(secondAccountBalance, 100);
|
||||
});
|
||||
|
||||
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);
|
||||
it("should throw an error when trying to transfer more than balance", async function() {
|
||||
let token = await BasicTokenMock.new(accounts[0], 100);
|
||||
try {
|
||||
let transfer = await token.transfer(accounts[1], 101);
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
217
test/Bounty.js
217
test/Bounty.js
@ -1,141 +1,114 @@
|
||||
var sendReward = function(sender, receiver, value){
|
||||
'use strict';
|
||||
|
||||
let sendReward = function(sender, receiver, value){
|
||||
web3.eth.sendTransaction({
|
||||
from:sender,
|
||||
to:receiver,
|
||||
value: value
|
||||
})
|
||||
});
|
||||
};
|
||||
var SecureTargetBounty = artifacts.require('helpers/SecureTargetBounty.sol');
|
||||
var InsecureTargetBounty = artifacts.require('helpers/InsecureTargetBounty.sol');
|
||||
|
||||
function awaitEvent(event, handler) {
|
||||
return new Promise((resolve, reject) => {
|
||||
function wrappedHandler(...args) {
|
||||
Promise.resolve(handler(...args)).then(resolve).catch(reject);
|
||||
}
|
||||
|
||||
event.watch(wrappedHandler);
|
||||
});
|
||||
}
|
||||
|
||||
contract('Bounty', function(accounts) {
|
||||
|
||||
it("sets reward", function(done){
|
||||
var owner = accounts[0];
|
||||
var reward = web3.toWei(1, "ether");
|
||||
it('sets reward', async function() {
|
||||
let owner = accounts[0];
|
||||
let reward = web3.toWei(1, 'ether');
|
||||
let bounty = await SecureTargetBounty.new();
|
||||
sendReward(owner, bounty.address, reward);
|
||||
|
||||
SecureTargetBounty.new().
|
||||
then(function(bounty){
|
||||
assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber());
|
||||
});
|
||||
|
||||
it('empties itself when killed', async function(){
|
||||
let owner = accounts[0];
|
||||
let reward = web3.toWei(1, 'ether');
|
||||
let bounty = await SecureTargetBounty.new();
|
||||
sendReward(owner, bounty.address, reward);
|
||||
|
||||
assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber());
|
||||
|
||||
await bounty.kill();
|
||||
assert.equal(0, web3.eth.getBalance(bounty.address).toNumber());
|
||||
});
|
||||
|
||||
describe('Against secure contract', function(){
|
||||
|
||||
it('cannot claim reward', async function(){
|
||||
let owner = accounts[0];
|
||||
let researcher = accounts[1];
|
||||
let reward = web3.toWei(1, 'ether');
|
||||
let bounty = await SecureTargetBounty.new();
|
||||
let event = bounty.TargetCreated({});
|
||||
|
||||
event.watch(async 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())
|
||||
}).
|
||||
then(done);
|
||||
})
|
||||
|
||||
it("empties itself when killed", function(done){
|
||||
var owner = accounts[0];
|
||||
var reward = web3.toWei(1, "ether");
|
||||
var bounty;
|
||||
assert.equal(reward,
|
||||
web3.eth.getBalance(bounty.address).toNumber());
|
||||
|
||||
SecureTargetBounty.new().
|
||||
then(function(_bounty){
|
||||
bounty = _bounty;
|
||||
try {
|
||||
await bounty.claim(targetAddress, {from:researcher});
|
||||
assert.isTrue(false); // should never reach here
|
||||
} catch(error) {
|
||||
let reClaimedBounty = await bounty.claimed.call();
|
||||
assert.isFalse(reClaimedBounty);
|
||||
|
||||
}
|
||||
try {
|
||||
await bounty.withdrawPayments({from:researcher});
|
||||
assert.isTrue(false); // should never reach here
|
||||
} catch (err) {
|
||||
assert.equal(reward,
|
||||
web3.eth.getBalance(bounty.address).toNumber());
|
||||
}
|
||||
});
|
||||
bounty.createTarget({from:researcher});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Against broken contract', function(){
|
||||
it('claims reward', async function() {
|
||||
let owner = accounts[0];
|
||||
let researcher = accounts[1];
|
||||
let reward = web3.toWei(1, 'ether');
|
||||
let bounty = await InsecureTargetBounty.new();
|
||||
let event = bounty.TargetCreated({});
|
||||
|
||||
let watcher = async function(err, result) {
|
||||
event.stopWatching();
|
||||
if (err) { throw err; }
|
||||
let targetAddress = result.args.createdAddress;
|
||||
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;
|
||||
assert.equal(reward, web3.eth.getBalance(bounty.address).toNumber());
|
||||
|
||||
SecureTargetBounty.new().
|
||||
then(function(_bounty) {
|
||||
bounty = _bounty;
|
||||
return bounty.createTarget();
|
||||
}).
|
||||
then(function() {
|
||||
return bounty.checkInvariant.call()
|
||||
}).
|
||||
then(function(result) {
|
||||
assert.isTrue(result);
|
||||
}).
|
||||
then(done);
|
||||
})
|
||||
await bounty.claim(targetAddress, {from:researcher});
|
||||
let claim = await bounty.claimed.call();
|
||||
|
||||
it("cannot claim reward", function(done){
|
||||
var owner = accounts[0];
|
||||
var researcher = accounts[1];
|
||||
var reward = web3.toWei(1, "ether");
|
||||
assert.isTrue(claim);
|
||||
|
||||
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});
|
||||
})
|
||||
})
|
||||
})
|
||||
await bounty.withdrawPayments({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});
|
||||
})
|
||||
})
|
||||
})
|
||||
assert.equal(0, web3.eth.getBalance(bounty.address).toNumber());
|
||||
};
|
||||
bounty.createTarget({from:researcher});
|
||||
await awaitEvent(event, watcher);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,71 +1,60 @@
|
||||
'use strict';
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
|
||||
var Claimable = artifacts.require('../contracts/ownership/Claimable.sol');
|
||||
|
||||
contract('Claimable', function(accounts) {
|
||||
var claimable;
|
||||
let claimable;
|
||||
|
||||
beforeEach(function() {
|
||||
return Claimable.new().then(function(deployed) {
|
||||
claimable = deployed;
|
||||
});
|
||||
beforeEach(async function() {
|
||||
claimable = await Claimable.new();
|
||||
});
|
||||
|
||||
it("should have an owner", function(done) {
|
||||
return claimable.owner()
|
||||
.then(function(owner) {
|
||||
assert.isTrue(owner != 0);
|
||||
})
|
||||
.then(done)
|
||||
it('should have an owner', async function() {
|
||||
let owner = await claimable.owner();
|
||||
assert.isTrue(owner !== 0);
|
||||
});
|
||||
|
||||
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('changes pendingOwner after transfer', async function() {
|
||||
let newOwner = accounts[1];
|
||||
await claimable.transferOwnership(newOwner);
|
||||
let pendingOwner = await claimable.pendingOwner();
|
||||
|
||||
assert.isTrue(pendingOwner === newOwner);
|
||||
});
|
||||
|
||||
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 to claimOwnership from no pendingOwner', async function() {
|
||||
try {
|
||||
await claimable.claimOwnership({from: accounts[2]});
|
||||
} catch(error) {
|
||||
assertJump(error);
|
||||
}
|
||||
});
|
||||
|
||||
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)
|
||||
it('should prevent non-owners from transfering', async function() {
|
||||
const other = accounts[2];
|
||||
const owner = await claimable.owner.call();
|
||||
assert.isTrue(owner !== other);
|
||||
try {
|
||||
await claimable.transferOwnership(other, {from: other});
|
||||
} catch(error) {
|
||||
assertJump(error);
|
||||
}
|
||||
});
|
||||
|
||||
describe("after initiating a transfer", function () {
|
||||
var newOwner;
|
||||
describe('after initiating a transfer', function () {
|
||||
let newOwner;
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(async function () {
|
||||
newOwner = accounts[1];
|
||||
return claimable.transfer(newOwner);
|
||||
await claimable.transferOwnership(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)
|
||||
it('changes allow pending owner to claim ownership', async function() {
|
||||
await claimable.claimOwnership({from: newOwner});
|
||||
let owner = await claimable.owner();
|
||||
|
||||
assert.isTrue(owner === newOwner);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
30
test/Contactable.js
Normal file
30
test/Contactable.js
Normal file
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
|
||||
var Contactable = artifacts.require('../contracts/ownership/Contactable.sol');
|
||||
|
||||
contract('Contactable', function(accounts) {
|
||||
let contactable;
|
||||
|
||||
beforeEach(async function() {
|
||||
contactable = await Contactable.new();
|
||||
});
|
||||
|
||||
it('should have an empty contact info', async function() {
|
||||
let info = await contactable.contactInformation();
|
||||
assert.isTrue(info == "");
|
||||
});
|
||||
|
||||
describe('after setting the contact information', function () {
|
||||
let contactInfo = "contact information"
|
||||
|
||||
beforeEach(async function () {
|
||||
await contactable.setContactInformation(contactInfo);
|
||||
});
|
||||
|
||||
it('should return the setted contact information', async function() {
|
||||
let info = await contactable.contactInformation();
|
||||
assert.isTrue(info === contactInfo);
|
||||
});
|
||||
});
|
||||
});
|
||||
87
test/DayLimit.js
Normal file
87
test/DayLimit.js
Normal file
@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
|
||||
var DayLimitMock = artifacts.require('helpers/DayLimitMock.sol');
|
||||
|
||||
contract('DayLimit', function(accounts) {
|
||||
|
||||
it('should construct with the passed daily limit', async function() {
|
||||
let initLimit = 10;
|
||||
let dayLimit = await DayLimitMock.new(initLimit);
|
||||
let dailyLimit = await dayLimit.dailyLimit();
|
||||
assert.equal(initLimit, dailyLimit);
|
||||
});
|
||||
|
||||
it('should be able to spend if daily limit is not reached', async function() {
|
||||
let limit = 10;
|
||||
let dayLimit = await DayLimitMock.new(limit);
|
||||
|
||||
await dayLimit.attemptSpend(8);
|
||||
let spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 8);
|
||||
|
||||
await dayLimit.attemptSpend(2);
|
||||
spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 10);
|
||||
});
|
||||
|
||||
it('should prevent spending if daily limit is reached', async function() {
|
||||
let limit = 10;
|
||||
let dayLimit = await DayLimitMock.new(limit);
|
||||
|
||||
await dayLimit.attemptSpend(8);
|
||||
let spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 8);
|
||||
|
||||
try {
|
||||
await dayLimit.attemptSpend(3);
|
||||
} catch(error) {
|
||||
assertJump(error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow spending if daily limit is reached and then set higher', async function() {
|
||||
let limit = 10;
|
||||
let dayLimit = await DayLimitMock.new(limit);
|
||||
|
||||
await dayLimit.attemptSpend(8);
|
||||
let spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 8);
|
||||
|
||||
try {
|
||||
await dayLimit.attemptSpend(3);
|
||||
} catch(error) {
|
||||
assertJump(error);
|
||||
}
|
||||
spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 8);
|
||||
|
||||
await dayLimit.setDailyLimit(15);
|
||||
await dayLimit.attemptSpend(3);
|
||||
spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 11);
|
||||
});
|
||||
|
||||
it('should allow spending if daily limit is reached and then amount spent is reset', async function() {
|
||||
let limit = 10;
|
||||
let dayLimit = await DayLimitMock.new(limit);
|
||||
|
||||
await dayLimit.attemptSpend(8);
|
||||
let spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 8);
|
||||
|
||||
try {
|
||||
await dayLimit.attemptSpend(3);
|
||||
} catch(error) {
|
||||
assertJump(error);
|
||||
}
|
||||
spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 8);
|
||||
|
||||
await dayLimit.resetSpentToday(15);
|
||||
await dayLimit.attemptSpend(3);
|
||||
spentToday = await dayLimit.spentToday();
|
||||
assert.equal(spentToday, 3);
|
||||
});
|
||||
|
||||
});
|
||||
68
test/DelayedClaimble.js
Normal file
68
test/DelayedClaimble.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
var DelayedClaimable = artifacts.require('../contracts/ownership/DelayedClaimable.sol');
|
||||
|
||||
contract('DelayedClaimable', function(accounts) {
|
||||
var delayedClaimable;
|
||||
|
||||
beforeEach(function() {
|
||||
return DelayedClaimable.new().then(function(deployed) {
|
||||
delayedClaimable = deployed;
|
||||
});
|
||||
});
|
||||
|
||||
it('can set claim blocks', async function() {
|
||||
await delayedClaimable.transferOwnership(accounts[2]);
|
||||
await delayedClaimable.setLimits(0, 1000);
|
||||
let end = await delayedClaimable.end();
|
||||
assert.equal(end, 1000);
|
||||
let start = await delayedClaimable.start();
|
||||
assert.equal(start, 0);
|
||||
});
|
||||
|
||||
it('changes pendingOwner after transfer successful', async function() {
|
||||
await delayedClaimable.transferOwnership(accounts[2]);
|
||||
await delayedClaimable.setLimits(0, 1000);
|
||||
let end = await delayedClaimable.end();
|
||||
assert.equal(end, 1000);
|
||||
let start = await delayedClaimable.start();
|
||||
assert.equal(start, 0);
|
||||
let pendingOwner = await delayedClaimable.pendingOwner();
|
||||
assert.equal(pendingOwner, accounts[2]);
|
||||
await delayedClaimable.claimOwnership({from: accounts[2]});
|
||||
let owner = await delayedClaimable.owner();
|
||||
assert.equal(owner, accounts[2]);
|
||||
});
|
||||
|
||||
it('changes pendingOwner after transfer fails', async function() {
|
||||
await delayedClaimable.transferOwnership(accounts[1]);
|
||||
await delayedClaimable.setLimits(100, 110);
|
||||
let end = await delayedClaimable.end();
|
||||
assert.equal(end, 110);
|
||||
let start = await delayedClaimable.start();
|
||||
assert.equal(start, 100);
|
||||
let pendingOwner = await delayedClaimable.pendingOwner();
|
||||
assert.equal(pendingOwner, accounts[1]);
|
||||
var err = null;
|
||||
try {
|
||||
await delayedClaimable.claimOwnership({from: accounts[1]});
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
assert.isFalse(err.message.search('invalid JUMP') === -1);
|
||||
let owner = await delayedClaimable.owner();
|
||||
assert.isTrue(owner !== accounts[1]);
|
||||
});
|
||||
|
||||
it('set end and start invalid values fail', async function() {
|
||||
await delayedClaimable.transferOwnership(accounts[1]);
|
||||
var err = null;
|
||||
try {
|
||||
await delayedClaimable.setLimits(1001, 1000);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
assert.isFalse(err.message.search('invalid JUMP') === -1);
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,69 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
var Killable = artifacts.require('../contracts/lifecycle/Killable.sol');
|
||||
require('./helpers/transactionMined.js');
|
||||
|
||||
contract('Killable', function(accounts) {
|
||||
//from https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6
|
||||
web3.eth.getTransactionReceiptMined = function (txnHash, interval) {
|
||||
var transactionReceiptAsync;
|
||||
interval = interval ? interval : 500;
|
||||
transactionReceiptAsync = function(txnHash, resolve, reject) {
|
||||
try {
|
||||
var receipt = web3.eth.getTransactionReceipt(txnHash);
|
||||
if (receipt == null) {
|
||||
setTimeout(function () {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
}, interval);
|
||||
} else {
|
||||
resolve(receipt);
|
||||
}
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(txnHash)) {
|
||||
var promises = [];
|
||||
txnHash.forEach(function (oneTxHash) {
|
||||
promises.push(web3.eth.getTransactionReceiptMined(oneTxHash, interval));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
} else {
|
||||
return new Promise(function (resolve, reject) {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
});
|
||||
}
|
||||
};
|
||||
it('should send balance to owner after death', async function() {
|
||||
let killable = await Killable.new({from: accounts[0], value: web3.toWei('10','ether')});
|
||||
let owner = await killable.owner();
|
||||
let initBalance = web3.eth.getBalance(owner);
|
||||
await killable.kill({from: owner});
|
||||
let newBalance = web3.eth.getBalance(owner);
|
||||
|
||||
it("should send balance to owner after death", function(done) {
|
||||
var initBalance, newBalance, owner, address, killable, kBalance;
|
||||
web3.eth.sendTransaction({from: web3.eth.coinbase, to: accounts[0], value: web3.toWei('50','ether')}, function(err, result) {
|
||||
if(err)
|
||||
console.log("ERROR:" + err);
|
||||
else {
|
||||
console.log(result);
|
||||
}
|
||||
})
|
||||
return Killable.new({from: accounts[0], value: web3.toWei('10','ether')})
|
||||
.then(function(_killable) {
|
||||
killable = _killable;
|
||||
return killable.owner();
|
||||
})
|
||||
.then(function(_owner) {
|
||||
owner = _owner;
|
||||
initBalance = web3.eth.getBalance(owner);
|
||||
kBalance = web3.eth.getBalance(killable.address);
|
||||
})
|
||||
.then(function() {
|
||||
return killable.kill({from: owner});
|
||||
})
|
||||
.then(function (txnHash) {
|
||||
return web3.eth.getTransactionReceiptMined(txnHash);
|
||||
})
|
||||
.then(function() {
|
||||
newBalance = web3.eth.getBalance(owner);
|
||||
})
|
||||
.then(function() {
|
||||
assert.isTrue(newBalance > initBalance);
|
||||
})
|
||||
.then(done);
|
||||
assert.isTrue(newBalance > initBalance);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -1,64 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
var LimitBalanceMock = artifacts.require('helpers/LimitBalanceMock.sol');
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
|
||||
contract('LimitBalance', function(accounts) {
|
||||
var lb;
|
||||
let lb;
|
||||
|
||||
beforeEach(function() {
|
||||
return LimitBalanceMock.new().then(function(deployed) {
|
||||
lb = deployed;
|
||||
});
|
||||
beforeEach(async function() {
|
||||
lb = await LimitBalanceMock.new();
|
||||
});
|
||||
|
||||
var LIMIT = 1000;
|
||||
let LIMIT = 1000;
|
||||
|
||||
it("should expose limit", function(done) {
|
||||
return lb.limit()
|
||||
.then(function(limit) {
|
||||
assert.equal(limit, LIMIT);
|
||||
})
|
||||
.then(done)
|
||||
it('should expose limit', async function() {
|
||||
let limit = await lb.limit();
|
||||
assert.equal(limit, LIMIT);
|
||||
});
|
||||
|
||||
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('should allow sending below limit', async function() {
|
||||
let amount = 1;
|
||||
await lb.limitedDeposit({value: amount});
|
||||
|
||||
assert.equal(web3.eth.getBalance(lb.address), amount);
|
||||
});
|
||||
|
||||
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('shouldnt allow sending above limit', async function() {
|
||||
let amount = 1110;
|
||||
try {
|
||||
await lb.limitedDeposit({value: amount});
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
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('should allow multiple sends below limit', async function() {
|
||||
let amount = 500;
|
||||
await lb.limitedDeposit({value: amount});
|
||||
|
||||
assert.equal(web3.eth.getBalance(lb.address), amount);
|
||||
|
||||
await lb.limitedDeposit({value: amount});
|
||||
assert.equal(web3.eth.getBalance(lb.address), amount*2);
|
||||
});
|
||||
|
||||
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)
|
||||
it('shouldnt allow multiple sends above limit', async function() {
|
||||
let amount = 500;
|
||||
await lb.limitedDeposit({value: amount});
|
||||
|
||||
assert.equal(web3.eth.getBalance(lb.address), amount);
|
||||
|
||||
try {
|
||||
await lb.limitedDeposit({value: amount+1});
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
119
test/MultisigWallet.js
Normal file
119
test/MultisigWallet.js
Normal file
@ -0,0 +1,119 @@
|
||||
'use strict';
|
||||
|
||||
var MultisigWalletMock = artifacts.require('./helpers/MultisigWalletMock.sol');
|
||||
require('./helpers/transactionMined.js');
|
||||
|
||||
contract('MultisigWallet', function(accounts) {
|
||||
let shouldntFail = function(err) {
|
||||
assert.isFalse(!!err);
|
||||
};
|
||||
it('should send balance to passed address upon death', async function() {
|
||||
//Give account[0] 20 ether
|
||||
web3.eth.sendTransaction({from: web3.eth.coinbase, to: accounts[0], value: web3.toWei('20','ether')}, shouldntFail);
|
||||
|
||||
let dailyLimit = 10;
|
||||
let ownersRequired = 2;
|
||||
|
||||
//Create MultisigWallet contract with 10 ether
|
||||
let wallet = await MultisigWalletMock.new(accounts, ownersRequired, dailyLimit, {value: web3.toWei('10', 'ether')});
|
||||
|
||||
//Get balances of owner and wallet after wallet creation.
|
||||
let ownerBalance = web3.eth.getBalance(accounts[0]);
|
||||
let walletBalance = web3.eth.getBalance(wallet.address);
|
||||
let hash = 1234;
|
||||
|
||||
//Call kill function from two different owner accounts, satisfying owners required
|
||||
await wallet.kill(accounts[0], {data: hash});
|
||||
let txnHash = await wallet.kill(accounts[0], {from: accounts[1], data: hash});
|
||||
|
||||
//Get balances of owner and wallet after kill function is complete, compare with previous values
|
||||
let newOwnerBalance = web3.eth.getBalance(accounts[0]);
|
||||
let newWalletBalance = web3.eth.getBalance(wallet.address);
|
||||
|
||||
assert.isTrue(newOwnerBalance > ownerBalance);
|
||||
assert.isTrue(newWalletBalance < walletBalance);
|
||||
});
|
||||
|
||||
it('should execute transaction if below daily limit', async function() {
|
||||
//Give account[0] 20 ether
|
||||
web3.eth.sendTransaction({from: web3.eth.coinbase, to: accounts[0], value: web3.toWei('20','ether')}, shouldntFail);
|
||||
|
||||
let dailyLimit = 10;
|
||||
let ownersRequired = 2;
|
||||
|
||||
//Create MultisigWallet contract with 10 ether
|
||||
let wallet = await MultisigWalletMock.new(accounts, ownersRequired, dailyLimit, {value: web3.toWei('10', 'ether')});
|
||||
|
||||
let accountBalance = web3.eth.getBalance(accounts[2]);
|
||||
let hash = 1234;
|
||||
|
||||
//Owner account0 commands wallet to send 9 wei to account2
|
||||
let txnHash = await wallet.execute(accounts[2], 9, hash);
|
||||
|
||||
//Balance of account2 should have increased
|
||||
let newAccountBalance = web3.eth.getBalance(accounts[2]);
|
||||
assert.isTrue(newAccountBalance > accountBalance);
|
||||
});
|
||||
|
||||
it('should prevent execution of transaction if above daily limit', async function() {
|
||||
//Give account[0] 20 ether
|
||||
web3.eth.sendTransaction({from: web3.eth.coinbase, to: accounts[0], value: web3.toWei('20','ether')}, shouldntFail);
|
||||
|
||||
let dailyLimit = 10;
|
||||
let ownersRequired = 2;
|
||||
|
||||
//Create MultisigWallet contract with 10 ether
|
||||
let wallet = await MultisigWalletMock.new(accounts, ownersRequired, dailyLimit, {value: web3.toWei('10', 'ether')});
|
||||
|
||||
let accountBalance = web3.eth.getBalance(accounts[2]);
|
||||
let hash = 1234;
|
||||
|
||||
//Owner account0 commands wallet to send 9 wei to account2
|
||||
let txnHash = await wallet.execute(accounts[2], 9, hash);
|
||||
|
||||
//Balance of account2 should have increased
|
||||
let newAccountBalance = web3.eth.getBalance(accounts[2]);
|
||||
assert.isTrue(newAccountBalance > accountBalance);
|
||||
|
||||
accountBalance = newAccountBalance;
|
||||
hash = 4567;
|
||||
|
||||
//Owner account0 commands wallet to send 2 more wei to account2, going over the daily limit of 10
|
||||
txnHash = await wallet.execute(accounts[2], 2, hash);
|
||||
|
||||
//Balance of account2 should not change
|
||||
newAccountBalance = web3.eth.getBalance(accounts[2]);
|
||||
assert.equal(newAccountBalance.toString(), accountBalance.toString());
|
||||
});
|
||||
|
||||
it('should execute transaction if above daily limit and enough owners approve', async function() {
|
||||
//Give account[0] 20 ether
|
||||
web3.eth.sendTransaction({from: web3.eth.coinbase, to: accounts[0], value: web3.toWei('20','ether')}, shouldntFail);
|
||||
|
||||
let dailyLimit = 10;
|
||||
let ownersRequired = 2;
|
||||
|
||||
//Create MultisigWallet contract with 10 ether
|
||||
let wallet = await MultisigWalletMock.new(accounts, ownersRequired, dailyLimit, {value: web3.toWei('10', 'ether')});
|
||||
|
||||
let accountBalance = web3.eth.getBalance(accounts[2]);
|
||||
let hash = 1234;
|
||||
|
||||
//Owner account0 commands wallet to send 11 wei to account2
|
||||
let txnHash = await wallet.execute(accounts[2], 11, hash);
|
||||
|
||||
//Balance of account2 should not change
|
||||
let newAccountBalance = web3.eth.getBalance(accounts[2]);
|
||||
assert.equal(newAccountBalance.toString(), accountBalance.toString());
|
||||
|
||||
accountBalance = newAccountBalance;
|
||||
|
||||
//Owner account1 commands wallet to send 11 wei to account2
|
||||
txnHash = await wallet.execute(accounts[2], 2, hash);
|
||||
|
||||
//Balance of account2 should change
|
||||
newAccountBalance = web3.eth.getBalance(accounts[2]);
|
||||
assert.isTrue(newAccountBalance > accountBalance);
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,58 +1,45 @@
|
||||
'use strict';
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
|
||||
var Ownable = artifacts.require('../contracts/ownership/Ownable.sol');
|
||||
|
||||
contract('Ownable', function(accounts) {
|
||||
var ownable;
|
||||
let ownable;
|
||||
|
||||
beforeEach(function() {
|
||||
return Ownable.new().then(function(deployed) {
|
||||
ownable = deployed;
|
||||
});
|
||||
beforeEach(async function() {
|
||||
ownable = await Ownable.new();
|
||||
});
|
||||
|
||||
it("should have an owner", function(done) {
|
||||
return ownable.owner()
|
||||
.then(function(owner) {
|
||||
assert.isTrue(owner != 0);
|
||||
})
|
||||
.then(done)
|
||||
it('should have an owner', async function() {
|
||||
let owner = await ownable.owner();
|
||||
assert.isTrue(owner !== 0);
|
||||
});
|
||||
|
||||
it("changes owner after transfer", function(done) {
|
||||
var other = accounts[1];
|
||||
return ownable.transfer(other)
|
||||
.then(function() {
|
||||
return ownable.owner();
|
||||
})
|
||||
.then(function(owner) {
|
||||
assert.isTrue(owner === other);
|
||||
})
|
||||
.then(done)
|
||||
it('changes owner after transfer', async function() {
|
||||
let other = accounts[1];
|
||||
await ownable.transferOwnership(other);
|
||||
let owner = await ownable.owner();
|
||||
|
||||
assert.isTrue(owner === other);
|
||||
});
|
||||
|
||||
it("should prevent non-owners from transfering" ,function(done) {
|
||||
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)
|
||||
it('should prevent non-owners from transfering', async function() {
|
||||
const other = accounts[2];
|
||||
const owner = await ownable.owner.call();
|
||||
assert.isTrue(owner !== other);
|
||||
try {
|
||||
await ownable.transferOwnership(other, {from: other});
|
||||
} catch(error) {
|
||||
assertJump(error);
|
||||
}
|
||||
});
|
||||
|
||||
it("should guard ownership against stuck state" ,function(done) {
|
||||
var ownable = Ownable.deployed();
|
||||
it('should guard ownership against stuck state', async function() {
|
||||
let originalOwner = await ownable.owner();
|
||||
await ownable.transferOwnership(null, {from: originalOwner});
|
||||
let newOwner = await ownable.owner();
|
||||
|
||||
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);
|
||||
});
|
||||
assert.equal(originalOwner, newOwner);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
56
test/Pausable.js
Normal file
56
test/Pausable.js
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
var PausableMock = artifacts.require('helpers/PausableMock.sol');
|
||||
|
||||
contract('Pausable', function(accounts) {
|
||||
|
||||
it('can perform normal process in non-emergency', async function() {
|
||||
let Pausable = await PausableMock.new();
|
||||
let count0 = await Pausable.count();
|
||||
assert.equal(count0, 0);
|
||||
|
||||
await Pausable.normalProcess();
|
||||
let count1 = await Pausable.count();
|
||||
assert.equal(count1, 1);
|
||||
});
|
||||
|
||||
it('can not perform normal process in emergency', async function() {
|
||||
let Pausable = await PausableMock.new();
|
||||
await Pausable.emergencyStop();
|
||||
let count0 = await Pausable.count();
|
||||
assert.equal(count0, 0);
|
||||
|
||||
await Pausable.normalProcess();
|
||||
let count1 = await Pausable.count();
|
||||
assert.equal(count1, 0);
|
||||
});
|
||||
|
||||
|
||||
it('can not take drastic measure in non-emergency', async function() {
|
||||
let Pausable = await PausableMock.new();
|
||||
await Pausable.drasticMeasure();
|
||||
let drasticMeasureTaken = await Pausable.drasticMeasureTaken();
|
||||
|
||||
assert.isFalse(drasticMeasureTaken);
|
||||
});
|
||||
|
||||
it('can take a drastic measure in an emergency', async function() {
|
||||
let Pausable = await PausableMock.new();
|
||||
await Pausable.emergencyStop();
|
||||
await Pausable.drasticMeasure();
|
||||
let drasticMeasureTaken = await Pausable.drasticMeasureTaken();
|
||||
|
||||
assert.isTrue(drasticMeasureTaken);
|
||||
});
|
||||
|
||||
it('should resume allowing normal process after emergency is over', async function() {
|
||||
let Pausable = await PausableMock.new();
|
||||
await Pausable.emergencyStop();
|
||||
await Pausable.release();
|
||||
await Pausable.normalProcess();
|
||||
let count0 = await Pausable.count();
|
||||
|
||||
assert.equal(count0, 1);
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,102 +1,59 @@
|
||||
var PullPaymentMock = artifacts.require("./helpers/PullPaymentMock.sol");
|
||||
|
||||
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't call asyncSend externally", async function() {
|
||||
let ppc = await PullPaymentMock.new();
|
||||
assert.isUndefined(ppc.asyncSend);
|
||||
});
|
||||
|
||||
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 record an async payment correctly", async function() {
|
||||
let AMOUNT = 100;
|
||||
let ppce = await PullPaymentMock.new();
|
||||
let callSend = await ppce.callSend(accounts[0], AMOUNT);
|
||||
let paymentsToAccount0 = await ppce.payments(accounts[0]);
|
||||
|
||||
assert.equal(paymentsToAccount0, AMOUNT);
|
||||
});
|
||||
|
||||
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 multiple balances on one account", async function() {
|
||||
let ppce = await PullPaymentMock.new();
|
||||
let call1 = await ppce.callSend(accounts[0], 200);
|
||||
let call2 = await ppce.callSend(accounts[0], 300);
|
||||
let paymentsToAccount0 = await ppce.payments(accounts[0]);
|
||||
|
||||
assert.equal(paymentsToAccount0, 500);
|
||||
});
|
||||
|
||||
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 add balances on multiple accounts", async function() {
|
||||
let ppce = await PullPaymentMock.new();
|
||||
let call1 = await ppce.callSend(accounts[0], 200);
|
||||
let call2 = await ppce.callSend(accounts[1], 300);
|
||||
|
||||
let paymentsToAccount0 = await ppce.payments(accounts[0]);
|
||||
assert.equal(paymentsToAccount0, 200);
|
||||
|
||||
let paymentsToAccount1 = await ppce.payments(accounts[1]);
|
||||
assert.equal(paymentsToAccount1, 300);
|
||||
});
|
||||
|
||||
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);
|
||||
it("can withdraw payment", async function() {
|
||||
let AMOUNT = 17*1e18;
|
||||
let payee = accounts[1];
|
||||
let initialBalance = web3.eth.getBalance(payee);
|
||||
|
||||
let ppce = await PullPaymentMock.new({value: AMOUNT});
|
||||
let call1 = await ppce.callSend(payee, AMOUNT);
|
||||
|
||||
let payment1 = await ppce.payments(payee);
|
||||
assert.equal(payment1, AMOUNT);
|
||||
|
||||
let withdraw = await ppce.withdrawPayments({from: payee});
|
||||
let payment2 = await ppce.payments(payee);
|
||||
assert.equal(payment2, 0);
|
||||
|
||||
let balance = web3.eth.getBalance(payee);
|
||||
assert(Math.abs(balance-initialBalance-AMOUNT) < 1e16);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
115
test/SafeMath.js
115
test/SafeMath.js
@ -1,82 +1,71 @@
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
var SafeMathMock = artifacts.require("./helpers/SafeMathMock.sol");
|
||||
|
||||
contract('SafeMath', function(accounts) {
|
||||
|
||||
var safeMath;
|
||||
let safeMath;
|
||||
|
||||
before(function() {
|
||||
return SafeMathMock.new()
|
||||
.then(function(_safeMath) {
|
||||
safeMath = _safeMath;
|
||||
});
|
||||
before(async function() {
|
||||
safeMath = await SafeMathMock.new();
|
||||
});
|
||||
|
||||
it("multiplies correctly", function(done) {
|
||||
var a = 5678;
|
||||
var b = 1234;
|
||||
return safeMath.multiply(a, b)
|
||||
.then(function() {
|
||||
return safeMath.result();
|
||||
})
|
||||
.then(function(result) {
|
||||
assert.equal(result, a*b);
|
||||
})
|
||||
.then(done);
|
||||
it("multiplies correctly", async function() {
|
||||
let a = 5678;
|
||||
let b = 1234;
|
||||
let mult = await safeMath.multiply(a, b);
|
||||
let result = await safeMath.result();
|
||||
assert.equal(result, a*b);
|
||||
});
|
||||
|
||||
it("adds correctly", function(done) {
|
||||
var a = 5678;
|
||||
var b = 1234;
|
||||
return safeMath.add(a, b)
|
||||
.then(function() {
|
||||
return safeMath.result();
|
||||
})
|
||||
.then(function(result) {
|
||||
assert.equal(result, a+b);
|
||||
})
|
||||
.then(done);
|
||||
it("adds correctly", async function() {
|
||||
let a = 5678;
|
||||
let b = 1234;
|
||||
let add = await safeMath.add(a, b);
|
||||
let result = await safeMath.result();
|
||||
|
||||
assert.equal(result, a+b);
|
||||
});
|
||||
|
||||
it("subtracts correctly", function(done) {
|
||||
var a = 5678;
|
||||
var b = 1234;
|
||||
return safeMath.subtract(a, b)
|
||||
.then(function() {
|
||||
return safeMath.result();
|
||||
})
|
||||
.then(function(result) {
|
||||
assert.equal(result, a-b);
|
||||
})
|
||||
.then(done);
|
||||
it("subtracts correctly", async function() {
|
||||
let a = 5678;
|
||||
let b = 1234;
|
||||
let subtract = await safeMath.subtract(a, b);
|
||||
let result = await safeMath.result();
|
||||
|
||||
assert.equal(result, a-b);
|
||||
});
|
||||
|
||||
it("should throw an error if subtraction result would be negative", function (done) {
|
||||
var a = 1234;
|
||||
var b = 5678;
|
||||
return safeMath.subtract(a, b)
|
||||
.catch(function(error) {
|
||||
if (error.message.search('invalid JUMP') == -1) throw error
|
||||
})
|
||||
.then(done);
|
||||
it("should throw an error if subtraction result would be negative", async function () {
|
||||
let a = 1234;
|
||||
let b = 5678;
|
||||
try {
|
||||
let subtract = await safeMath.subtract(a, b);
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
it("should throw an error on addition overflow", function(done) {
|
||||
var a = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
|
||||
var b = 1;
|
||||
return safeMath.add(a, b)
|
||||
.catch(function(error) {
|
||||
if (error.message.search('invalid JUMP') == -1) throw error
|
||||
})
|
||||
.then(done);
|
||||
it("should throw an error on addition overflow", async function() {
|
||||
let a = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
|
||||
let b = 1;
|
||||
try {
|
||||
let add = await safeMath.add(a, b);
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
it("should throw an error on multiplication overflow", function(done) {
|
||||
var a = 115792089237316195423570985008687907853269984665640564039457584007913129639933;
|
||||
var b = 2;
|
||||
return safeMath.multiply(a, b)
|
||||
.catch(function(error) {
|
||||
if (error.message.search('invalid JUMP') == -1) throw error
|
||||
})
|
||||
.then(done);
|
||||
it("should throw an error on multiplication overflow", async function() {
|
||||
let a = 115792089237316195423570985008687907853269984665640564039457584007913129639933;
|
||||
let b = 2;
|
||||
try {
|
||||
let multiply = await safeMath.multiply(a, b);
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
103
test/Shareable.js
Normal file
103
test/Shareable.js
Normal file
@ -0,0 +1,103 @@
|
||||
var ShareableMock = artifacts.require("./helpers/ShareableMock.sol");
|
||||
|
||||
contract('Shareable', function(accounts) {
|
||||
|
||||
it('should construct with correct owners and number of sigs required', async function() {
|
||||
let requiredSigs = 2;
|
||||
let owners = accounts.slice(1,4);
|
||||
let shareable = await ShareableMock.new(owners, requiredSigs);
|
||||
|
||||
let required = await shareable.required();
|
||||
assert.equal(required, requiredSigs);
|
||||
let owner = await shareable.getOwner(0);
|
||||
assert.equal(owner, accounts[0]);
|
||||
|
||||
for(let i = 0; i < accounts.length; i++) {
|
||||
let owner = await shareable.getOwner(i);
|
||||
let isowner = await shareable.isOwner(accounts[i]);
|
||||
if(i <= owners.length) {
|
||||
assert.equal(accounts[i], owner);
|
||||
assert.isTrue(isowner);
|
||||
} else {
|
||||
assert.notEqual(accounts[i], owner);
|
||||
assert.isFalse(isowner);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should only perform multisig function with enough sigs', async function() {
|
||||
let requiredSigs = 3;
|
||||
let owners = accounts.slice(1,4);
|
||||
let shareable = await ShareableMock.new(owners, requiredSigs);
|
||||
let hash = 1234;
|
||||
|
||||
let initCount = await shareable.count();
|
||||
initCount = initCount.toString();
|
||||
|
||||
for(let i = 0; i < requiredSigs; i++) {
|
||||
await shareable.increaseCount(hash, {from: accounts[i]});
|
||||
let count = await shareable.count();
|
||||
if(i == requiredSigs - 1) {
|
||||
assert.equal(Number(initCount)+1, count.toString());
|
||||
} else {
|
||||
assert.equal(initCount, count.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should require approval from different owners', async function() {
|
||||
let requiredSigs = 2;
|
||||
let owners = accounts.slice(1,4);
|
||||
let shareable = await ShareableMock.new(owners, requiredSigs);
|
||||
let hash = 1234;
|
||||
|
||||
let initCount = await shareable.count();
|
||||
initCount = initCount.toString();
|
||||
|
||||
//Count shouldn't increase when the same owner calls repeatedly
|
||||
for(let i = 0; i < 2; i++) {
|
||||
await shareable.increaseCount(hash);
|
||||
let count = await shareable.count();
|
||||
assert.equal(initCount, count.toString());
|
||||
}
|
||||
});
|
||||
|
||||
it('should reset sig count after operation is approved', async function() {
|
||||
let requiredSigs = 3;
|
||||
let owners = accounts.slice(1,4);
|
||||
let shareable = await ShareableMock.new(owners, requiredSigs);
|
||||
let hash = 1234;
|
||||
|
||||
let initCount = await shareable.count();
|
||||
|
||||
for(let i = 0; i < requiredSigs * 3; i++) {
|
||||
await shareable.increaseCount(hash, {from: accounts[i % 4]});
|
||||
let count = await shareable.count();
|
||||
if((i%(requiredSigs)) == requiredSigs - 1) {
|
||||
initCount = Number(initCount)+1;
|
||||
assert.equal(initCount, count);
|
||||
} else {
|
||||
assert.equal(initCount.toString(), count);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should not perform multisig function after an owner revokes', async function() {
|
||||
let requiredSigs = 3;
|
||||
let owners = accounts.slice(1,4);
|
||||
let shareable = await ShareableMock.new(owners, requiredSigs);
|
||||
let hash = 1234;
|
||||
|
||||
let initCount = await shareable.count();
|
||||
|
||||
for(let i = 0; i < requiredSigs; i++) {
|
||||
if(i == 1) {
|
||||
await shareable.revoke(hash, {from: accounts[i-1]});
|
||||
}
|
||||
await shareable.increaseCount(hash, {from: accounts[i]});
|
||||
let count = await shareable.count();
|
||||
assert.equal(initCount.toString(), count);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,108 +1,67 @@
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
var StandardTokenMock = artifacts.require("./helpers/StandardTokenMock.sol");
|
||||
|
||||
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 totalSupply after construction", async function() {
|
||||
let token = await StandardTokenMock.new(accounts[0], 100);
|
||||
let totalSupply = await token.totalSupply();
|
||||
|
||||
assert.equal(totalSupply, 100);
|
||||
})
|
||||
|
||||
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 the correct allowance amount after approval", async function() {
|
||||
let token = await StandardTokenMock.new();
|
||||
let approve = await token.approve(accounts[1], 100);
|
||||
let allowance = await token.allowance(accounts[0], accounts[1]);
|
||||
|
||||
assert.equal(allowance, 100);
|
||||
});
|
||||
|
||||
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 return correct balances after transfer", async function() {
|
||||
let token = await StandardTokenMock.new(accounts[0], 100);
|
||||
let transfer = await token.transfer(accounts[1], 100);
|
||||
let balance0 = await token.balanceOf(accounts[0]);
|
||||
assert.equal(balance0, 0);
|
||||
|
||||
let balance1 = await token.balanceOf(accounts[1]);
|
||||
assert.equal(balance1, 100);
|
||||
});
|
||||
|
||||
it("should throw an error 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 throw an error when trying to transfer more than balance", async function() {
|
||||
let token = await StandardTokenMock.new(accounts[0], 100);
|
||||
try {
|
||||
let transfer = await token.transfer(accounts[1], 101);
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
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 return correct balances after transfering from another account", async function() {
|
||||
let token = await StandardTokenMock.new(accounts[0], 100);
|
||||
let approve = await token.approve(accounts[1], 100);
|
||||
let transferFrom = await token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]});
|
||||
|
||||
let balance0 = await token.balanceOf(accounts[0]);
|
||||
assert.equal(balance0, 0);
|
||||
|
||||
let balance1 = await token.balanceOf(accounts[2]);
|
||||
assert.equal(balance1, 100);
|
||||
|
||||
let balance2 = await token.balanceOf(accounts[1]);
|
||||
assert.equal(balance2, 0);
|
||||
});
|
||||
|
||||
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);
|
||||
it("should throw an error when trying to transfer more than allowed", async function() {
|
||||
let token = await StandardTokenMock.new();
|
||||
let approve = await token.approve(accounts[1], 99);
|
||||
try {
|
||||
let transfer = await token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]});
|
||||
} catch (error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
99
test/VestedToken.js
Normal file
99
test/VestedToken.js
Normal file
@ -0,0 +1,99 @@
|
||||
const assertJump = require('./helpers/assertJump');
|
||||
const timer = require('./helpers/timer');
|
||||
var VestedTokenMock = artifacts.require("./helpers/VestedTokenMock.sol");
|
||||
|
||||
contract('VestedToken', function(accounts) {
|
||||
let token = null
|
||||
let now = 0
|
||||
|
||||
const tokenAmount = 50
|
||||
|
||||
const granter = accounts[0]
|
||||
const receiver = accounts[1]
|
||||
|
||||
beforeEach(async () => {
|
||||
token = await VestedTokenMock.new(granter, 100);
|
||||
now = web3.eth.getBlock(web3.eth.blockNumber).timestamp;
|
||||
})
|
||||
|
||||
it('granter can grant tokens without vesting', async () => {
|
||||
await token.transfer(receiver, tokenAmount, { from: granter })
|
||||
|
||||
assert.equal(await token.balanceOf(receiver), tokenAmount);
|
||||
assert.equal(await token.transferableTokens(receiver, now), tokenAmount);
|
||||
})
|
||||
|
||||
describe('getting a token grant', async () => {
|
||||
const cliff = 10000
|
||||
const vesting = 20000 // seconds
|
||||
|
||||
beforeEach(async () => {
|
||||
await token.grantVestedTokens(receiver, tokenAmount, now, now + cliff, now + vesting, { from: granter })
|
||||
})
|
||||
|
||||
it('tokens are received', async () => {
|
||||
assert.equal(await token.balanceOf(receiver), tokenAmount);
|
||||
})
|
||||
|
||||
it('has 0 transferable tokens before cliff', async () => {
|
||||
assert.equal(await token.transferableTokens(receiver, now), 0);
|
||||
})
|
||||
|
||||
it('all tokens are transferable after vesting', async () => {
|
||||
assert.equal(await token.transferableTokens(receiver, now + vesting + 1), tokenAmount);
|
||||
})
|
||||
|
||||
it('throws when trying to transfer non vested tokens', async () => {
|
||||
try {
|
||||
await token.transfer(accounts[7], 1, { from: receiver })
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
})
|
||||
|
||||
it('throws when trying to transfer from non vested tokens', async () => {
|
||||
try {
|
||||
await token.approve(accounts[7], 1, { from: receiver })
|
||||
await token.transferFrom(receiver, accounts[7], tokenAmount, { from: accounts[7] })
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
})
|
||||
|
||||
it('can be revoked by granter', async () => {
|
||||
await token.revokeTokenGrant(receiver, 0, { from: granter });
|
||||
assert.equal(await token.balanceOf(receiver), 0);
|
||||
assert.equal(await token.balanceOf(granter), 100);
|
||||
})
|
||||
|
||||
it('cannot be revoked by non granter', async () => {
|
||||
try {
|
||||
await token.revokeTokenGrant(receiver, 0, { from: accounts[3] });
|
||||
} catch(error) {
|
||||
return assertJump(error);
|
||||
}
|
||||
assert.fail('should have thrown before');
|
||||
})
|
||||
|
||||
it('can be revoked by granter and non vested tokens are returned', async () => {
|
||||
await timer(cliff);
|
||||
await token.revokeTokenGrant(receiver, 0, { from: granter });
|
||||
assert.equal(await token.balanceOf(receiver), tokenAmount * cliff / vesting);
|
||||
})
|
||||
|
||||
it('can transfer all tokens after vesting ends', async () => {
|
||||
await timer(vesting + 1);
|
||||
await token.transfer(accounts[7], tokenAmount, { from: receiver })
|
||||
assert.equal(await token.balanceOf(accounts[7]), tokenAmount);
|
||||
})
|
||||
|
||||
it('can approve and transferFrom all tokens after vesting ends', async () => {
|
||||
await timer(vesting + 1);
|
||||
await token.approve(accounts[7], tokenAmount, { from: receiver })
|
||||
await token.transferFrom(receiver, accounts[7], tokenAmount, { from: accounts[7] })
|
||||
assert.equal(await token.balanceOf(accounts[7]), tokenAmount);
|
||||
})
|
||||
})
|
||||
});
|
||||
@ -1,5 +1,8 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../token/BasicToken.sol';
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import '../../contracts/token/BasicToken.sol';
|
||||
|
||||
|
||||
// mock class using BasicToken
|
||||
contract BasicTokenMock is BasicToken {
|
||||
23
test/helpers/DayLimitMock.sol
Normal file
23
test/helpers/DayLimitMock.sol
Normal file
@ -0,0 +1,23 @@
|
||||
pragma solidity ^0.4.8;
|
||||
import "../../contracts/DayLimit.sol";
|
||||
|
||||
contract DayLimitMock is DayLimit {
|
||||
uint public totalSpending;
|
||||
|
||||
function DayLimitMock(uint _value) DayLimit(_value) {
|
||||
totalSpending = 0;
|
||||
}
|
||||
|
||||
function attemptSpend(uint _value) external limitedDaily(_value) {
|
||||
totalSpending += _value;
|
||||
}
|
||||
|
||||
function setDailyLimit(uint _newLimit) external {
|
||||
_setDailyLimit(_newLimit);
|
||||
}
|
||||
|
||||
function resetSpentToday() external {
|
||||
_resetSpentToday();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import {Bounty, Target} from "../Bounty.sol";
|
||||
import {Bounty, Target} from "../../contracts/Bounty.sol";
|
||||
|
||||
|
||||
contract InsecureTargetMock is Target {
|
||||
@ -1,5 +1,8 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../LimitBalance.sol';
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import '../../contracts/LimitBalance.sol';
|
||||
|
||||
|
||||
// mock class using LimitBalance
|
||||
contract LimitBalanceMock is LimitBalance(1000) {
|
||||
12
test/helpers/MultisigWalletMock.sol
Normal file
12
test/helpers/MultisigWalletMock.sol
Normal file
@ -0,0 +1,12 @@
|
||||
pragma solidity ^0.4.8;
|
||||
import "../../contracts/MultisigWallet.sol";
|
||||
|
||||
contract MultisigWalletMock is MultisigWallet {
|
||||
uint public totalSpending;
|
||||
|
||||
function MultisigWalletMock(address[] _owners, uint _required, uint _daylimit)
|
||||
MultisigWallet(_owners, _required, _daylimit) payable { }
|
||||
|
||||
function changeOwner(address _from, address _to) external { }
|
||||
|
||||
}
|
||||
@ -1,12 +1,15 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../Stoppable.sol';
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
// mock class using Stoppable
|
||||
contract StoppableMock is Stoppable {
|
||||
|
||||
import '../../contracts/lifecycle/Pausable.sol';
|
||||
|
||||
|
||||
// mock class using Pausable
|
||||
contract PausableMock is Pausable {
|
||||
bool public drasticMeasureTaken;
|
||||
uint public count;
|
||||
|
||||
function StoppableMock() {
|
||||
function PausableMock() {
|
||||
drasticMeasureTaken = false;
|
||||
count = 0;
|
||||
}
|
||||
@ -1,8 +1,14 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../PullPayment.sol';
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import '../../contracts/payment/PullPayment.sol';
|
||||
|
||||
|
||||
// mock class using PullPayment
|
||||
contract PullPaymentMock is PullPayment {
|
||||
|
||||
function PullPaymentMock() payable { }
|
||||
|
||||
// test helper function to call asyncSend
|
||||
function callSend(address dest, uint amount) {
|
||||
asyncSend(dest, amount);
|
||||
@ -1,5 +1,8 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../SafeMath.sol';
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import '../../contracts/SafeMath.sol';
|
||||
|
||||
|
||||
contract SafeMathMock is SafeMath {
|
||||
uint public result;
|
||||
@ -1,7 +1,7 @@
|
||||
pragma solidity ^0.4.4;
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import {Bounty, Target} from "../Bounty.sol";
|
||||
import {Bounty, Target} from "../../contracts/Bounty.sol";
|
||||
|
||||
|
||||
contract SecureTargetMock is Target {
|
||||
16
test/helpers/ShareableMock.sol
Normal file
16
test/helpers/ShareableMock.sol
Normal file
@ -0,0 +1,16 @@
|
||||
pragma solidity ^0.4.8;
|
||||
import "../../contracts/ownership/Shareable.sol";
|
||||
|
||||
contract ShareableMock is Shareable {
|
||||
|
||||
uint public count = 0;
|
||||
|
||||
function ShareableMock(address[] _owners, uint _required) Shareable(_owners, _required) {
|
||||
|
||||
}
|
||||
|
||||
function increaseCount(bytes32 action) onlymanyowners(action) {
|
||||
count = count + 1;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
pragma solidity ^0.4.4;
|
||||
import '../token/StandardToken.sol';
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
|
||||
import '../../contracts/token/StandardToken.sol';
|
||||
|
||||
|
||||
// mock class using StandardToken
|
||||
contract StandardTokenMock is StandardToken {
|
||||
11
test/helpers/VestedTokenMock.sol
Normal file
11
test/helpers/VestedTokenMock.sol
Normal file
@ -0,0 +1,11 @@
|
||||
pragma solidity ^0.4.8;
|
||||
|
||||
import '../../contracts/token/VestedToken.sol';
|
||||
|
||||
// mock class using StandardToken
|
||||
contract VestedTokenMock is VestedToken {
|
||||
function VestedTokenMock(address initialAccount, uint initialBalance) {
|
||||
balances[initialAccount] = initialBalance;
|
||||
totalSupply = initialBalance;
|
||||
}
|
||||
}
|
||||
3
test/helpers/assertJump.js
Normal file
3
test/helpers/assertJump.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function(error) {
|
||||
assert.isAbove(error.message.search('invalid JUMP'), -1, 'Invalid JUMP error must be returned');
|
||||
}
|
||||
15
test/helpers/timer.js
Normal file
15
test/helpers/timer.js
Normal file
@ -0,0 +1,15 @@
|
||||
// timer for tests specific to testrpc
|
||||
module.exports = s => {
|
||||
return new Promise((resolve, reject) => {
|
||||
web3.currentProvider.sendAsync({
|
||||
jsonrpc: '2.0',
|
||||
method: 'evm_increaseTime',
|
||||
params: [s], // 60 seaconds, may need to be hex, I forget
|
||||
id: new Date().getTime() // Id of the request; anything works, really
|
||||
}, function(err) {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
//setTimeout(() => resolve(), s * 1000 + 600) // 600ms breathing room for testrpc to sync
|
||||
});
|
||||
};
|
||||
34
test/helpers/transactionMined.js
Normal file
34
test/helpers/transactionMined.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
//from https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6
|
||||
module.export = web3.eth.transactionMined = function (txnHash, interval) {
|
||||
var transactionReceiptAsync;
|
||||
interval = interval ? interval : 500;
|
||||
transactionReceiptAsync = function(txnHash, resolve, reject) {
|
||||
try {
|
||||
var receipt = web3.eth.getTransactionReceipt(txnHash);
|
||||
if (receipt === null) {
|
||||
setTimeout(function () {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
}, interval);
|
||||
} else {
|
||||
resolve(receipt);
|
||||
}
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(txnHash)) {
|
||||
var promises = [];
|
||||
txnHash.forEach(function (oneTxHash) {
|
||||
promises.push(
|
||||
web3.eth.getTransactionReceiptMined(oneTxHash, interval));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
} else {
|
||||
return new Promise(function (resolve, reject) {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
});
|
||||
}
|
||||
};
|
||||
22
truffle.js
22
truffle.js
@ -1,6 +1,22 @@
|
||||
require('babel-register');
|
||||
require('babel-polyfill');
|
||||
|
||||
var HDWalletProvider = require('truffle-hdwallet-provider');
|
||||
|
||||
var mnemonic = '[REDACTED]';
|
||||
var provider = new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/');
|
||||
|
||||
|
||||
module.exports = {
|
||||
rpc: {
|
||||
host: "localhost",
|
||||
port: 8545
|
||||
networks: {
|
||||
development: {
|
||||
host: 'localhost',
|
||||
port: 8545,
|
||||
network_id: '*'
|
||||
},
|
||||
ropsten: {
|
||||
provider: provider,
|
||||
network_id: 3 // official id of the ropsten network
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user