├── .editorconfig
├── .gitignore
├── .solcover.js
├── .soliumignore
├── .soliumrc.json
├── .travis.yml
├── .yarnrc
├── CHANGELOG.md
├── README.md
├── build
└── doc
│ └── .gitignore
├── contracts
├── Bidirectional.sol
├── Conditional.sol
├── DistributeEth.sol
├── DistributeToken.sol
├── LibBidirectional.sol
├── LibCommon.sol
├── LibLineup.sol
├── LibMultisig.sol
├── Lineup.sol
├── Migrations.sol
├── Multisig.sol
├── Proxy.sol
├── PublicRegistry.sol
├── TestContract.sol
├── TestDelegatecall.sol
├── TestToken.sol
└── TransferToken.sol
├── doc
├── Contract.md
├── channel-states.svg
└── development
│ └── verification.md
├── migrations
├── 10_deploy_LibLineup.ts
├── 1_initial_migration.ts
├── 3_deploy_LibBidirectional.ts
├── 4_deploy_registry.ts
├── 5_deploy_transfer_token.ts
├── 6_deploy_multisigLibrary.ts
├── 7_deploy_conditionalCallLibrary.ts
├── 8_deploy_publicRegistryLibrary.ts
└── 9_deploy_proxy.ts
├── package.json
├── src
├── MerkleTree.ts
├── Units.ts
├── globals.d.ts
└── index.ts
├── support
├── natspec
│ ├── NatSpec.ts
│ ├── compiler.ts
│ ├── index.ts
│ └── sources.ts
├── tsconfig.json
└── wrap
│ ├── Context.ts
│ ├── ContractTemplate.ts
│ ├── IndexTemplate.ts
│ ├── helpers.ts
│ ├── index.ts
│ └── templates
│ ├── _event.mustache
│ ├── _function.mustache
│ ├── _getter.mustache
│ ├── _method_input.mustache
│ ├── _method_output.mustache
│ ├── _params.mustache
│ ├── contract.mustache
│ └── index.mustache
├── test
├── scenarios
│ ├── Cooperative.test.ts
│ └── Dispute.test.ts
├── support
│ └── index.ts
└── unit
│ ├── Bidirectional.test.ts
│ ├── Conditional.test.ts
│ ├── Lineup.test.ts
│ ├── Multisig._test.ts
│ ├── Multisig.test.ts
│ └── PublicRegistry.test.ts
├── truffle.js
├── tsconfig.json
├── tslint.json
├── types
└── ethereumjs-abi
│ └── index.d.ts
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.sol]
14 | indent_size = 4
15 |
16 | [*.md]
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | .idea/
3 | node_modules/
4 | .env
5 | yarn-error.log
6 |
7 | **/*.js.map
8 | **/*.d.ts
9 | **/*.js
10 | coverage.json
11 | coverage/
12 | build/wrappers
13 |
14 | .DS_Store
15 | dist/
16 |
--------------------------------------------------------------------------------
/.solcover.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | copyPackages: ['zeppelin-solidity']
3 | }
4 |
--------------------------------------------------------------------------------
/.soliumignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | contracts/zeppelin-solidity
3 |
--------------------------------------------------------------------------------
/.soliumrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solium:all",
3 | "plugins": [
4 | "security"
5 | ],
6 | "rules": {
7 | "quotes": [
8 | "error",
9 | "double"
10 | ],
11 | "indentation": [
12 | "error",
13 | 4
14 | ],
15 | "arg-overflow": [
16 | "warning",
17 | 5
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - '10'
5 | before_install:
6 | - curl -o- -L https://yarnpkg.com/install.sh | bash
7 | - export PATH="$HOME/.yarn/bin:$PATH"
8 | script:
9 | - yarn lint
10 | - yarn test
11 | after_script:
12 | - yarn coverage && cat coverage/lcov.info | coveralls
13 | notifications:
14 | slack:
15 | secure: ROidOwDK3i9EJY9x2OojBmVGQYkxxZi92w1AsWfKutDktCaqocXLpjWLuPedTRs3/VFl/1YDMPhYzWQBXzVzlmMJ3kC4EKvZbBAu3C/3M+bDjo/hvqBeDLANn06WVmzKkcCHmhh6YJLK5a7C4ygoy9gS0SAzMC4Ou9ZJ8+V4umBf66//WRwNWZ+MKJ2P2qkuy4M55o3Ke98ozgukPTSuHUdpW4Zwo57C/YiJAy2ZMvda4oKYRmuRu7q0PkilHO+WRGv09CsAwTBCEUJluLva9zJuS1S+x+015LIzrya/3OqLp0ySoDOYD4PnR37H60/Vcvd1SGXXAj13lSZt7ByOVYKxfyQMmTe7CGFbpwgiZXVFUmQTygQeiPH0XZVzM8jxyaCRnUHeyGHunhDhsiDXSQ34Q6db29oH1h4KHMZOoJR0FetVontetZoDi7y6UzFiAvsfLc2CYkg7yukUFcIClmjTUSXt/+yjsLAgq9QviNo9HIvR/OtaoKQkom080jXn//Ntj3JEBbqLg1QIbKjmjpp5LwLRdd1CSoNahIiS07IOQV3kK89av22LX2VD5Ooqggq9VkTevN1T2yT3RoW6k6K9IwjVyQtjLdWbYFg6EbQwptvjl+eRa6D5I1OgkntN5MXGnS3aps3kistKvbv/xvpo8dgaw9bo9BQVBmxEF7Q=
16 |
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | --install.pure-lockfile true
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [2.0.2] - 2017-11-1
2 | ### Added
3 |
4 | - Add README.md
5 |
6 | ## [2.0.1] - 2017-11-1
7 | ### Fixed
8 |
9 | - Add 0x prefix to sign
10 |
11 | ## [2.0.0] - 2017-11-1
12 | ### Changed
13 |
14 | - All monetary values are now in BigNumber
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Machinomy contracts [![Build Status][travis-img]][travis] [![Coverage Status][coveralls-img]][coveralls]
2 | [travis]: https://travis-ci.org/machinomy/machinomy-contracts
3 | [travis-img]: https://img.shields.io/travis/machinomy/machinomy-contracts.svg
4 | [coveralls]: https://coveralls.io/github/machinomy/machinomy-contracts?branch=master
5 | [coveralls-img]: https://coveralls.io/repos/github/machinomy/machinomy-contracts/badge.svg?branch=master
6 |
7 | Machinomy contracts is a TypeScript interface for Ethereum contracts managed by [Truffle](https://github.com/trufflesuite/truffle) used by [Machinomy](https://github.com/machinomy/machinomy).
8 |
9 | ## Install
10 | ```
11 | $ yarn add @machinomy/contracts
12 | ```
13 |
14 | ## Workflow
15 | Use [testrpc](https://github.com/ethereumjs/testrpc) for fast development. Start testrpc by command:
16 | ```
17 | $ testrpc
18 | ```
19 |
20 | Then deploy contracts to the tesrpc network:
21 | ```
22 | $ yarn truffle:migrate
23 | ```
24 |
25 | Truffle generates json files by default. You need to compile the json files to ts files. Run:
26 | ```
27 | $ yarn build
28 | ```
29 | Now package is ready to use by Machinony.
30 |
31 | ## Deployment
32 | To deploy the package to the Ropsten network you need to run local geth instance and then run commands:
33 | ```
34 | $ yarn truffle:migrate --network ropsten
35 | $ yarn build
36 | ```
37 |
38 | ## Testing
39 |
40 | ```
41 | $ yarn prepublish && yarn test
42 | ```
43 |
44 | To log gas cost for transactions:
45 | ```
46 | $ LOG_GAS_COST=true yarn test
47 | ```
48 |
--------------------------------------------------------------------------------
/build/doc/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machinomy/mc2/9113e7c20383f0b7e47313244696023900e2d69f/build/doc/.gitignore
--------------------------------------------------------------------------------
/contracts/Bidirectional.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./Multisig.sol";
4 | import "./LibBidirectional.sol";
5 |
6 |
7 | /// @title Bidirectional Ether payment channels contract.
8 | contract Bidirectional {
9 | LibBidirectional.State public state;
10 |
11 | function Bidirectional(address _multisig, uint32 _settlementPeriod) public payable {
12 | state.multisig = Multisig(_multisig);
13 | state.lastUpdate = block.number;
14 | state.settlementPeriod = _settlementPeriod;
15 | // DidOpen
16 | }
17 |
18 | function () payable public {}
19 |
20 | function update(
21 | uint256 _nonce,
22 | uint256 _toSender,
23 | uint256 _toReceiver,
24 | bytes _senderSig,
25 | bytes _receiverSig
26 | ) external
27 | {
28 | LibBidirectional.update(
29 | state,
30 | _nonce,
31 | _toSender,
32 | _toReceiver,
33 | _senderSig,
34 | _receiverSig
35 | );
36 | // DidUpdate
37 | }
38 |
39 | function close(uint256 _toSender, uint256 _toReceiver, bytes _senderSig, bytes _receiverSig) public {
40 | require(LibBidirectional.canClose(state, _toSender, _toReceiver, _senderSig, _receiverSig));
41 | var (sender, receiver,) = state.multisig.state();
42 | receiver.transfer(_toReceiver);
43 | sender.transfer(_toSender);
44 | selfdestruct(state.multisig);
45 | }
46 |
47 | function withdraw() public {
48 | require(!LibBidirectional.isSettling(state));
49 |
50 | var (sender, receiver,) = state.multisig.state();
51 | receiver.transfer(state.toReceiver);
52 | sender.transfer(state.toSender);
53 | selfdestruct(state.multisig);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/contracts/Conditional.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./Lineup.sol";
4 | import "./PublicRegistry.sol";
5 |
6 |
7 | contract Conditional {
8 | function doCall(
9 | address _registry,
10 | bytes32 _lineupCF,
11 | bytes _proof,
12 | address _destination,
13 | uint256 _value,
14 | bytes _data
15 | ) public
16 | {
17 |
18 | PublicRegistry registry = PublicRegistry(_registry);
19 | address lineupAddress = registry.resolve(_lineupCF);
20 | Lineup lineup = Lineup(lineupAddress);
21 |
22 | bytes32 hash = callHash(_destination, _value, _data);
23 | require(lineup.isContained(_proof, hash));
24 | require(_destination.call.value(_value)(_data)); // solium-disable-line security/no-call-value
25 | }
26 |
27 | function doDelegate(
28 | address _registry,
29 | bytes32 _lineupCF,
30 | bytes _proof,
31 | address _destination,
32 | uint256 _value,
33 | bytes _data
34 | ) public
35 | {
36 |
37 | PublicRegistry registry = PublicRegistry(_registry);
38 | address lineupAddress = registry.resolve(_lineupCF);
39 | Lineup lineup = Lineup(lineupAddress);
40 |
41 | bytes32 hash = callHash(_destination, _value, _data);
42 | require(lineup.isContained(_proof, hash));
43 | require(_destination.delegatecall(_data)); // solium-disable-line security/no-low-level-calls
44 | }
45 |
46 | function callHash(address _destination, uint256 _value, bytes _data) public pure returns (bytes32) {
47 | return keccak256(_destination, _value, _data);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/contracts/DistributeEth.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 |
4 | contract DistributeEth {
5 | function execute(address a, address b, uint256 amountA, uint256 amountB) external {
6 | a.transfer(amountA);
7 | b.transfer(amountB);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/contracts/DistributeToken.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
4 |
5 |
6 | contract DistributeToken {
7 | function execute(address _token, address a, address b, uint256 amountA, uint256 amountB) public {
8 | StandardToken token = StandardToken(_token);
9 | require(token.transfer(a, amountA));
10 | require(token.transfer(b, amountB));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/contracts/LibBidirectional.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./Multisig.sol";
4 | import "./LibCommon.sol";
5 |
6 |
7 | library LibBidirectional {
8 | struct State {
9 | Multisig multisig;
10 | uint256 lastUpdate;
11 | uint256 settlementPeriod;
12 | uint256 nonce;
13 | uint256 toSender;
14 | uint256 toReceiver;
15 | }
16 |
17 | function canUpdate(
18 | State storage _self,
19 | uint256 _nonce,
20 | uint256 _toSender,
21 | uint256 _toReceiver,
22 | bytes _senderSig,
23 | bytes _receiverSig
24 | ) public view returns (bool)
25 | {
26 | bool isNonceHigher = _nonce > _self.nonce;
27 | // FIXME Add a secret to prevent replay attach
28 | bytes32 hash = LibCommon.recoveryDigest(keccak256(_nonce, _toSender, _toReceiver));
29 | bool isSigned = _self.multisig.isUnanimous(hash, _senderSig, _receiverSig);
30 | return isSettling(_self) && isNonceHigher && isSigned;
31 | }
32 |
33 | function update(
34 | State storage _state,
35 | uint256 _nonce,
36 | uint256 _toSender,
37 | uint256 _toReceiver,
38 | bytes _senderSig,
39 | bytes _receiverSig
40 | ) public
41 | {
42 | var can = canUpdate(
43 | _state,
44 | _nonce,
45 | _toSender,
46 | _toReceiver,
47 | _senderSig,
48 | _receiverSig
49 | );
50 | require(can);
51 | _state.toSender = _toSender;
52 | _state.toReceiver = _toReceiver;
53 | _state.nonce = _nonce;
54 | _state.lastUpdate = block.number;
55 | }
56 |
57 | function canClose(
58 | State storage _self,
59 | uint256 _toSender,
60 | uint256 _toReceiver,
61 | bytes _senderSig,
62 | bytes _receiverSig
63 | ) public view returns (bool)
64 | {
65 | // FIXME Add a secret to prevent replay attach
66 | bytes32 hash = LibCommon.recoveryDigest(keccak256("c", _toSender, _toReceiver));
67 | bool isSigned = _self.multisig.isUnanimous(hash, _senderSig, _receiverSig);
68 | return isSettling(_self) && isSigned;
69 | }
70 |
71 | function isSettling(State storage _state) public view returns(bool) {
72 | return block.number <= _state.lastUpdate + _state.settlementPeriod;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/contracts/LibCommon.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 |
4 | library LibCommon {
5 |
6 | // TODO Move it to the separate library
7 | struct ShareStateState {
8 | address owner;
9 | uint256 nonce;
10 | bytes32 merkleRoot;
11 | uint256 updatePeriod;
12 | uint256 lastUpdate;
13 | }
14 |
15 | function recoveryDigest(bytes32 hash) public pure returns(bytes32) {
16 | bytes memory prefix = "\x19Ethereum Signed Message:\n32";
17 | return keccak256(prefix, hash);
18 | }
19 |
20 | function executeHashCalc(address thisAddress, address destination, uint256 value, bytes data, uint256 nonce) public pure returns(bytes32) {
21 | return recoveryDigest(keccak256(thisAddress, destination, value, data, nonce));
22 | }
23 |
24 | function executeHashCheck(bytes32 hash, bytes senderSig, bytes receiverSig, address sender, address receiver) public pure returns(bool) {
25 | return sender == recover(hash, senderSig) && receiver == recover(hash, receiverSig);
26 | }
27 |
28 | /**
29 | * @dev [code from zeppelin-solidity] Recover signer address from a message by using his signature
30 | * @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
31 | * @param sig bytes signature, the signature is generated using web3.eth.sign()
32 | */
33 | function recover(bytes32 hash, bytes sig) public pure returns (address) {
34 | bytes32 r;
35 | bytes32 s;
36 | uint8 v;
37 |
38 | //Check the signature length
39 | if (sig.length != 65) {
40 | return (address(0));
41 | }
42 |
43 | // Divide the signature in r, s and v variables
44 | assembly { // solium-disable-line security/no-inline-assembly
45 | r := mload(add(sig, 32))
46 | s := mload(add(sig, 64))
47 | v := byte(0, mload(add(sig, 96)))
48 | }
49 |
50 | // Version of signature should be 27 or 28, but 0 and 1 are also possible versions
51 | if (v < 27) {
52 | v += 27;
53 | }
54 |
55 | // If the version is correct return the signer address
56 | if (v != 27 && v != 28) {
57 | return (address(0));
58 | } else {
59 | return ecrecover(hash, v, r, s);
60 | }
61 | }
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/contracts/LibLineup.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./Multisig.sol";
4 | import "./LibCommon.sol";
5 |
6 |
7 | library LibLineup {
8 | struct State {
9 | uint256 nonce;
10 | bytes32 merkleRoot;
11 | uint256 updatePeriod;
12 | uint256 lastUpdate;
13 | Multisig multisig;
14 | }
15 |
16 | event Trace(bool a);
17 | function update(State storage _self, uint256 _nonce, bytes32 _merkleRoot, bytes _senderSig, bytes _receiverSig) internal {
18 | var hash = keccak256(_merkleRoot, _nonce);
19 | require(_self.multisig.isUnanimous(LibCommon.recoveryDigest(hash), _senderSig, _receiverSig));
20 | require(_nonce > _self.nonce);
21 | require(block.number <= _self.lastUpdate + _self.updatePeriod);
22 |
23 | _self.merkleRoot = _merkleRoot;
24 | _self.nonce = _nonce;
25 | _self.lastUpdate = block.number;
26 | }
27 |
28 | function isContained(State storage _self, bytes _proof, bytes32 _hashlock) public view returns (bool) {
29 | bytes32 proofElement;
30 | bytes32 cursor = _hashlock;
31 | bool result = false;
32 |
33 | if (block.number >= _self.lastUpdate + _self.updatePeriod) {
34 | for (uint256 i = 32; i <= _proof.length; i += 32) {
35 | assembly { proofElement := mload(add(_proof, i)) } // solium-disable-line security/no-inline-assembly
36 |
37 | if (cursor < proofElement) {
38 | cursor = keccak256(cursor, proofElement);
39 | } else {
40 | cursor = keccak256(proofElement, cursor);
41 | }
42 | }
43 | result = cursor == _self.merkleRoot;
44 | }
45 | return result;
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/contracts/LibMultisig.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "zeppelin-solidity/contracts/math/SafeMath.sol";
4 | import "./LibCommon.sol";
5 |
6 |
7 | library LibMultisig {
8 | using SafeMath for uint256;
9 |
10 | struct State {
11 | address sender;
12 | address receiver;
13 | uint256 nonce;
14 | }
15 |
16 | function callDigest(
17 | address _self,
18 | address _destination,
19 | uint256 _value,
20 | bytes _data,
21 | uint256 _nonce
22 | ) public pure returns(bytes32)
23 | {
24 | return LibCommon.recoveryDigest(keccak256(_self, _destination, _value, _data, _nonce));
25 | }
26 |
27 | function delegateDigest(
28 | address _self,
29 | address _destination,
30 | bytes _data,
31 | uint256 _nonce
32 | ) public pure returns(bytes32)
33 | {
34 | return LibCommon.recoveryDigest(keccak256(_self, _destination, _data, _nonce));
35 | }
36 |
37 | function isUnanimous(
38 | State storage state,
39 | bytes32 hash,
40 | bytes senderSig,
41 | bytes receiverSig
42 | ) public view returns(bool)
43 | {
44 | return state.sender == LibCommon.recover(hash, senderSig) &&
45 | state.receiver == LibCommon.recover(hash, receiverSig);
46 | }
47 |
48 | function doCall(
49 | address self,
50 | address destination,
51 | uint256 value,
52 | bytes data,
53 | bytes senderSig,
54 | bytes receiverSig,
55 | State storage state
56 | ) public
57 | {
58 | require(isUnanimous(state, callDigest(self, destination, value, data, state.nonce), senderSig, receiverSig));
59 | state.nonce = state.nonce.add(1);
60 | }
61 |
62 | function doDelegatecall(
63 | address self,
64 | address destination,
65 | bytes data,
66 | bytes senderSig,
67 | bytes receiverSig,
68 | State storage state
69 | ) public
70 | {
71 | require(isUnanimous(state, delegateDigest(self, destination, data, state.nonce), senderSig, receiverSig));
72 | state.nonce = state.nonce.add(1);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/contracts/Lineup.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./LibLineup.sol";
4 | import "./Multisig.sol";
5 |
6 |
7 | // Optimisation Idea: Use shared Lineup.
8 | contract Lineup {
9 | LibLineup.State public state;
10 |
11 | event Trace(bytes32 a);
12 | function Lineup(bytes32 _merkleRoot, uint256 _updatePeriod, address _multisig) public {
13 | state.merkleRoot = _merkleRoot;
14 | state.updatePeriod = _updatePeriod;
15 | state.lastUpdate = block.number;
16 | state.multisig = Multisig(_multisig);
17 | }
18 |
19 | function update(uint256 _nonce, bytes32 _merkleRoot, bytes _senderSig, bytes _receiverSig) external {
20 | LibLineup.update(state, _nonce, _merkleRoot, _senderSig, _receiverSig);
21 | }
22 |
23 | function isContained(bytes proof, bytes32 hashlock) public view returns (bool) {
24 | return LibLineup.isContained(state, proof, hashlock);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/contracts/Migrations.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 |
4 | contract Migrations {
5 | address public owner;
6 | uint public last_completed_migration;
7 |
8 | modifier restricted() {
9 | if (msg.sender == owner)
10 | _;
11 | }
12 |
13 | function Migrations() public {
14 | owner = msg.sender;
15 | }
16 |
17 | function setCompleted(uint completed) public restricted {
18 | last_completed_migration = completed;
19 | }
20 |
21 | function upgrade(address newAddress) public restricted {
22 | Migrations upgraded = Migrations(newAddress);
23 | upgraded.setCompleted(last_completed_migration);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/contracts/Multisig.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./LibMultisig.sol";
4 |
5 |
6 | contract Multisig {
7 | LibMultisig.State public state;
8 |
9 | function Multisig(address _sender, address _receiver) public {
10 | state.sender = _sender;
11 | state.receiver = _receiver;
12 | }
13 |
14 | function () payable external {}
15 |
16 | function doCall(
17 | address destination,
18 | uint256 value,
19 | bytes data,
20 | bytes senderSig,
21 | bytes receiverSig
22 | ) external
23 | {
24 | LibMultisig.doCall(
25 | address(this),
26 | destination,
27 | value,
28 | data,
29 | senderSig,
30 | receiverSig,
31 | state
32 | );
33 | require(destination.call.value(value)(data)); // solium-disable-line security/no-call-value
34 | }
35 |
36 | function doDelegate(
37 | address destination,
38 | bytes data,
39 | bytes senderSig,
40 | bytes receiverSig
41 | ) external
42 | {
43 | LibMultisig.doDelegatecall(
44 | address(this),
45 | destination,
46 | data,
47 | senderSig,
48 | receiverSig,
49 | state
50 | );
51 | require(destination.delegatecall(data)); // solium-disable-line security/no-low-level-calls
52 | }
53 |
54 | function isUnanimous(bytes32 _hash, bytes _senderSig, bytes _receiverSig) public view returns(bool) {
55 | return LibMultisig.isUnanimous(state, _hash, _senderSig, _receiverSig);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/contracts/Proxy.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./PublicRegistry.sol";
4 |
5 |
6 | // @title Proxy call via counterfactual address.
7 | // @dev This may really suck. TODO Check output types
8 | contract Proxy {
9 | function doCall(address _registry, bytes32 _destination, uint256 value, bytes data) public {
10 | address destination = findDestination(_registry, _destination);
11 | require(destination.call.value(value)(data)); // solium-disable-line security/no-call-value
12 | }
13 |
14 | function doDelegateCall(address _registry, bytes32 _destination, bytes data) public {
15 | address destination = findDestination(_registry, _destination);
16 | require(destination.delegatecall(data)); // solium-disable-line security/no-low-level-calls
17 | }
18 |
19 | function findDestination(address _registry, bytes32 _destination) internal view returns (address destination) {
20 | PublicRegistry registry = PublicRegistry(_registry);
21 | destination = registry.resolve(_destination);
22 | require(destination != address(0x0));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/contracts/PublicRegistry.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 |
4 | contract PublicRegistry {
5 | mapping (bytes32 => address) _contracts;
6 |
7 | event DidDeploy(bytes32 indexed id, address indexed deployed, address indexed owner);
8 |
9 | function deploy(bytes _code, bytes32 nonce) public {
10 | address realAddress;
11 | // solium-disable-next-line security/no-inline-assembly
12 | assembly {
13 | realAddress := create(0, add(_code, 0x20), mload(_code))
14 | }
15 | bytes32 cfAddress = counterfactualAddress(_code, nonce);
16 | _contracts[cfAddress] = realAddress;
17 |
18 | DidDeploy(cfAddress, realAddress, msg.sender);
19 | }
20 |
21 | function resolve(bytes32 cfAddress) public view returns (address) {
22 | return _contracts[cfAddress];
23 | }
24 |
25 | function counterfactualAddress(bytes _code, bytes32 nonce) public pure returns (bytes32) {
26 | return keccak256(_code, nonce);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/contracts/TestContract.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
4 |
5 |
6 | contract TestContract {
7 | uint256 public nonce;
8 |
9 | event DidUpdate(uint256 nonce);
10 |
11 | function TestContract(uint256 _nonce) public {
12 | nonce = _nonce;
13 | }
14 |
15 | function () payable public {}
16 |
17 | function updateNonce(uint256 _nonce) payable public {
18 | nonce = _nonce;
19 | DidUpdate(nonce);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/contracts/TestDelegatecall.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 |
4 | contract TestDelegatecall {
5 | event Executed();
6 | function execute(uint256 amountA, uint256 amountB) public {
7 | require(amountA > amountB);
8 | Executed();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/contracts/TestToken.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
4 | import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol";
5 |
6 |
7 | contract TestToken is StandardToken, MintableToken {
8 | function TestToken() public {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/contracts/TransferToken.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.19;
2 |
3 | import "./PublicRegistry.sol";
4 | import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
5 |
6 |
7 | // @title Proxy call via counterfactual address.
8 | contract TransferToken {
9 | function execute(address _registry, address _token, bytes32 _destination, uint256 amount) public {
10 | PublicRegistry registry = PublicRegistry(_registry);
11 | address destination = registry.resolve(_destination);
12 | require(destination != address(0x0));
13 |
14 | StandardToken token = StandardToken(_token);
15 | require(token.transfer(destination, amount));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/doc/Contract.md:
--------------------------------------------------------------------------------
1 | # How the contract is supposed to work
2 |
3 | Broker contract manages uni-directional payment channels. A payment channel lifecycle is depicted below:
4 |
5 | 
6 |
7 | **No Channel → Open:**
8 |
9 | Initiator, that is a sender, deposits money to the contract, calls `createChannel` method of the contract.
10 |
11 | **Open:**
12 |
13 | While the channel is open, the sender and the receiver (the parties), are free to exchange with IOU notes. Validity of IOU note is checked in `canClaim` contract method.
14 |
15 | **Open → Settled:**
16 |
17 | Settlement process that is initiated by the receiver. It provides IOU note to `settle` method. That triggers distribution of the deposited money to the parties, according to presented IOU note.
18 |
19 | **Open → Settling → Settled:**
20 |
21 | Settlement process that is initiated by the sender. It is driven by an assumption of an unresponsive receiver. The sender starts the settlement process by calling `startSettle` method, provides the expected payout to the receiver. If the latter does not respond indeed, the sender could finish the process by calling `finishSettle`. There is no constraint the payout amount provided by the sender. It could be zero. To mitigate that, `createChannel` accepts `settlementPeriod` param that specifies how long the channel waits for the receiver responds. The receiver is free not to accept payment and provide service, if she finds that period inadequate.
22 |
23 | **Settled → Closed:**
24 |
25 | A _settled_ channel is not _closed_. It could probably have money attached to it. Also, closed channel no longer occupies space in the list of payment channels.
26 |
27 | Either sender, or receiver, or _contract owner_ is free to call `close` to finally close the channel. That involves moving remaining money to the sender, and removing the channel from the list maintained by the contract.
28 |
--------------------------------------------------------------------------------
/doc/channel-states.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/development/verification.md:
--------------------------------------------------------------------------------
1 | # Contract verification
2 |
3 | To display contract source code on etherscan you need to verify it:
4 | 1. [ropsten](https://ropsten.etherscan.io/verifyContract)
5 | 2. [kovan](https://kovan.etherscan.io/verifyContract)
6 |
7 | Use [truffle-flattener](https://github.com/alcuadrado/truffle-flattener) to get flattened solidity contract code.
8 |
9 | Code example for constructor arguments encoding into ABI:
10 |
11 | ```js
12 | var abi = require('ethereumjs-abi')
13 | var parameterTypes = ["uint32"];
14 | var parameterValues = [42]
15 | var encoded = abi.rawEncode(parameterTypes, parameterValues);
16 | console.log(encoded.toString('hex'));
17 | ```
18 |
--------------------------------------------------------------------------------
/migrations/10_deploy_LibLineup.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const LibLineup = artifacts.require('LibLineup')
4 | const LibMultisig = artifacts.require('LibMultisig')
5 | const LibCommon = artifacts.require('LibCommon')
6 |
7 | module.exports = function (deployer: Deployer) {
8 | LibLineup.link(LibMultisig)
9 | LibLineup.link(LibCommon)
10 | return deployer.deploy(LibLineup)
11 | }
12 |
--------------------------------------------------------------------------------
/migrations/1_initial_migration.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const Migrations = artifacts.require('./Migrations.sol')
4 |
5 | module.exports = function (deployer: Deployer) {
6 | return deployer.deploy(Migrations)
7 | }
8 |
--------------------------------------------------------------------------------
/migrations/3_deploy_LibBidirectional.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const LibCommon = artifacts.require('LibCommon')
4 | const LibBidirectional = artifacts.require('LibBidirectional.sol')
5 |
6 | module.exports = function (deployer: Deployer) {
7 | return deployer.deploy(LibCommon).then(() => {
8 | return deployer.link(LibCommon, LibBidirectional)
9 | }).then(() => {
10 | return deployer.deploy(LibBidirectional)
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/migrations/4_deploy_registry.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const PublicRegistry = artifacts.require('PublicRegistry')
4 |
5 | module.exports = function (deployer: Deployer) {
6 | return deployer.deploy(PublicRegistry)
7 | }
8 |
--------------------------------------------------------------------------------
/migrations/5_deploy_transfer_token.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const TransferToken = artifacts.require('TransferToken')
4 |
5 | module.exports = function (deployer: Deployer) {
6 | return deployer.deploy(TransferToken)
7 | }
8 |
--------------------------------------------------------------------------------
/migrations/6_deploy_multisigLibrary.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const ECRecovery = artifacts.require('ECRecovery.sol')
4 | const LibMultisig = artifacts.require('LibMultisig.sol')
5 | const LibCommon = artifacts.require('LibCommon.sol')
6 |
7 | module.exports = function (deployer: Deployer) {
8 | return deployer.deploy(ECRecovery).then(() => {
9 | return deployer.link(ECRecovery, LibMultisig)
10 | }).then(() => {
11 | return deployer.deploy(LibCommon)
12 | }).then(() => {
13 | return deployer.link(LibCommon, LibMultisig)
14 | }).then(() => {
15 | return deployer.deploy(LibMultisig)
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/migrations/7_deploy_conditionalCallLibrary.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const Conditional = artifacts.require('Conditional')
4 |
5 | module.exports = function (deployer: Deployer) {
6 | return deployer.deploy(Conditional)
7 | }
8 |
--------------------------------------------------------------------------------
/migrations/8_deploy_publicRegistryLibrary.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const PublicRegistry = artifacts.require('PublicRegistry')
4 | const PublicRegistryLibrary = artifacts.require('PublicRegistryLibrary')
5 |
6 | module.exports = function (deployer: Deployer) {
7 | return deployer.deploy(PublicRegistryLibrary).then(() => {
8 | return deployer.link(PublicRegistryLibrary, PublicRegistry)
9 | }).then(() => {
10 | return deployer.deploy(PublicRegistry)
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/migrations/9_deploy_proxy.ts:
--------------------------------------------------------------------------------
1 | import * as Deployer from 'truffle-deployer'
2 |
3 | const Proxy = artifacts.require('Proxy')
4 |
5 | module.exports = function (deployer: Deployer) {
6 | return deployer.deploy(Proxy)
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@machinomy/contracts",
3 | "version": "4.0.8",
4 | "description": "Machinomy contracts managed by Truffle",
5 | "license": "AGPL-3.0-or-later",
6 | "homepage": "https://github.com/machinomy/machinomy-contracts#readme",
7 | "bugs": {
8 | "url": "https://github.com/machinomy/machinomy-contracts/issues"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/machinomy/machinomy-contracts.git"
13 | },
14 | "main": "dist/src/index.js",
15 | "types": "dist/src/index.d.ts",
16 | "files": [
17 | "dist/"
18 | ],
19 | "directories": {
20 | "test": "test"
21 | },
22 | "config": {
23 | "truffleTests": "test/**/*.test.js",
24 | "wrappedArtifacts": "build/contracts/@(Test*|Bidirectional|Conditional|DistributeEth|DistributeToken|PublicRegistry|Lineup|TestToken|Multisig|TransferToken|TestContract|Proxy|ECRecovery).json"
25 | },
26 | "natspec": {
27 | "whitelist": [
28 | "Unidirectional"
29 | ]
30 | },
31 | "scripts": {
32 | "prepublish": "yarn lint && yarn build --outDir dist/ && cp -r build/contracts dist/build",
33 | "lint": "yarn lint:solidity && yarn lint:typescript",
34 | "lint:solidity": "solium -d contracts/",
35 | "lint:typescript": "tslint --format stylish --project .",
36 | "build": "yarn support:wrap && tsc --project tsconfig.json",
37 | "support:wrap": "yarn truffle:compile && yarn support:build && node support/wrap $npm_package_config_wrappedArtifacts --output $npm_package_config_wrappers_dir",
38 | "support:build": "tsc --project support/tsconfig.json",
39 | "truffle:test": "run-with-testrpc 'truffle test $npm_package_config_truffleTests'",
40 | "truffle:compile": "truffle compile",
41 | "truffle:migrate": "truffle migrate",
42 | "test": "yarn build && yarn truffle:test",
43 | "doc:natspec": "node support/natspec",
44 | "coverage": "./node_modules/.bin/solidity-coverage"
45 | },
46 | "author": "Sergey Ukustov ",
49 | "David Wolever "
50 | ],
51 | "devDependencies": {
52 | "@machinomy/types-ethereumjs-units": "^0.0.2",
53 | "@machinomy/types-truffle": "^0.0.2",
54 | "@machinomy/types-truffle-artifactor": "^0.0.1",
55 | "@machinomy/types-truffle-compile": "^0.0.2",
56 | "@machinomy/types-truffle-config": "^0.0.2",
57 | "@machinomy/types-truffle-contract-sources": "^0.0.1",
58 | "@machinomy/types-truffle-resolver": "^0.0.1",
59 | "@machinomy/types-web3": "^0.0.7",
60 | "@types/bignumber.js": "4.0.2",
61 | "@types/chai": "^4.0.5",
62 | "@types/chai-as-promised": "^7.1.0",
63 | "@types/mkdirp": "^0.5.2",
64 | "@types/node": "^8.0.28",
65 | "@types/yargs": "^10.0.1",
66 | "chai": "^4.1.2",
67 | "chai-as-promised": "^7.1.1",
68 | "coveralls": "^3.0.0",
69 | "ethereumjs-abi": "https://github.com/ethereumjs/ethereumjs-abi",
70 | "ethereumjs-units": "^0.2.0",
71 | "handlebars": "^4.0.11",
72 | "mkdirp": "^0.5.1",
73 | "run-with-testrpc": "^0.2.1",
74 | "solidity-coverage": "^0.4.9",
75 | "solium": "^1.0.11",
76 | "truffle": "^4.1.0",
77 | "truffle-flattener": "^1.2.3",
78 | "tslint": "^5.7.0",
79 | "tslint-config-standard": "^6.0.1",
80 | "types-ethereumjs-util": "https://github.com/machinomy/types-ethereumjs-util",
81 | "typescript": "^2.8.3",
82 | "yargs": "^10.1.1",
83 | "zeppelin-solidity": "^1.6.0"
84 | },
85 | "dependencies": {
86 | "bignumber.js": "4.1.0",
87 | "buffer": "^5.0.7",
88 | "ethereumjs-util": "^5.1.5",
89 | "keccak": "^1.4.0",
90 | "truffle-contract": "^3.0.0",
91 | "utf8": "^3.0.0",
92 | "web3": "^0.20.1"
93 | },
94 | "resolutions": {
95 | "upath": "^1.0.5",
96 | "sha3": "^1.2.1"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/MerkleTree.ts:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'buffer'
2 | import * as util from 'ethereumjs-util'
3 |
4 | function isHash (buffer: Buffer): boolean {
5 | return buffer.length === 32 && Buffer.isBuffer(buffer)
6 | }
7 |
8 | function isNotHash (buffer: Buffer): boolean {
9 | return !isHash(buffer)
10 | }
11 |
12 | function combinedHash (first: Buffer, second: Buffer): Buffer {
13 | if (!second) {
14 | return first
15 | }
16 | if (!first) {
17 | return second
18 | }
19 | let sorted = Buffer.concat([first, second].sort(Buffer.compare))
20 | return util.sha3(sorted)
21 | }
22 |
23 | function getNextLayer (elements: Array): Array {
24 | return elements.reduce>((layer, element, index, arr) => {
25 | if (index % 2 === 0) {
26 | layer.push(combinedHash(element, arr[index + 1]))
27 | }
28 | return layer
29 | }, [])
30 | }
31 |
32 | function getLayers (elements: Array): Array> {
33 | if (elements.length === 0) {
34 | return [[Buffer.from('')]]
35 | }
36 | let layers = []
37 | layers.push(elements)
38 | while (layers[layers.length - 1].length > 1) {
39 | layers.push(getNextLayer(layers[layers.length - 1]))
40 | }
41 | return layers
42 | }
43 |
44 | function getPair (index: number, layer: Array): Buffer|null {
45 | let pairIndex = index % 2 ? index - 1 : index + 1
46 | if (pairIndex < layer.length) {
47 | return layer[pairIndex]
48 | } else {
49 | return null
50 | }
51 | }
52 |
53 | function deduplicate (buffers: Array): Array {
54 | return buffers.filter((buffer, i) => {
55 | return buffers.findIndex(e => e.equals(buffer)) === i
56 | })
57 | }
58 |
59 | export default class MerkleTree {
60 | elements: Array
61 | layers: Array>
62 | _root?: Buffer
63 |
64 | get root (): Buffer {
65 | if (!this._root) {
66 | this._root = this.layers[this.layers.length - 1][0]
67 | }
68 | return this._root
69 | }
70 |
71 | constructor (elements: Array) {
72 | // check buffers
73 | if (elements.some(isNotHash)) {
74 | throw new Error('elements must be 32 byte buffers')
75 | }
76 |
77 | this.elements = deduplicate(elements)
78 | this.elements.sort(Buffer.compare)
79 |
80 | this.layers = getLayers(this.elements)
81 | }
82 |
83 | verify (proof: Array, element: Buffer): boolean {
84 | return this.root.equals(proof.reduce((hash, pair) => combinedHash(hash, pair), element))
85 | }
86 |
87 | proof (element: Buffer): Array {
88 | let index = this.elements.findIndex(e => e.equals(element))
89 | if (index === -1) {
90 | throw new Error('element not found in merkle tree')
91 | }
92 |
93 | return this.layers.reduce((proof, layer) => {
94 | let pair = getPair(index, layer)
95 | if (pair) {
96 | proof.push(pair)
97 | }
98 | index = Math.floor(index / 2)
99 | return proof
100 | }, [])
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Units.ts:
--------------------------------------------------------------------------------
1 | import * as ethUnits from 'ethereumjs-units'
2 | import * as BigNumber from 'bignumber.js'
3 |
4 | namespace Units {
5 | export function convert (value: number | BigNumber.BigNumber, fromUnit: string, toUnit: string): BigNumber.BigNumber {
6 | let stringNumber = ethUnits.convert(value.toString(), fromUnit, toUnit)
7 | return new BigNumber.BigNumber(stringNumber)
8 | }
9 | }
10 |
11 | export default Units
12 |
--------------------------------------------------------------------------------
/src/globals.d.ts:
--------------------------------------------------------------------------------
1 | // Truffle injects the following into the global scope
2 | declare var before: any
3 | declare var beforeEach: any
4 | declare var describe: any
5 | declare var xdescribe: any
6 | declare var it: any
7 | declare var specify: any
8 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import PublicRegistry from '../build/wrappers/PublicRegistry'
2 | import Multisig from '../build/wrappers/Multisig'
3 | import Proxy from '../build/wrappers/Proxy'
4 | import ECRecovery from '../build/wrappers/ECRecovery'
5 | import TransferToken from '../build/wrappers/TransferToken'
6 | import DistributeEth from '../build/wrappers/DistributeEth'
7 | import DistributeToken from '../build/wrappers/DistributeToken'
8 | import Lineup from '../build/wrappers/Lineup'
9 | import Conditional from '../build/wrappers/Conditional'
10 | import Bidirectional from '../build/wrappers/Bidirectional'
11 | import TestContract from '../build/wrappers/TestContract'
12 | import TestToken from '../build/wrappers/TestToken'
13 | import * as ethUtil from 'ethereumjs-util'
14 |
15 | export {
16 | PublicRegistry,
17 | Multisig,
18 | Proxy,
19 | ECRecovery,
20 | TransferToken,
21 | DistributeEth,
22 | DistributeToken,
23 | Conditional,
24 | Lineup,
25 | Bidirectional,
26 | TestContract,
27 | TestToken
28 | }
29 |
30 | export function randomId (digits: number = 3) {
31 | const datePart = new Date().getTime() * Math.pow(10, digits)
32 | const extraPart = Math.floor(Math.random() * Math.pow(10, digits)) // 3 random digits
33 | return datePart + extraPart // 16 digits
34 | }
35 |
36 | export function channelId (sender: string, receiver: string): string {
37 | let random = randomId()
38 | let buffer = ethUtil.sha3(sender + receiver + random)
39 | return ethUtil.bufferToHex(buffer)
40 | }
41 |
--------------------------------------------------------------------------------
/support/natspec/NatSpec.ts:
--------------------------------------------------------------------------------
1 | import * as Web3 from 'web3'
2 | import * as util from 'ethereumjs-util'
3 | import { CompiledResult, ContractEntry } from './compiler'
4 |
5 | enum AbiType {
6 | Function = 'function',
7 | Constructor = 'constructor',
8 | Event = 'event',
9 | Fallback = 'fallback'
10 | }
11 |
12 | export namespace NatSpec {
13 | export type Container = {[name: string]: Entry}
14 |
15 | export interface Entry {
16 | author?: string
17 | title?: string
18 | fileName: string
19 | name?: string
20 | abi?: Web3.ContractAbi
21 | abiDocs: any
22 | }
23 |
24 | function functionSignature (signature: string): string {
25 | return util.sha3(signature).toString('hex').substr(0, 8)
26 | }
27 |
28 | function functionDefinition (entry: ContractEntry, definition: Web3.MethodAbi): any {
29 | let inputParams = definition.inputs
30 | let name = definition.name
31 | let signature = `${name}(${inputParams.map(i => i.type).join(',')})`
32 | let devDocs = entry.devdoc.methods[signature] || {}
33 | let userDocs = entry.userdoc.methods[signature] || {}
34 |
35 | let params = devDocs.params || {}
36 | let inputs = inputParams.map(param => ({ ...param, description: params[param.name] }))
37 | delete devDocs.params
38 |
39 | let outputParams = {}
40 | let outputs = []
41 | try {
42 | outputParams = JSON.parse(devDocs.return)
43 | } catch (e) {
44 | try {
45 | const split = devDocs.return.split(' ')
46 | const name = split.shift()
47 | outputParams = { [name]: split.join(' ') }
48 | } catch (e2) { /* */ }
49 | }
50 | try {
51 | outputs = definition.outputs.map(param => ({ ...param, description: outputParams[param.name] }))
52 | } catch (e) { /* */ }
53 |
54 | return {
55 | ...definition,
56 | ...devDocs,
57 | ...userDocs,
58 | inputs,
59 | outputs,
60 | signature,
61 | signatureHash: signature && functionSignature(signature)
62 | }
63 | }
64 |
65 | function eventDefinition (entry: ContractEntry, definition: Web3.EventAbi): any {
66 | let inputParams = definition.inputs
67 | let name = definition.name
68 | let signature = `${name}(${inputParams.map(i => i.type).join(',')})`
69 | let devDocs = entry.devdoc.methods[signature] || {}
70 | let userDocs = entry.userdoc.methods[signature] || {}
71 |
72 | let params = devDocs.params || {}
73 | let inputs = inputParams.map(param => ({ ...param, description: params[param.name] }))
74 | delete devDocs.params
75 |
76 | return {
77 | ...definition,
78 | ...devDocs,
79 | ...userDocs,
80 | inputs,
81 | signature,
82 | signatureHash: signature && functionSignature(signature)
83 | }
84 | }
85 |
86 |
87 | function buildDefinition (entry: ContractEntry, definition: Web3.AbiDefinition): any {
88 | switch (definition.type) {
89 | case AbiType.Function:
90 | return functionDefinition(entry, definition)
91 | case AbiType.Event:
92 | return eventDefinition(entry, definition)
93 | case AbiType.Constructor:
94 | return {
95 | ...definition,
96 | inputs: definition.inputs
97 | }
98 | case AbiType.Fallback:
99 | return {
100 | ...definition,
101 | inputs: []
102 | }
103 | default:
104 | throw new Error(`Got unexpected definition ${definition.type}`)
105 | }
106 | }
107 |
108 | function buildEntry (fileName: string, name: string, entry: ContractEntry): Entry {
109 | return {
110 | fileName: fileName,
111 | author: entry.devdoc.author,
112 | title: entry.devdoc.title,
113 | name: name,
114 | abiDocs: entry.abi.map(definition => buildDefinition(entry, definition)),
115 | abi: entry.abi
116 | }
117 | }
118 |
119 | export async function build (input: CompiledResult, whitelist: Array = []): Promise {
120 | let contracts = input.contracts
121 | let result: Container = {}
122 | Object.keys(contracts).forEach(fileName => {
123 | let contractEntry = contracts[fileName]
124 | let name = Object.keys(contractEntry)[0]
125 | if (whitelist.length > 0 && whitelist.includes(name)) {
126 | result[name] = buildEntry(fileName, name, contractEntry[name])
127 | }
128 | })
129 |
130 | return result
131 | }
132 | }
133 |
134 | export default NatSpec
135 |
--------------------------------------------------------------------------------
/support/natspec/compiler.ts:
--------------------------------------------------------------------------------
1 | import * as solc from 'solc'
2 | import * as Web3 from 'web3'
3 | import { FullText } from './sources'
4 |
5 | export interface MethodDevDoc {
6 | details?: string
7 | params?: {[method: string]: string}
8 | return?: any
9 | }
10 |
11 | export interface DevDoc {
12 | author?: string
13 | title?: string
14 | name?: string
15 | methods?: {[signature: string]: MethodDevDoc}
16 | }
17 |
18 | export interface AbiDocDefinition {
19 |
20 | }
21 |
22 | export interface UserDoc {
23 | methods?: {[signature: string]: any}
24 | }
25 |
26 | export interface ContractEntry {
27 | devdoc?: DevDoc
28 | userdoc?: UserDoc
29 | abi?: Web3.ContractAbi
30 | abiDocs?: AbiDocDefinition[]
31 | }
32 |
33 | export interface CompiledResult {
34 | contracts: {
35 | [fileName: string]: {
36 | [name: string]: ContractEntry
37 | }
38 | }
39 | }
40 |
41 | export async function doc (fullText: FullText): Promise {
42 | const standardInput = {
43 | language: 'Solidity',
44 | sources: {},
45 | settings: {
46 | outputSelection: {
47 | '*': {
48 | '*': [
49 | 'devdoc',
50 | 'userdoc',
51 | 'abi'
52 | ]
53 | }
54 | }
55 | }
56 | }
57 | for (let name in fullText) {
58 | standardInput.sources[name] = {
59 | content: fullText[name]
60 | }
61 | }
62 | let result = solc.compileStandard(JSON.stringify(standardInput))
63 | return JSON.parse(result)as CompiledResult
64 | }
65 |
--------------------------------------------------------------------------------
/support/natspec/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import * as fs from 'fs'
4 | import * as path from 'path'
5 | import * as sources from './sources'
6 | import * as compiler from './compiler'
7 | import NatSpec from './NatSpec'
8 |
9 | async function write (fileName: string, data: string): Promise {
10 | return new Promise((resolve, reject) => {
11 | fs.writeFile(fileName, data, error => {
12 | error ? reject(error) : resolve()
13 | })
14 | })
15 | }
16 |
17 | async function main () {
18 | let packageJsonFilename = path.resolve(__dirname, '../../package.json')
19 | let packageJson = JSON.parse(fs.readFileSync(packageJsonFilename).toString())
20 | let whitelist = packageJson.natspec.whitelist
21 | let config = await sources.currentConfig()
22 | let fullText = await sources.requiredSources(config)
23 | let solcOutput = await compiler.doc(fullText)
24 | let natSpec = await NatSpec.build(solcOutput, whitelist)
25 | for (let name in natSpec) {
26 | let documentation = natSpec[name]
27 | let outputFile = path.join(config.build_directory, 'doc', `${name}.json`)
28 | let asString = JSON.stringify(documentation, null, 4)
29 | await write(outputFile, asString)
30 | }
31 | }
32 |
33 | main().catch(err => {
34 | console.error(err)
35 | process.exit(1)
36 | })
37 |
--------------------------------------------------------------------------------
/support/natspec/sources.ts:
--------------------------------------------------------------------------------
1 | import * as truffleContractSources from 'truffle-contract-sources'
2 | import * as Profiler from 'truffle-compile/profiler'
3 | import * as Config from 'truffle-config'
4 | import * as Resolver from 'truffle-resolver'
5 | import * as Artifactor from 'truffle-artifactor'
6 |
7 | export type FullText = {[name: string]: string}
8 |
9 | /**
10 | * Configuration of the current Truffle project.
11 | */
12 | export async function currentConfig (): Promise {
13 | let config = Config.default()
14 | config.resolver = new Resolver(config)
15 | config.artifactor = new Artifactor()
16 | config.paths = await contractPaths(config)
17 | config.base_path = config.contracts_directory
18 | return config
19 | }
20 |
21 | /**
22 | * Subset of configuration needed to [[contractPaths]] function.
23 | */
24 | interface ContractFilesConfig {
25 | contracts_directory: string
26 | }
27 |
28 | /**
29 | * Paths to the contracts managed by Truffle.
30 | */
31 | export async function contractPaths (config: ContractFilesConfig): Promise> {
32 | return new Promise>((resolve, reject) => {
33 | truffleContractSources(config.contracts_directory, (err, files) => {
34 | err ? reject(err) : resolve(files)
35 | })
36 | })
37 | }
38 |
39 | /**
40 | * Subset of configuration needed to [[requiredSources]] function.
41 | */
42 | interface RequiredSourcesConfig {
43 | base_path: string
44 | resolver: any
45 | paths: Array
46 | }
47 |
48 | /**
49 | * Sources of contracts along with the dependencies, for the current Truffle project.
50 | */
51 | export async function requiredSources (config: RequiredSourcesConfig): Promise {
52 | return new Promise((resolve, reject) => {
53 | Profiler.required_sources(config, (err, sources) => {
54 | err ? reject(err) : resolve(sources)
55 | })
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/support/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "lib": [ "es2017" ],
6 | "typeRoots": [
7 | "../node_modules/@types",
8 | "../node_modules/@machinomy"
9 | ]
10 | },
11 | "include": [
12 | "../node_modules/**/*/index.d.ts",
13 | "../types/**/index.d.ts",
14 | "./**/*.ts"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/support/wrap/Context.ts:
--------------------------------------------------------------------------------
1 | import * as Web3 from 'web3'
2 |
3 | export interface MethodAbi extends Web3.MethodAbi {
4 | singleReturnValue: boolean
5 | }
6 |
7 | export default interface Context {
8 | artifact: string
9 | contractName: string
10 | relativeArtifactPath: string
11 | getters: Array
12 | functions: Array
13 | events: Array
14 | }
15 |
--------------------------------------------------------------------------------
/support/wrap/ContractTemplate.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as fs from 'fs'
3 | import * as Web3 from 'web3'
4 | import * as Handlebars from 'handlebars'
5 | import Context, { MethodAbi } from './Context'
6 | import * as helpers from './helpers'
7 |
8 | const ABI_TYPE_FUNCTION = 'function'
9 | const ABI_TYPE_EVENT = 'event'
10 |
11 | function isAbiFunction (abi: Web3.AbiDefinition): abi is Web3.MethodAbi {
12 | return abi.type === ABI_TYPE_FUNCTION
13 | }
14 |
15 | function isAbiEvent (abi: Web3.AbiDefinition): abi is Web3.EventAbi {
16 | return abi.type === ABI_TYPE_EVENT
17 | }
18 |
19 | export default class ContractTemplate {
20 | handlebars: typeof Handlebars
21 | templatesDir: string
22 | outputDir: string
23 | private _template?: Handlebars
24 |
25 | constructor (templatesDir: string, outputDir: string) {
26 | this.handlebars = Handlebars.create()
27 | this.templatesDir = templatesDir
28 | this.outputDir = outputDir
29 | this.registerPartials()
30 | this.registerHelpers()
31 | }
32 |
33 | get template () {
34 | if (this._template) {
35 | return this._template
36 | } else {
37 | let contents = this.readTemplate('contract.mustache')
38 | this._template = this.handlebars.compile(contents)
39 | return this._template
40 | }
41 | }
42 |
43 | registerPartials () {
44 | fs.readdirSync(this.templatesDir).forEach(file => {
45 | let match = file.match(/^_(\w+)\.(handlebars|mustache)/)
46 | if (match) {
47 | this.handlebars.registerPartial(match[1], this.readTemplate(file))
48 | }
49 | })
50 | }
51 |
52 | registerHelpers () {
53 | this.handlebars.registerHelper('inputType', helpers.inputType)
54 | this.handlebars.registerHelper('outputType', helpers.outputType)
55 | }
56 |
57 | render (abiFilePath: string) {
58 | let artifact = JSON.parse(fs.readFileSync(abiFilePath).toString())
59 | let abi = artifact.abi
60 | if (abi) {
61 | let methods = abi.filter(isAbiFunction).map((abi: MethodAbi) => {
62 | if (abi.outputs.length === 1) {
63 | abi.singleReturnValue = true
64 | }
65 | abi.inputs = abi.inputs.map(input => {
66 | input.name = input.name ? input.name : 'index'
67 | return input
68 | })
69 | return abi
70 | })
71 | let getters = methods.filter((abi: MethodAbi) => abi.constant)
72 | let functions = methods.filter((abi: MethodAbi) => !abi.constant)
73 |
74 | let events = abi.filter(isAbiEvent)
75 |
76 | let contractName = path.parse(abiFilePath).name
77 | const basename = path.basename(abiFilePath, path.extname(abiFilePath))
78 | const filePath = `${this.outputDir}/${basename}.ts`
79 | const relativeArtifactPath = path.relative(this.outputDir, abiFilePath)
80 |
81 | let context: Context = {
82 | artifact: JSON.stringify(artifact, null, 2),
83 | contractName: contractName,
84 | relativeArtifactPath: relativeArtifactPath,
85 | getters: getters,
86 | functions: functions,
87 | events: events
88 | }
89 | let code = this.template(context)
90 | fs.writeFileSync(filePath, code)
91 | } else {
92 | throw new Error(`No ABI found in ${abiFilePath}.`)
93 | }
94 | }
95 |
96 | protected readTemplate (name: string) {
97 | let file = path.resolve(this.templatesDir, name)
98 | return fs.readFileSync(file).toString()
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/support/wrap/IndexTemplate.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as fs from 'fs'
3 | import * as Handlebars from 'handlebars'
4 | import * as helpers from './helpers'
5 |
6 | export interface Wrap {
7 | name: string,
8 | path: string
9 | }
10 |
11 | export interface Context {
12 | wrappers: Array
13 | }
14 |
15 | export default class IndexTemplate {
16 | handlebars: typeof Handlebars
17 | templatesDir: string
18 | outputDir: string
19 | private _template?: Handlebars
20 |
21 | constructor (templatesDir: string, outputDir: string) {
22 | this.handlebars = Handlebars.create()
23 | this.templatesDir = templatesDir
24 | this.outputDir = outputDir
25 | this.registerPartials()
26 | this.registerHelpers()
27 | }
28 |
29 | get template () {
30 | if (this._template) {
31 | return this._template
32 | } else {
33 | let contents = this.readTemplate('index.mustache')
34 | this._template = this.handlebars.compile(contents)
35 | return this._template
36 | }
37 | }
38 |
39 | registerPartials () {
40 | fs.readdirSync(this.templatesDir).forEach(file => {
41 | let match = file.match(/^_(\w+)\.(handlebars|mustache)/)
42 | if (match) {
43 | this.handlebars.registerPartial(match[1], this.readTemplate(file))
44 | }
45 | })
46 | }
47 |
48 | registerHelpers () {
49 | this.handlebars.registerHelper('inputType', helpers.inputType)
50 | this.handlebars.registerHelper('outputType', helpers.outputType)
51 | }
52 |
53 | render (filePaths: Array) {
54 | let wrappers = []
55 | filePaths.forEach(filePath => {
56 | const basename = path.basename(filePath, path.extname(filePath))
57 | wrappers.push({
58 | name: path.parse(filePath).name,
59 | path: `./${basename}`
60 | })
61 | })
62 | let context = { wrappers }
63 | let code = this.template(context)
64 | let indexFilePath = `${this.outputDir}/index.ts`
65 | fs.writeFileSync(indexFilePath, code)
66 | }
67 |
68 | protected readTemplate (name: string) {
69 | let file = path.resolve(this.templatesDir, name)
70 | return fs.readFileSync(file).toString()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/support/wrap/helpers.ts:
--------------------------------------------------------------------------------
1 | type Mapping = {
2 | regex: string
3 | tsType: string
4 | }
5 |
6 | const TYPE_MAPPING = [
7 | {regex: '^string$', tsType: 'string'},
8 | {regex: '^address$', tsType: 'string'},
9 | {regex: '^bool$', tsType: 'boolean'},
10 | {regex: '^u?int\\d*$', tsType: 'BigNumber.BigNumber'},
11 | {regex: '^bytes\\d*$', tsType: 'string'}
12 | ]
13 |
14 | const INPUT_TYPE_MAPPING = [
15 | {regex: '^u?int(8|16|32)?$', tsType: 'number|BigNumber.BigNumber'}
16 | ].concat(TYPE_MAPPING)
17 |
18 | const ARRAY_BRACES = /\[\d*]$/
19 |
20 | function isArray (solidityType: string): boolean {
21 | return !!solidityType.match(ARRAY_BRACES)
22 | }
23 |
24 | function typeConversion (types: Array, solidityType: string): string {
25 | if (isArray(solidityType)) {
26 | const solidityItemType = solidityType.replace(ARRAY_BRACES, '')
27 | const type = typeConversion(types, solidityItemType)
28 | return `${type}[]`
29 | } else {
30 | let mapping = types.find(mapping => !!solidityType.match(mapping.regex))
31 | if (mapping) {
32 | return mapping.tsType
33 | } else {
34 | throw new Error(`Unknown Solidity type found: ${solidityType}`)
35 | }
36 | }
37 | }
38 |
39 | export function inputType (solidityType: string): string {
40 | return typeConversion(INPUT_TYPE_MAPPING, solidityType)
41 | }
42 |
43 | export function outputType (solidityType: string): string {
44 | return typeConversion(TYPE_MAPPING, solidityType)
45 | }
46 |
--------------------------------------------------------------------------------
/support/wrap/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import * as yargs from 'yargs'
4 | import * as mkdirp from 'mkdirp'
5 | import * as path from 'path'
6 | import * as glob from 'glob'
7 | import * as fs from 'fs'
8 | import ContractTemplate from './ContractTemplate'
9 | import IndexTemplate from './IndexTemplate'
10 |
11 | let args = yargs
12 | .option('output', {
13 | describe: 'Folder for generated files',
14 | alias: 'o'
15 | })
16 | .argv
17 |
18 | let pattern = args._[0]
19 | let fileNames = glob.sync(pattern)
20 | if (fileNames.length) {
21 | let templatesDir = path.resolve(__dirname, 'templates')
22 | let outputDir = path.resolve(__dirname, '..', '..', 'build', 'wrappers')
23 | let transformer = new ContractTemplate(templatesDir, outputDir)
24 | let indexTransformer = new IndexTemplate(templatesDir, outputDir)
25 |
26 | if (!fs.existsSync(outputDir)) {
27 | mkdirp.sync(outputDir)
28 | }
29 |
30 | fileNames.forEach(fileName => {
31 | transformer.render(fileName)
32 | })
33 |
34 | indexTransformer.render(fileNames)
35 | } else {
36 | console.error(`No Truffle Contract artifact found at ${pattern}`)
37 | process.exit(1)
38 | }
39 |
--------------------------------------------------------------------------------
/support/wrap/templates/_event.mustache:
--------------------------------------------------------------------------------
1 | export interface {{this.name}} {
2 | {{#if inputs}}
3 | {{#each inputs}}
4 | {{name}}: {{#inputType type}}{{/inputType}}
5 | {{/each}}
6 | {{/if}}
7 | }
8 |
9 | export function is{{this.name}}Event (something: truffle.AnyTransactionEvent): something is truffle.TransactionEvent<{{this.name}}> {
10 | return something.event === '{{this.name}}'
11 | }
12 |
--------------------------------------------------------------------------------
/support/wrap/templates/_function.mustache:
--------------------------------------------------------------------------------
1 | {{this.name}}: {
2 | ({{> method_input inputs=inputs trailingComma=true}}options?: Web3.CallData): Promise
3 | call ({{> method_input inputs=inputs trailingComma=true}}options?: Web3.CallData): Promise<{{> method_output outputs=outputs}}>
4 | estimateGas ({{> method_input inputs=inputs}}): Promise
5 | request ({{> method_input inputs=inputs}}): { method: string, params: [{to: string, data: string}]}
6 | }
7 |
--------------------------------------------------------------------------------
/support/wrap/templates/_getter.mustache:
--------------------------------------------------------------------------------
1 | {{this.name}}: {
2 | ({{> method_input inputs=inputs}}): Promise<{{> method_output outputs=outputs}}>
3 | call ({{> method_input inputs=inputs}}): Promise<{{> method_output outputs=outputs}}>
4 | }
5 |
--------------------------------------------------------------------------------
/support/wrap/templates/_method_input.mustache:
--------------------------------------------------------------------------------
1 | {{~#if inputs~}}
2 | {{~#each inputs~}}{{name}}: {{#inputType type}} {{/inputType}}{{#unless @last}}, {{/unless}}{{~/each~}}
3 | {{~#if trailingComma~}}, {{/if}}
4 | {{~/if~}}
5 |
--------------------------------------------------------------------------------
/support/wrap/templates/_method_output.mustache:
--------------------------------------------------------------------------------
1 | {{~#if outputs~}}
2 | {{~#singleReturnValue~}}
3 | {{#outputType outputs.0.type}}{{/outputType}}
4 | {{~/singleReturnValue~}}
5 | {{~^singleReturnValue~}}
6 | [{{#each outputs}}{{#outputType type}}{{/outputType}}{{#unless @last}}, {{/unless}}{{/each}}]
7 | {{~/singleReturnValue~}}
8 | {{~else~}}
9 | void
10 | {{~/if~}}
11 |
--------------------------------------------------------------------------------
/support/wrap/templates/_params.mustache:
--------------------------------------------------------------------------------
1 | {{#each inputs}}
2 | {{name}},
3 | {{/each}}
4 |
--------------------------------------------------------------------------------
/support/wrap/templates/contract.mustache:
--------------------------------------------------------------------------------
1 | import * as BigNumber from 'bignumber.js'
2 | import * as Web3 from 'web3'
3 | import * as truffle from 'truffle-contract'
4 |
5 | export namespace {{contractName}} {
6 | export const ARTIFACT = require('{{relativeArtifactPath}}')
7 |
8 | export interface Contract {
9 | address: string
10 |
11 | constructor: {
12 | web3: Web3
13 | }
14 |
15 | {{#each getters}}
16 | {{> getter contractName=../contractName}}
17 | {{/each}}
18 |
19 | {{#each functions}}
20 | {{> function contractName=../contractName}}
21 | {{/each}}
22 |
23 | send: (value: BigNumber.BigNumber | number) => Promise
24 | sendTransaction: (opts: Web3.CallData) => Promise
25 | }
26 |
27 | {{#each events}}
28 | {{> event contractName=../contractName}}
29 | {{/each}}
30 |
31 | export function contract (provider?: Web3.Provider, defaults?: Web3.CallData): truffle.TruffleContract {
32 | let instance = truffle(ARTIFACT)
33 | if (provider) {
34 | instance.setProvider(provider)
35 | }
36 | if (defaults) {
37 | instance.defaults(defaults)
38 | }
39 | return instance
40 | }
41 | }
42 |
43 | export default {{contractName}}
44 |
--------------------------------------------------------------------------------
/support/wrap/templates/index.mustache:
--------------------------------------------------------------------------------
1 | {{#each wrappers}}
2 | import {{name}} from '{{path}}'
3 | {{/each}}
4 |
5 |
6 | export {
7 | {{#each wrappers}}
8 | {{name}}{{#unless @last}},{{/unless}}
9 | {{/each}}
10 | }
11 |
--------------------------------------------------------------------------------
/test/scenarios/Cooperative.test.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import * as Web3 from 'web3'
3 | import * as asPromised from 'chai-as-promised'
4 | import * as contracts from '../../src/index'
5 | import * as support from '../support/index'
6 | import * as BigNumber from 'bignumber.js'
7 | import * as wrappers from '../../build/wrappers/index'
8 | import { InstantiationFactory } from '../support/index'
9 |
10 |
11 | chai.use(asPromised)
12 |
13 | const web3 = (global as any).web3 as Web3
14 | const assert = chai.assert
15 |
16 | const ECRecovery = artifacts.require('ECRecovery.sol')
17 | const Multisig = artifacts.require('Multisig.sol')
18 | const LibMultisig = artifacts.require('LibMultisig.sol')
19 | const LibCommon = artifacts.require('LibCommon.sol')
20 |
21 | const DistributeEth = artifacts.require('DistributeEth.sol')
22 |
23 | const TestToken = artifacts.require('TestToken.sol')
24 | const DistributeToken = artifacts.require('DistributeToken.sol')
25 |
26 | const amountA = new BigNumber.BigNumber(10)
27 | const amountB = new BigNumber.BigNumber(20)
28 |
29 | contract('Cooperative Behaviour', accounts => {
30 | let addressA = accounts[4]
31 | let addressB = accounts[5]
32 |
33 | let counterFactory: support.InstantiationFactory
34 | let multisig: contracts.Multisig.Contract
35 |
36 | let distributeEth: contracts.DistributeEth.Contract
37 | let token: wrappers.TestToken.Contract
38 | let distributeToken: contracts.DistributeToken.Contract
39 |
40 | before(async () => {
41 | Multisig.link(ECRecovery)
42 | LibMultisig.link(LibCommon)
43 | Multisig.link(LibMultisig)
44 |
45 | distributeEth = await DistributeEth.new()
46 | multisig = await Multisig.new(addressA, addressB)
47 | counterFactory = new InstantiationFactory(web3, multisig)
48 |
49 | token = await TestToken.new()
50 | await token.mint(multisig.address, amountB.plus(amountA))
51 | await token.finishMinting()
52 | distributeToken = await DistributeToken.new()
53 | })
54 |
55 | specify('distribute Ether', async () => {
56 | let transfer = distributeEth.execute.request(addressA, addressB, amountA, amountB)
57 | let command = await counterFactory.delegatecall(transfer)
58 | web3.eth.sendTransaction({from: accounts[0], to: multisig.address, value: amountB.plus(amountA)})
59 | let beforeA = web3.eth.getBalance(addressA)
60 | let beforeB = web3.eth.getBalance(addressB)
61 | await multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig)
62 | let afterA = web3.eth.getBalance(addressA)
63 | let afterB = web3.eth.getBalance(addressB)
64 | assert.equal(afterA.minus(beforeA).toString(), amountA.toString())
65 | assert.equal(afterB.minus(beforeB).toString(), amountB.toString())
66 | assert.equal(web3.eth.getBalance(multisig.address).toNumber(), 0)
67 | })
68 |
69 | specify('move Tokens', async () => {
70 | let nonce = (await multisig.state())[2]
71 | let transferTokens = distributeToken.execute.request(token.address, addressA, addressB, amountA, amountB)
72 | let command = await counterFactory.delegatecall(transferTokens, nonce)
73 | await support.assertTokenBalance(token, addressA, 0)
74 | await support.assertTokenBalance(token, addressB, 0)
75 | await support.assertTokenBalance(token, multisig.address, amountB.plus(amountA))
76 | await multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig)
77 | await support.assertTokenBalance(token, addressA, amountA)
78 | await support.assertTokenBalance(token, addressB, amountB)
79 | await support.assertTokenBalance(token, multisig.address, 0)
80 | })
81 | })
82 |
--------------------------------------------------------------------------------
/test/scenarios/Dispute.test.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import * as Web3 from 'web3'
3 | import * as asPromised from 'chai-as-promised'
4 | import * as contracts from '../../src/index'
5 | import * as support from '../support/index'
6 | import * as BigNumber from 'bignumber.js'
7 | import { InstantiationFactory, BytecodeManager, HexString, Address } from '../support/index'
8 | import * as util from 'ethereumjs-util'
9 | import MerkleTree from '../../src/MerkleTree'
10 | import * as wrappers from '../../build/wrappers'
11 |
12 |
13 | chai.use(asPromised)
14 |
15 | const web3 = (global as any).web3 as Web3
16 | const assert = chai.assert
17 |
18 |
19 | const ECRecovery = artifacts.require('ECRecovery.sol')
20 | const PublicRegistry = artifacts.require('PublicRegistry.sol')
21 | const Lineup = artifacts.require('Lineup.sol')
22 | const LibLineup = artifacts.require('LibLineup.sol')
23 | const Multisig = artifacts.require('Multisig.sol')
24 | const Bidirectional = artifacts.require('Bidirectional.sol')
25 | const Proxy = artifacts.require('Proxy.sol')
26 | const Conditional = artifacts.require('Conditional.sol')
27 | const LibBidirectional = artifacts.require('LibBidirectional.sol')
28 | const LibMultisig = artifacts.require('LibMultisig.sol')
29 | const LibCommon = artifacts.require('LibCommon.sol')
30 |
31 | Multisig.link(ECRecovery)
32 | Multisig.link(LibCommon)
33 | LibBidirectional.link(ECRecovery)
34 | LibMultisig.link(ECRecovery)
35 | Multisig.link(LibMultisig)
36 | Bidirectional.link(ECRecovery)
37 | Bidirectional.link(LibCommon)
38 | Bidirectional.link(LibBidirectional)
39 | LibLineup.link(LibCommon)
40 | Lineup.link(LibLineup)
41 | Lineup.link(LibCommon)
42 |
43 | interface LineupUpdate {
44 | nonce: BigNumber.BigNumber
45 | merkleRoot: HexString
46 | senderSig: HexString
47 | receiverSig: HexString,
48 | tree: MerkleTree
49 | }
50 |
51 | let _lineupNonce = 0
52 | let _lineupElements: Array = []
53 | function updateLineup (element: HexString|null, sender: Address, receiver: Address): LineupUpdate {
54 | if (element) _lineupElements.push(element)
55 | let tree = new MerkleTree(_lineupElements.map(util.toBuffer))
56 | _lineupNonce += 1
57 | let r = util.bufferToHex(tree.root)
58 | return {
59 | nonce: new BigNumber.BigNumber(_lineupNonce),
60 | merkleRoot: r,
61 | senderSig: support.lineupSign(sender, r, _lineupNonce),
62 | receiverSig: support.lineupSign(receiver, r, _lineupNonce),
63 | tree: tree
64 | }
65 | }
66 |
67 | function proof (lineupU: LineupUpdate, e: HexString): HexString {
68 | let tree = lineupU.tree
69 | let p = Buffer.concat(tree.proof(util.toBuffer(e)))
70 | return util.bufferToHex(p)
71 | }
72 |
73 | const lineupSettlementPeriod = 1 // Hint: Set to 0 to test lineup update period
74 | const bidirectionalSettlementPeriod = 10
75 |
76 | const REGISTRY_NONCE = '0x20'
77 | const depositA = new BigNumber.BigNumber(100)
78 | let amountA = new BigNumber.BigNumber(10)
79 | let amountB = new BigNumber.BigNumber(90)
80 |
81 | contract('Uncooperative Behaviour', accounts => {
82 | const addressA = accounts[3]
83 | const addressB = accounts[4]
84 |
85 | let lineupU = updateLineup(null, addressA, addressB)
86 |
87 | let registry: contracts.PublicRegistry.Contract
88 | let proxy: contracts.Proxy.Contract
89 |
90 | let bytecodeManager: BytecodeManager
91 |
92 | before(async () => {
93 | bytecodeManager = new BytecodeManager(web3)
94 | await bytecodeManager.addLink(ECRecovery, 'ECRecovery')
95 | await bytecodeManager.addLink(LibBidirectional, 'LibBidirectional')
96 | await bytecodeManager.addLink(LibCommon, 'LibCommon')
97 | await bytecodeManager.addLink(LibMultisig, 'LibMultisig')
98 | await bytecodeManager.addLink(LibLineup, 'LibLineup')
99 |
100 | registry = await PublicRegistry.deployed()
101 | proxy = await Proxy.deployed()
102 | })
103 |
104 | specify('resolve dispute about Ether', async () => {
105 | // 1: Deploy Multisig
106 | let multisig = await Multisig.new(addressA, addressB)
107 | let counterFactory = new InstantiationFactory(web3, multisig)
108 | let conditional = await Conditional.new()
109 |
110 | // // 2: Counterfactually deploy Lineup
111 | let lineupB = bytecodeManager.constructBytecode(Lineup, 0x0, lineupSettlementPeriod, multisig.address)
112 | let lineupA = await registry.counterfactualAddress(lineupB, REGISTRY_NONCE)
113 | let lineupI = await counterFactory.call(registry.deploy.request(lineupB, REGISTRY_NONCE))
114 |
115 | // 3: Prepare counterfactual deployment of Bidirectional, and update Lineup
116 | let bidirectionalB = bytecodeManager.constructBytecode(Bidirectional, multisig.address, bidirectionalSettlementPeriod)
117 | let bidirectionalA = await registry.counterfactualAddress(bidirectionalB, REGISTRY_NONCE)
118 | let bidirectionalDeployment = registry.deploy.request(bidirectionalB, REGISTRY_NONCE).params[0].data
119 | let bidirectionalCodehash = await conditional.callHash(registry.address, new BigNumber.BigNumber(0), bidirectionalDeployment)
120 | lineupU = updateLineup(bidirectionalCodehash, addressA, addressB)
121 |
122 | // 4. Prepare counterfactual transfer, and update Lineup
123 | let transferB = proxy.doCall.request(registry.address, bidirectionalA, depositA, '0x').params[0].data
124 | let transferCodehash = await conditional.callHash(proxy.address, depositA, transferB)
125 | lineupU = updateLineup(transferCodehash, addressA, addressB)
126 |
127 | // 5: Conditionally counterfactually deploy Bidirectional
128 | let conditionalBidirectionalB = conditional.doCall.request(registry.address, lineupA, proof(lineupU, bidirectionalCodehash), registry.address, new BigNumber.BigNumber(0), bidirectionalDeployment)
129 | let conditionalBidirectionalI = await counterFactory.call(conditionalBidirectionalB, 1)
130 |
131 | // 5. Conditionally counterfactually move money to deployed Bidirectional
132 | let conditionalTransferB = conditional.doDelegate.request(registry.address, lineupA, proof(lineupU, transferCodehash), proxy.address, depositA, transferB)
133 | let conditionalTransferI = await counterFactory.delegatecall(conditionalTransferB, 2)
134 |
135 | // 6. Deposit to Multisig
136 | web3.eth.sendTransaction({from: addressA, to: multisig.address, value: depositA})
137 |
138 | // 5. Do exchanges on Bidirectional Eth channel
139 | // let nonce = new BigNumber.BigNumber(10)
140 | // let sigA = support.bidirectionalSign(addressA, nonce, amountA, amountB)
141 | // let sigB = support.bidirectionalSign(addressB, nonce, amountA, amountB)
142 |
143 | // 5.1 And close transaction for Bidirectional Eth channel
144 | let closeSigA = support.bidirectionalCloseSign(addressA, amountA, amountB)
145 | let closeSigB = support.bidirectionalCloseSign(addressB, amountA, amountB)
146 |
147 | // ****** And now Resolve the dispute for Bidirectional ****** \\
148 | // 6. Deploy Lineup
149 | await counterFactory.execute(lineupI)
150 | let lineup = await Lineup.at(await registry.resolve(lineupA))
151 | // 6.1 Optionally update Lineup
152 | await lineup.update(lineupU.nonce, lineupU.merkleRoot, lineupU.senderSig, lineupU.receiverSig)
153 |
154 | // 7 Deploy Bidirectional channel
155 | await counterFactory.execute(conditionalBidirectionalI)
156 |
157 | // 8 Move money to Bidirectional
158 | await counterFactory.execute(conditionalTransferI)
159 | assert.equal(web3.eth.getBalance(multisig.address).toNumber(), 0, 'Multisig balance')
160 |
161 | // 9 Close Bidirectional channel
162 | let bidirectional = await Bidirectional.at(await registry.resolve(bidirectionalA))
163 | assert.equal(web3.eth.getBalance(bidirectional.address).toNumber(), depositA.toNumber(), 'Bidirectional balance')
164 |
165 | let addressABefore = web3.eth.getBalance(addressA)
166 | let addressBBefore = web3.eth.getBalance(addressB)
167 | await bidirectional.close(amountA, amountB, closeSigA, closeSigB)
168 | let addressAAfter = web3.eth.getBalance(addressA)
169 | let addressBAfter = web3.eth.getBalance(addressB)
170 | // Ensure the funds are distributed correctly
171 | assert.equal(web3.eth.getBalance(bidirectional.address).toNumber(), 0, 'Bidirectional balance after close')
172 | assert.equal(addressAAfter.minus(addressABefore).toNumber(), amountA.toNumber(), 'balance of addressA after close')
173 | assert.equal(addressBAfter.minus(addressBBefore).toNumber(), amountB.toNumber(), 'balance of addressB after close')
174 | })
175 | })
176 |
--------------------------------------------------------------------------------
/test/support/index.ts:
--------------------------------------------------------------------------------
1 | import * as BigNumber from 'bignumber.js'
2 | import * as truffle from 'truffle-contract'
3 | import * as Web3 from 'web3'
4 | import { Multisig, PublicRegistry } from '../../src/index'
5 | import * as chai from 'chai'
6 | import * as abi from 'ethereumjs-abi'
7 | import * as util from 'ethereumjs-util'
8 |
9 | const web3 = (global as any).web3 as Web3
10 |
11 | const LOG_GAS_COST = Boolean(process.env.LOG_GAS_COST)
12 | const GAS_COST_IN_USD = 0.000012 // 1 ETH = 600 USD
13 |
14 | export type Number = BigNumber.BigNumber | number
15 |
16 | export class Gaser {
17 | web3: Web3
18 |
19 | constructor (_web3: Web3) {
20 | this.web3 = _web3
21 | }
22 |
23 | async gasDiff (name: string, account: string, fn: () => A, forceLog: boolean = false) {
24 | let before = web3.eth.getBalance(account)
25 | let result = fn()
26 | let after = web3.eth.getBalance(account)
27 | let gasCost = before.minus(after).div(this.web3.eth.gasPrice.div(0.2)).toNumber()
28 | this.log(gasCost, name, forceLog)
29 | return result
30 | }
31 |
32 | async logGas (name: string, promisedTx: Promise, forceLog: boolean = false) {
33 | let tx = await promisedTx
34 | this.log(tx.receipt.gasUsed, name, forceLog)
35 | return tx
36 | }
37 |
38 | private log (gasCost: number, name: string, forceLog: boolean = false) {
39 | if (LOG_GAS_COST || forceLog) {
40 | console.log(`GAS: ${name}: ($${(gasCost * GAS_COST_IN_USD).toFixed(2)})`, gasCost)
41 | }
42 | }
43 | }
44 |
45 | export async function logGas (name: string, promisedTx: Promise, forceLog: boolean = false) {
46 | let tx = await promisedTx
47 | if (LOG_GAS_COST || forceLog) {
48 | let gasCost = tx.receipt.gasUsed
49 | console.log(`GAS: ${name}: ($${(gasCost * GAS_COST_IN_USD).toFixed(2)})`, gasCost)
50 | }
51 | return tx
52 | }
53 |
54 | export async function gasDiff (name: string, web3: Web3, account: string, fn: () => A, forceLog: boolean = false) {
55 | let before = web3.eth.getBalance(account)
56 |
57 | // tslint:disable-next-line:await-promise
58 | let result = await fn() // TODO Do we need await here?
59 | let after = web3.eth.getBalance(account)
60 | if (LOG_GAS_COST || forceLog) {
61 | let gasCost = before.minus(after).div(web3.eth.gasPrice.div(0.2)).toString() // Beware of magic numbers
62 | console.log(`GAS: ${name}: ($${(parseFloat(gasCost) * GAS_COST_IN_USD).toFixed(2)})`, gasCost)
63 | }
64 | return result
65 | }
66 |
67 | export function txPrice (web3: Web3, log: truffle.TransactionResult): BigNumber.BigNumber {
68 | return web3.eth.getTransaction(log.tx).gasPrice.mul(log.receipt.gasUsed)
69 | }
70 |
71 | export function constructorBytecode (web3: Web3, contract: truffle.TruffleContract, ...params: any[]): string {
72 | return web3.eth.contract(contract.abi).getData(...params, {data: contract.bytecode})
73 | }
74 |
75 | export function lineupSign (account: string, merkleRoot: HexString, nonce: Number) {
76 | let operationHash = util.bufferToHex(abi.soliditySHA3(
77 | ['bytes32', 'uint256'],
78 | [merkleRoot, nonce.toString()]
79 | ))
80 | return web3.eth.sign(account, operationHash)
81 | }
82 |
83 | export function bidirectionalSign (account: string, nonce: Number, toSender: Number, toReceiver: Number) {
84 | let operationHash = util.bufferToHex(abi.soliditySHA3(
85 | ['uint256', 'uint256', 'uint256'],
86 | [nonce.toString(), toSender.toString(), toReceiver.toString()]
87 | ))
88 | return web3.eth.sign(account, operationHash)
89 | }
90 |
91 | export function bidirectionalCloseSign (account: string, toSender: Number, toReceiver: Number) {
92 | let operationHash = util.bufferToHex(abi.soliditySHA3(
93 | ['string', 'uint256', 'uint256'],
94 | ['c', toSender.toString(), toReceiver.toString()]
95 | ))
96 | return web3.eth.sign(account, operationHash)
97 | }
98 |
99 | export interface CallParams {
100 | to: string
101 | data: string
102 | }
103 |
104 | export interface Call {
105 | method: string
106 | value?: BigNumber.BigNumber,
107 | params: Array
108 | }
109 |
110 | export type Address = string
111 | export type HexString = string
112 |
113 | export interface Instantiation {
114 | destination: Address
115 | callBytecode: HexString
116 | value: BigNumber.BigNumber
117 | operation: number,
118 | senderSig: HexString,
119 | receiverSig: HexString,
120 | nonce: BigNumber.BigNumber
121 | }
122 |
123 | export class InstantiationFactory {
124 | web3: Web3
125 | multisig: Multisig.Contract
126 |
127 | constructor (web3: Web3, multisig: Multisig.Contract) {
128 | this.web3 = web3
129 | this.multisig = multisig
130 | }
131 |
132 | async call (call: Call, _nonce?: BigNumber.BigNumber|number): Promise {
133 | return this.build(call, _nonce)
134 | }
135 |
136 | async delegatecall (call: Call, _nonce?: BigNumber.BigNumber|number): Promise {
137 | return this.buildDelegate(call, _nonce)
138 | }
139 |
140 | async execute (i: Instantiation, options?: Web3.TxData): Promise {
141 | if (i.operation === 0) {
142 | return this.multisig.doCall(i.destination, i.value, i.callBytecode, i.senderSig, i.receiverSig, options)
143 | } else {
144 | return this.multisig.doDelegate(i.destination, i.callBytecode, i.senderSig, i.receiverSig, options)
145 | }
146 |
147 | }
148 |
149 | async raw (destination: Address, value: BigNumber.BigNumber, callBytecode: HexString, nonce: BigNumber.BigNumber, operation: number) {
150 | let _state = await this.multisig.state()
151 | let sender = _state[0]
152 | let receiver = _state[1]
153 |
154 | let operationHash = util.bufferToHex(abi.soliditySHA3(
155 | ['address', 'address' , 'uint256', 'bytes', 'uint256'],
156 | [this.multisig.address, destination, value.toString(), util.toBuffer(callBytecode), nonce.toString()]
157 | )) // TODO Make it different for call and delegatecall
158 | let senderSig = this.web3.eth.sign(sender, operationHash)
159 | let receiverSig = this.web3.eth.sign(receiver, operationHash)
160 |
161 | return Promise.resolve({
162 | destination,
163 | callBytecode,
164 | value,
165 | operation,
166 | senderSig,
167 | receiverSig,
168 | nonce
169 | })
170 | }
171 |
172 | private async build (call: Call, _nonce?: BigNumber.BigNumber|number): Promise {
173 | let params = call.params[0]
174 | let value = new BigNumber.BigNumber(0)
175 | let _state = await this.multisig.state()
176 | let nonce = new BigNumber.BigNumber(_nonce || _state[2])
177 |
178 | return this.raw(params.to, value, params.data, nonce, 0)
179 | }
180 |
181 | private async buildDelegate (call: Call, _nonce?: BigNumber.BigNumber|number): Promise {
182 | let params = call.params[0]
183 | let value = call.value || new BigNumber.BigNumber(0)
184 | let _state = await this.multisig.state()
185 | let nonce = new BigNumber.BigNumber(_nonce || _state[2])
186 | let destination = params.to
187 | let callBytecode = params.data
188 | let operation = 1
189 | let sender = _state[0]
190 | let receiver = _state[1]
191 |
192 | let operationHash = util.bufferToHex(abi.soliditySHA3(
193 | ['address', 'address' , 'bytes', 'uint256'],
194 | [this.multisig.address, destination, util.toBuffer(callBytecode), nonce.toString()]
195 | )) // TODO Make it different for call and delegatecall
196 | let senderSig = this.web3.eth.sign(sender, operationHash)
197 | let receiverSig = this.web3.eth.sign(receiver, operationHash)
198 |
199 | return Promise.resolve({
200 | destination,
201 | callBytecode,
202 | value,
203 | operation,
204 | senderSig,
205 | receiverSig,
206 | nonce
207 | })
208 | }
209 | }
210 |
211 | export function counterfactualAddress (registry: PublicRegistry.Contract, i: Instantiation, nonce: HexString): Promise {
212 | let bytecode = i.callBytecode
213 | return registry.counterfactualAddress(bytecode, nonce)
214 | }
215 |
216 | export type ContractLike = {
217 | address: string
218 | constructor: {
219 | web3: Web3
220 | }
221 | }
222 | export async function assertBalance (contract: ContractLike, expected: BigNumber.BigNumber|number): Promise {
223 | let web3 = contract.constructor.web3
224 | let actualBalance = web3.eth.getBalance(contract.address)
225 | chai.assert.equal(actualBalance.toString(), expected.toString())
226 | }
227 |
228 | export interface TokenLike {
229 | balanceOf (_owner: Address): Promise
230 | }
231 | export async function assertTokenBalance (token: TokenLike, address: Address, expected: BigNumber.BigNumber|number): Promise {
232 | let actualBalance = await token.balanceOf(address)
233 | chai.assert.equal(actualBalance.toString(), expected.toString())
234 | }
235 |
236 | export class BytecodeManager {
237 | private _web3: Web3
238 | private _links: Map
239 |
240 | constructor (web3: Web3, links?: Map) {
241 | this._web3 = web3
242 | this._links = links ? links : new Map()
243 | }
244 |
245 | public constructBytecode (contract: truffle.TruffleContract, ...params: any[]): string {
246 | let bytecode = this._link(contract.bytecode)
247 | return this._web3.eth.contract(contract.abi).getData(...params, {data: bytecode})
248 | }
249 |
250 | public async addLink (contract: truffle.TruffleContract, name: string) {
251 | let deployed = await contract.deployed()
252 | this._links.set(name, deployed.address)
253 | }
254 |
255 | protected _link (bytecode: string) {
256 | this._links.forEach((libraryAddress: string, libraryName: string) => {
257 | let regex = new RegExp('__' + libraryName + '_+', 'g')
258 | bytecode = bytecode.replace(regex, libraryAddress.replace('0x', ''))
259 | })
260 | return bytecode
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/test/unit/Bidirectional.test.ts:
--------------------------------------------------------------------------------
1 | import * as Web3 from 'web3'
2 | import * as chai from 'chai'
3 | import * as BigNumber from 'bignumber.js'
4 | import * as asPromised from 'chai-as-promised'
5 | import * as contracts from '../../src/index'
6 | import * as support from '../support'
7 |
8 | chai.use(asPromised)
9 |
10 | const web3 = (global as any).web3 as Web3
11 | const assert = chai.assert
12 | const gaser = new support.Gaser(web3)
13 |
14 | const Multisig = artifacts.require('Multisig.sol')
15 | const LibMultisig = artifacts.require('LibMultisig.sol')
16 | const LibCommon = artifacts.require('LibCommon.sol')
17 | const Bidirectional = artifacts.require('Bidirectional.sol')
18 | const LibBidirectional = artifacts.require('LibBidirectional.sol')
19 |
20 | Multisig.link(LibCommon)
21 | Multisig.link(LibMultisig)
22 | Bidirectional.link(LibCommon)
23 | Bidirectional.link(LibBidirectional)
24 |
25 | let settlementPeriod = new BigNumber.BigNumber(10)
26 |
27 | contract('Bidirectional', accounts => {
28 | let multisig: contracts.Multisig.Contract
29 | let instance: contracts.Bidirectional.Contract
30 |
31 | let sender = accounts[2]
32 | let receiver = accounts[3]
33 | let toSender = new BigNumber.BigNumber(10)
34 | let toReceiver = new BigNumber.BigNumber(20)
35 |
36 | beforeEach(async () => {
37 | multisig = await Multisig.new(sender, receiver)
38 | instance = await Bidirectional.new(multisig.address, settlementPeriod)
39 | })
40 |
41 | describe('.new', function () {
42 | specify('instantiate', async () => {
43 | let instance = await gaser.gasDiff('Bidirectional.new', sender, async () => {
44 | return await Bidirectional.new(multisig.address, settlementPeriod, {from: sender})
45 | })
46 | let state = await instance.state()
47 | assert.equal(state[0], multisig.address, 'multisig address')
48 | assert.equal(state[1].toNumber(), web3.eth.getBlock('latest').number, 'lastUpdate')
49 | assert.equal(state[2].toNumber(), settlementPeriod.toNumber(), 'settlementPeriod')
50 | assert.equal(state[3].toNumber(), 0, 'nonce')
51 | assert.equal(state[4].toNumber(), 0, 'toSender')
52 | assert.equal(state[5].toNumber(), 0, 'toReceiver')
53 | })
54 | })
55 |
56 | describe('.update', function () {
57 | specify('set new state', async () => {
58 | let nonce = new BigNumber.BigNumber(1)
59 | let senderSig = support.bidirectionalSign(sender, nonce, toSender, toReceiver)
60 | let receiverSig = support.bidirectionalSign(receiver, nonce, toSender, toReceiver)
61 | await gaser.logGas('Bidirectional.update', instance.update(nonce, toSender, toReceiver, senderSig, receiverSig))
62 | let state = await instance.state()
63 | assert.equal(state[0], multisig.address, 'multisig address')
64 | assert.equal(state[1].toNumber(), web3.eth.getBlock('latest').number, 'lastUpdate')
65 | assert.equal(state[2].toNumber(), settlementPeriod.toNumber(), 'settlementPeriod')
66 | assert.equal(state[3].toNumber(), 1, 'nonce')
67 | assert.equal(state[4].toNumber(), toSender.toNumber(), 'toSender')
68 | assert.equal(state[5].toNumber(), toReceiver.toNumber(), 'toReceiver')
69 | })
70 | specify('not if earlier nonce', async () => {
71 | let nonce = (await instance.state())[3]
72 | let senderSig = support.bidirectionalSign(sender, nonce, toSender, toReceiver)
73 | let receiverSig = support.bidirectionalSign(receiver, nonce, toSender, toReceiver)
74 | // tslint:disable-next-line:await-promise
75 | await assert.isRejected(instance.update(nonce, toSender, toReceiver, senderSig, receiverSig))
76 | })
77 | specify('not if late', async () => {
78 | let instance = await Bidirectional.new(multisig.address, 0, {from: sender})
79 | let nonce = (await instance.state())[3].plus(1)
80 | let senderSig = support.bidirectionalSign(sender, nonce, toSender, toReceiver)
81 | let receiverSig = support.bidirectionalSign(receiver, nonce, toSender, toReceiver)
82 | web3.eth.sendTransaction({from: sender, to: receiver, value: 10})
83 | // tslint:disable-next-line:await-promise
84 | await assert.isRejected(instance.update(nonce, toSender, toReceiver, senderSig, receiverSig))
85 | })
86 | specify('not if wrong signature', async () => {
87 | let nonce = (await instance.state())[3].plus(1)
88 | let senderSig = support.bidirectionalSign(sender, nonce, toSender, toReceiver)
89 | let receiverSig = support.bidirectionalSign(receiver, nonce, toSender, toReceiver)
90 | web3.eth.sendTransaction({from: sender, to: receiver, value: 10})
91 | // tslint:disable-next-line:await-promise
92 | await assert.isRejected(instance.update(nonce, toSender, toReceiver, '0xdead', receiverSig))
93 | // tslint:disable-next-line:await-promise
94 | await assert.isRejected(instance.update(nonce, toSender, toReceiver, senderSig, '0xdead'))
95 | })
96 | })
97 |
98 | describe('.close', () => {
99 | specify('destroy contract', async () => {
100 | let senderSig = support.bidirectionalCloseSign(sender, toSender, toReceiver)
101 | let receiverSig = support.bidirectionalCloseSign(receiver, toSender, toReceiver)
102 | web3.eth.sendTransaction({from: sender, to: instance.address, value: toSender.plus(toReceiver)})
103 | await gaser.logGas('Bidirectional.close', instance.close(toSender, toReceiver, senderSig, receiverSig))
104 | let state = await instance.state()
105 | assert.equal(state[0], '0x', 'multisig address')
106 | assert.equal(state[1].toNumber(), 0, 'lastUpdate')
107 | assert.equal(state[2].toNumber(), 0, 'settlementPeriod')
108 | assert.equal(state[3].toNumber(), 0, 'nonce')
109 | assert.equal(state[4].toNumber(), 0, 'toSender')
110 | assert.equal(state[5].toNumber(), 0, 'toReceiver')
111 | })
112 | specify('transfer funds', async () => {
113 | let senderSig = support.bidirectionalCloseSign(sender, toSender, toReceiver)
114 | let receiverSig = support.bidirectionalCloseSign(receiver, toSender, toReceiver)
115 | web3.eth.sendTransaction({from: sender, to: instance.address, value: toSender.plus(toReceiver)})
116 | let senderBefore = web3.eth.getBalance(sender)
117 | let receiverBefore = web3.eth.getBalance(receiver)
118 | await instance.close(toSender, toReceiver, senderSig, receiverSig)
119 | let senderAfter = web3.eth.getBalance(sender)
120 | let receiverAfter = web3.eth.getBalance(receiver)
121 | assert.equal(senderAfter.minus(senderBefore).toString(), toSender.toString())
122 | assert.equal(receiverAfter.minus(receiverBefore).toString(), toReceiver.toString())
123 | })
124 | specify('not if no funds', async () => {
125 | let senderSig = support.bidirectionalCloseSign(sender, toSender, toReceiver)
126 | let receiverSig = support.bidirectionalCloseSign(receiver, toSender, toReceiver)
127 | // tslint:disable-next-line:await-promise
128 | await assert.isRejected(instance.close(toSender, toReceiver, senderSig, receiverSig))
129 | })
130 | specify('not if wrong signature', async () => {
131 | let senderSig = support.bidirectionalCloseSign(receiver, toSender, toReceiver)
132 | let receiverSig = support.bidirectionalCloseSign(receiver, toSender, toReceiver)
133 | web3.eth.sendTransaction({from: sender, to: instance.address, value: toSender.plus(toReceiver)})
134 | // tslint:disable-next-line:await-promise
135 | await assert.isRejected(gaser.logGas('Bidirectional.close', instance.close(toSender, toReceiver, '0xdead', receiverSig)))
136 | // tslint:disable-next-line:await-promise
137 | await assert.isRejected(gaser.logGas('Bidirectional.close', instance.close(toSender, toReceiver, senderSig, '0xdead')))
138 | })
139 | })
140 | })
141 |
--------------------------------------------------------------------------------
/test/unit/Conditional.test.ts:
--------------------------------------------------------------------------------
1 | import * as Web3 from 'web3'
2 | import * as chai from 'chai'
3 | import * as BigNumber from 'bignumber.js'
4 | import * as asPromised from 'chai-as-promised'
5 | import * as contracts from '../../src/index'
6 | import * as util from 'ethereumjs-util'
7 | import * as truffle from 'truffle-contract'
8 |
9 | import TestContractWrapper from '../../build/wrappers/TestContract'
10 | import { InstantiationFactory, BytecodeManager } from '../support/index'
11 | import MerkleTree from '../../src/MerkleTree'
12 |
13 | chai.use(asPromised)
14 |
15 | const web3 = (global as any).web3 as Web3
16 | const assert = chai.assert
17 |
18 | const ECRecovery = artifacts.require('ECRecovery.sol')
19 | const PublicRegistry = artifacts.require('PublicRegistry.sol')
20 | const Multisig = artifacts.require('Multisig.sol')
21 | const Lineup = artifacts.require('Lineup.sol')
22 | const Conditional = artifacts.require('Conditional.sol')
23 | const LibCommon = artifacts.require('LibCommon.sol')
24 | const LibMultisig = artifacts.require('LibMultisig.sol')
25 | const LibLineup = artifacts.require('LibLineup.sol')
26 |
27 | const TestContract: truffle.TruffleContract = artifacts.require('TestContract.sol')
28 |
29 |
30 | contract('ConditionalCall', accounts => {
31 | let registry: contracts.PublicRegistry.Contract
32 | let conditional: contracts.Conditional.Contract
33 | let multisig: contracts.Multisig.Contract
34 | let counterFactory: InstantiationFactory
35 | let bytecodeManager: BytecodeManager
36 |
37 | let sender = accounts[0]
38 | let receiver = accounts[1]
39 |
40 | before(async () => {
41 | Multisig.link(ECRecovery)
42 | Multisig.link(LibCommon)
43 | Multisig.link(LibMultisig)
44 | multisig = await Multisig.new(sender, receiver) // TxCheck
45 | registry = await PublicRegistry.deployed()
46 |
47 | counterFactory = new InstantiationFactory(web3, multisig)
48 | conditional = await Conditional.new()
49 |
50 | bytecodeManager = new BytecodeManager(web3)
51 | await bytecodeManager.addLink(LibCommon, 'LibCommon')
52 | await bytecodeManager.addLink(LibLineup, 'LibLineup')
53 | })
54 |
55 | let registryNonce = util.bufferToHex(Buffer.from('secret'))
56 |
57 | describe('.execute', () => {
58 | specify('call deployed contract', async () => {
59 | let testContract = await TestContract.new(1)
60 | assert.equal((await testContract.nonce()).toNumber(), 1)
61 | let newNonce = new BigNumber.BigNumber(10)
62 |
63 | let bytecode = testContract.updateNonce.request(newNonce).params[0].data
64 | let codeHash = await conditional.callHash(testContract.address, new BigNumber.BigNumber(0), bytecode)
65 |
66 | let merkleTree = new MerkleTree([util.toBuffer(codeHash)])
67 |
68 | let lineupB = bytecodeManager.constructBytecode(Lineup, util.bufferToHex(merkleTree.root), 0, multisig.address)
69 | let lineupCAddress = await registry.counterfactualAddress(lineupB, registryNonce)
70 | let lineupI = await counterFactory.call(registry.deploy.request(lineupB, registryNonce))
71 | await counterFactory.execute(lineupI)
72 |
73 | let proof = Buffer.concat(merkleTree.proof(util.toBuffer(codeHash))) // merkleTree.proof(codeHash)
74 | await conditional.doCall(registry.address, lineupCAddress, util.bufferToHex(proof), testContract.address, new BigNumber.BigNumber(0), bytecode)
75 | let actualNonce = await testContract.nonce()
76 | assert.equal(actualNonce.toNumber(), newNonce.toNumber())
77 | })
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/test/unit/Lineup.test.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import * as asPromised from 'chai-as-promised'
3 | import * as Web3 from 'web3'
4 | import * as BigNumber from 'bignumber.js'
5 | import * as util from 'ethereumjs-util'
6 | import * as contracts from '../../src/index'
7 | import * as support from '../support'
8 | import MerkleTree from '../../src/MerkleTree'
9 |
10 | chai.use(asPromised)
11 |
12 | const assert = chai.assert
13 | const web3 = (global as any).web3 as Web3
14 | const gaser = new support.Gaser(web3)
15 |
16 | const Lineup = artifacts.require('Lineup.sol')
17 | const Multisig = artifacts.require('Multisig.sol')
18 | const LibLineup = artifacts.require('LibLineup.sol')
19 | const LibMultisig = artifacts.require('LibMultisig.sol')
20 | const LibCommon = artifacts.require('LibCommon.sol')
21 |
22 | Multisig.link(LibCommon)
23 | Multisig.link(LibMultisig)
24 | Lineup.link(LibLineup)
25 | Lineup.link(LibCommon)
26 |
27 | const elements = [1, 2, 3].map(e => util.sha3(e))
28 | const merkleTree = new MerkleTree(elements)
29 |
30 | contract('Lineup', accounts => {
31 | let lineup: contracts.Lineup.Contract
32 | let multisig: contracts.Multisig.Contract
33 |
34 | const sender = accounts[0]
35 | const receiver = accounts[1]
36 |
37 | function oneBlock () {
38 | web3.eth.sendTransaction({from: sender, to: receiver, value: 1}) // 1 block
39 | }
40 |
41 | before(async () => {
42 | multisig = await Multisig.new(sender, receiver, {from: sender})
43 | lineup = await gaser.gasDiff('Lineup.new', sender, async () => {
44 | return await Lineup.new(0x0, 100, multisig.address, { from: sender })
45 | })
46 | })
47 |
48 | describe('.update', async () => {
49 | let nonce = new BigNumber.BigNumber(42)
50 | let merkleRoot = util.bufferToHex(merkleTree.root)
51 | let senderSig = support.lineupSign(sender, merkleRoot, nonce)
52 | let receiverSig = support.lineupSign(receiver, merkleRoot, nonce)
53 |
54 | specify('set new state', async () => {
55 | await gaser.gasDiff('Lineup.update', sender, async () => {
56 | await lineup.update(nonce, merkleRoot, senderSig, receiverSig)
57 | })
58 | let updatedNonce = (await lineup.state())[0]
59 | assert.equal(updatedNonce.toNumber(), nonce.toNumber())
60 | let updatedMerkleRoot = (await lineup.state())[1]
61 | assert.equal(updatedMerkleRoot.toString(), merkleRoot)
62 | })
63 | specify('not if late', async () => {
64 | let lineup = await Lineup.new(0x0, 0, multisig.address)
65 | oneBlock()
66 | // tslint:disable-next-line:await-promise
67 | await assert.isRejected(lineup.update(nonce, merkleRoot, senderSig, receiverSig))
68 | })
69 | specify('not if earlier nonce', async () => {
70 | // tslint:disable-next-line:await-promise
71 | await assert.isRejected(lineup.update(nonce.minus(1), merkleRoot, senderSig, receiverSig))
72 | })
73 | specify('not if not signed', async () => {
74 | // tslint:disable-next-line:await-promise
75 | await assert.isRejected(lineup.update(nonce.plus(1), merkleRoot, '0xdead', receiverSig))
76 | // tslint:disable-next-line:await-promise
77 | await assert.isRejected(lineup.update(nonce.plus(1), merkleRoot, senderSig, '0xdead'))
78 | })
79 | })
80 |
81 | describe('.isContained', () => {
82 | let updatePeriod = new BigNumber.BigNumber(0)
83 | let merkleRoot = util.bufferToHex(merkleTree.root)
84 | let element = elements[0]
85 | let proof = util.bufferToHex(Buffer.concat(merkleTree.proof(element)))
86 |
87 | let lineup: contracts.Lineup.Contract
88 |
89 | beforeEach(async () => {
90 | lineup = await Lineup.new(merkleRoot, updatePeriod, multisig.address)
91 | })
92 |
93 | specify('ok if contained', async () => {
94 | assert.isTrue(merkleTree.verify(merkleTree.proof(element), element))
95 | assert.isTrue(await lineup.isContained(proof, util.bufferToHex(element)))
96 | })
97 | specify('not if early', async () => {
98 | let lineup = await Lineup.new(merkleRoot, updatePeriod.plus(10), multisig.address)
99 | assert.isTrue(merkleTree.verify(merkleTree.proof(element), element))
100 | assert.isFalse(await lineup.isContained(proof, util.bufferToHex(element)))
101 | })
102 | specify('not if wrong proof', async () => {
103 | let fakeProof = util.toBuffer('0xdead')
104 | assert.isFalse(merkleTree.verify([fakeProof], element))
105 | assert.isFalse(await lineup.isContained(util.bufferToHex(fakeProof), util.bufferToHex(element)))
106 | })
107 | })
108 | })
109 |
--------------------------------------------------------------------------------
/test/unit/Multisig._test.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import * as Web3 from 'web3'
3 | import * as asPromised from 'chai-as-promised'
4 | import { Address, BytecodeManager, Instantiation, InstantiationFactory } from '../support/index'
5 | import * as support from '../support/index'
6 | import * as BigNumber from 'bignumber.js'
7 | import * as contracts from '../../src/index'
8 | import * as util from 'ethereumjs-util'
9 | import * as wrappers from '../../build/wrappers'
10 |
11 |
12 | chai.use(asPromised)
13 |
14 | const web3 = (global as any).web3 as Web3
15 | const assert = chai.assert
16 | const gaser = new support.Gaser(web3)
17 |
18 | const FAKE_ADDRESS_A: Address = '0x0a00000000000000000000000000000000000000'
19 | const FAKE_ADDRESS_B: Address = '0x0b00000000000000000000000000000000000000'
20 |
21 | contract('Multisig', accounts => {
22 | let sender: string
23 | let receiver: string
24 | let alien: string
25 |
26 | let registryNonce: string
27 |
28 | const ECRecovery = artifacts.require('ECRecovery.sol')
29 | const LibCommon = artifacts.require('LibCommon.sol')
30 | const Multisig = artifacts.require('Multisig.sol')
31 | const LibMultisig = artifacts.require('LibMultisig.sol')
32 | const BidirectionalCF = artifacts.require('BidirectionalCF.sol')
33 | const BidirectionalCFLibrary = artifacts.require('BidirectionalCFLibrary.sol')
34 | const PublicRegistry = artifacts.require('PublicRegistry.sol')
35 | const Proxy = artifacts.require('Proxy.sol')
36 | const ProxyLibrary = artifacts.require('ProxyLibrary.sol')
37 | const Lineup = artifacts.require('Lineup.sol')
38 | const TestContract = artifacts.require('TestContract.sol')
39 | const TestToken = artifacts.require('TestToken.sol')
40 | const TransferToken = artifacts.require('TransferToken.sol')
41 | const DistributeEth = artifacts.require('DistributeEth.sol')
42 | const DistributeToken = artifacts.require('DistributeToken.sol')
43 |
44 | let bytecodeManager: BytecodeManager
45 | let counterFactory: InstantiationFactory
46 | let multisig: contracts.Multisig.Contract
47 | let proxy: contracts.Proxy.Contract
48 | let registry: contracts.PublicRegistry.Contract
49 | let transferToken: contracts.TransferToken.Contract
50 | let distributeEth: contracts.DistributeEth.Contract
51 | let distributeToken: contracts.DistributeToken.Contract
52 |
53 |
54 |
55 | before(async () => {
56 | sender = accounts[0]
57 | receiver = accounts[1]
58 | alien = accounts[2]
59 |
60 | Multisig.link(ECRecovery)
61 | Multisig.link(LibCommon)
62 | Multisig.link(LibMultisig)
63 | BidirectionalCFLibrary.link(ECRecovery)
64 | BidirectionalCF.link(LibCommon)
65 | BidirectionalCF.link(BidirectionalCFLibrary)
66 | Proxy.link(ProxyLibrary)
67 | Lineup.link(LibCommon)
68 | Lineup.link(BidirectionalCFLibrary)
69 |
70 | bytecodeManager = new BytecodeManager(web3)
71 | await bytecodeManager.addLink(ECRecovery, 'ECRecovery')
72 | await bytecodeManager.addLink(BidirectionalCFLibrary, 'BidirectionalCFLibrary')
73 | await bytecodeManager.addLink(LibCommon, 'LibCommon')
74 | await bytecodeManager.addLink(LibMultisig, 'LibMultisig')
75 |
76 | registry = await PublicRegistry.deployed()
77 | proxy = await Proxy.deployed()
78 | transferToken = await TransferToken.new()
79 | distributeEth = await DistributeEth.new()
80 | distributeToken = await DistributeToken.new()
81 |
82 | registryNonce = util.bufferToHex(Buffer.from('secret'))
83 | })
84 |
85 | beforeEach(async () => {
86 | multisig = await Multisig.new(sender, receiver, {from: sender})
87 | counterFactory = new InstantiationFactory(web3, multisig)
88 | })
89 |
90 | describe('.new', () => {
91 | specify('have a valid sender', async () => {
92 | let _multisig = await gaser.gasDiff('Multisig.new', sender, async () => {
93 | return await Multisig.new(sender, receiver, { from: sender })
94 | })
95 | let actualSender = (await _multisig.state())[0]
96 | assert.equal(actualSender, sender, 'multisig must have a valid sender after construction')
97 | })
98 |
99 | specify('have a valid receiver', async () => {
100 | let actualReceiver = (await multisig.state())[1]
101 | assert.equal(actualReceiver, receiver, 'multisig must have a valid receiver after construction')
102 | })
103 |
104 | specify('have a valid nonce', async () => {
105 | let actualNonce = (await multisig.state())[2].toNumber()
106 | assert.equal(actualNonce, 0, 'multisig must have a valid nonce after construction')
107 | })
108 | })
109 |
110 | describe('.execute', () => {
111 | let amount = new BigNumber.BigNumber(100)
112 |
113 | specify('ok', async () => {
114 | web3.eth.sendTransaction({from: sender, to: multisig.address, value: amount})
115 |
116 | let transfer = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0x', new BigNumber.BigNumber(0), 0)
117 | let execution = multisig.doCall(transfer.destination, transfer.value, transfer.callBytecode, transfer.senderSig, transfer.receiverSig)
118 | // tslint:disable-next-line:await-promise
119 | await assert.isFulfilled(execution, 'transfer transaction')
120 | assert.equal(web3.eth.getBalance(FAKE_ADDRESS_A).toString(), amount.toString())
121 | })
122 |
123 | specify('not if wrong bytecode', async () => {
124 | let t = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0xdead', new BigNumber.BigNumber(0), 0)
125 | // tslint:disable-next-line:await-promise
126 | await assert.isRejected(multisig.doCall(t.destination, t.value, t.callBytecode, t.senderSig, t.receiverSig))
127 | })
128 |
129 | specify('not if wrong senderSig', async () => {
130 | let t = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0x', new BigNumber.BigNumber(0), 0)
131 | // tslint:disable-next-line:await-promise
132 | await assert.isRejected(multisig.doCall(t.destination, t.value, t.callBytecode, '0xdead', t.receiverSig))
133 | })
134 |
135 | specify('not if wrong receiverSig', async () => {
136 | let t = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0x', new BigNumber.BigNumber(0), 0)
137 | // tslint:disable-next-line:await-promise
138 | await assert.isRejected(multisig.doCall(t.destination, t.value, t.callBytecode, t.senderSig, t.receiverSig))
139 | })
140 | })
141 |
142 | describe('.executeDelegate', () => {
143 | let TestDelegatecallArtifact = artifacts.require('TestDelegatecall.sol')
144 | let testDelegatecall: wrappers.TestDelegatecall.Contract
145 | let amount = new BigNumber.BigNumber(100)
146 | let amountA = amount
147 | let amountB = amount.mul(2)
148 |
149 | before(async () => {
150 | testDelegatecall = await TestDelegatecallArtifact.new()
151 | })
152 |
153 | beforeEach(() => {
154 | web3.eth.sendTransaction({from: sender, to: multisig.address, value: amountA.plus(amountB)})
155 | })
156 |
157 | specify('ok', async () => {
158 | let transfer = distributeEth.execute.request(FAKE_ADDRESS_A, FAKE_ADDRESS_B, amountA, amountB)
159 | let command = await counterFactory.delegatecall(transfer)
160 | let beforeA = web3.eth.getBalance(FAKE_ADDRESS_A)
161 | let beforeB = web3.eth.getBalance(FAKE_ADDRESS_B)
162 | // tslint:disable-next-line:await-promise
163 | await assert.isFulfilled(multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig))
164 | let afterA = web3.eth.getBalance(FAKE_ADDRESS_A)
165 | let afterB = web3.eth.getBalance(FAKE_ADDRESS_B)
166 | assert.equal(afterA.minus(beforeA).toString(), amountA.toString())
167 | assert.equal(afterB.minus(beforeB).toString(), amountB.toString())
168 | })
169 |
170 | specify('not if failed call', async () => {
171 | let call = testDelegatecall.execute.request(amountA, amountB)
172 | let command = await counterFactory.delegatecall(call)
173 | // tslint:disable-next-line:await-promise
174 | await assert.isRejected(multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig))
175 | })
176 |
177 | specify('not if wrong bytecode', async () => {
178 | let call = testDelegatecall.execute.request(amountA, amountB)
179 | call.params[0].data = '0xdead'
180 | let command = await counterFactory.delegatecall(call)
181 | // tslint:disable-next-line:await-promise
182 | await assert.isRejected(multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig))
183 | })
184 |
185 | specify('not if wrong signature', async () => {
186 | let call = testDelegatecall.execute.request(amountA, amountB)
187 | let command = await counterFactory.delegatecall(call)
188 | command.callBytecode = '0xdead'
189 | // tslint:disable-next-line:await-promise
190 | await assert.isRejected(multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig))
191 | })
192 | })
193 |
194 |
195 | specify('can instantiate counterfactual contract', async () => {
196 | let probe = 42
197 | let bytecode = bytecodeManager.constructBytecode(TestContract, 42)
198 | let counterfactualAddress = await registry.counterfactualAddress(bytecode, registryNonce)
199 |
200 | // Instantiate
201 | let instantiation = await counterFactory.call(registry.deploy.request(bytecode, registryNonce))
202 | await support.logGas('instantiate test contract', counterFactory.execute(instantiation))
203 |
204 | // Check if instantiated
205 | let address = await registry.resolve(counterfactualAddress)
206 | let testInstance = await TestContract.at(address)
207 | let actualNonce = await testInstance.nonce()
208 | assert.equal(actualNonce.toNumber(), probe)
209 | })
210 |
211 | specify('can send Eth to counterfactual contract', async () => {
212 | let multisig = await Multisig.new(sender, receiver, {from: sender})
213 | let toMultisig = new BigNumber.BigNumber(web3.toWei(1, 'ether'))
214 | let toTestContract = new BigNumber.BigNumber(12)
215 | counterFactory = new InstantiationFactory(web3, multisig)
216 |
217 | let bytecodeTestContract = bytecodeManager.constructBytecode(TestContract, 1)
218 | let counterfactualAddress = await registry.counterfactualAddress(bytecodeTestContract, registryNonce)
219 |
220 | let instantiateTestContract = await counterFactory.call(registry.deploy.request(bytecodeTestContract, registryNonce))
221 | let moveMoney = await counterFactory.delegatecall(proxy.doCall.request(registry.address, counterfactualAddress, toTestContract, '0x00'), instantiateTestContract.nonce.plus(1))
222 |
223 | // Move Eth to Multisig
224 |
225 | await support.assertBalance(multisig, 0)
226 | web3.eth.sendTransaction({ from: sender, to: multisig.address, value: toMultisig }) // TxCheck
227 | await support.assertBalance(multisig, toMultisig)
228 |
229 | // Not Deployed Yet
230 | assert.equal(await registry.resolve(counterfactualAddress), '0x0000000000000000000000000000000000000000')
231 |
232 | await support.logGas('instantiate test contract', counterFactory.execute(instantiateTestContract))
233 |
234 | let address = await registry.resolve(counterfactualAddress)
235 | let testInstance = await TestContract.at(address)
236 |
237 | await support.assertBalance(testInstance, 0)
238 |
239 | // Not moved Eth yet from Multisig to TestContract
240 | await support.assertBalance(multisig, toMultisig)
241 | await support.assertBalance(testInstance, 0)
242 |
243 | // Move Eth to TestContract
244 | await support.logGas('move Eth', counterFactory.execute(moveMoney))
245 | await support.assertBalance(multisig, toMultisig.minus(toTestContract))
246 | await support.assertBalance(testInstance, toTestContract)
247 | })
248 |
249 | specify('can send ERC20 token to counterfactual contract', async () => {
250 | // Prepare
251 | let multisig = await Multisig.new(sender, receiver, {from: sender})
252 | let initialMultisigBalance = new BigNumber.BigNumber(100)
253 | let toTestContract = new BigNumber.BigNumber(12)
254 | counterFactory = new InstantiationFactory(web3, multisig)
255 |
256 | let token = await TestToken.new()
257 | await token.mint(multisig.address, initialMultisigBalance)
258 | await token.finishMinting()
259 |
260 | // Deploy TestContract
261 | let bytecodeTestContract = bytecodeManager.constructBytecode(TestContract, 1)
262 | let counterfactualAddress = await registry.counterfactualAddress(bytecodeTestContract, registryNonce)
263 | let instantiateTestContract = await counterFactory.call(registry.deploy.request(bytecodeTestContract, registryNonce))
264 |
265 | // Move tokens to contract
266 | let transferTokens = await counterFactory.delegatecall(transferToken.execute.request(registry.address, token.address, counterfactualAddress, toTestContract), instantiateTestContract.nonce.plus(1))
267 |
268 | await support.assertTokenBalance(token, multisig.address, initialMultisigBalance)
269 | await support.logGas('instantiate test contract', counterFactory.execute(instantiateTestContract))
270 | let testContractRealAddress = await registry.resolve(counterfactualAddress)
271 | await support.assertTokenBalance(token, multisig.address, initialMultisigBalance)
272 | await support.assertTokenBalance(token, testContractRealAddress, 0)
273 |
274 | await support.logGas('transfer tokens', counterFactory.execute(transferTokens))
275 | await support.assertTokenBalance(token, multisig.address, initialMultisigBalance.minus(toTestContract))
276 | await support.assertTokenBalance(token, testContractRealAddress, toTestContract)
277 | })
278 |
279 | specify('can distribute Eth', async () => {
280 | let multisig = await Multisig.new(sender, receiver, {from: sender})
281 | let toSender = new BigNumber.BigNumber(web3.toWei(3, 'ether'))
282 | let toReceiver = new BigNumber.BigNumber(web3.toWei(2, 'ether'))
283 | counterFactory = new InstantiationFactory(web3, multisig)
284 | let toMultisig = toSender.plus(toReceiver)
285 |
286 | let multisigBefore = web3.eth.getBalance(multisig.address)
287 | web3.eth.sendTransaction({ from: sender, to: multisig.address, value: toMultisig }) // TxCheck
288 | let multisigAfter = web3.eth.getBalance(multisig.address)
289 | assert.equal(multisigAfter.minus(multisigBefore).toString(), toMultisig.toString())
290 |
291 | let distributeEthCommand = await counterFactory.delegatecall(distributeEth.execute.request(sender, receiver, toSender, toReceiver))
292 |
293 | let senderBefore = web3.eth.getBalance(sender)
294 | let receiverBefore = web3.eth.getBalance(receiver)
295 | multisigBefore = web3.eth.getBalance(multisig.address)
296 | await support.logGas('distribute Eth', counterFactory.execute(distributeEthCommand, { from: alien }))
297 | let senderAfter = web3.eth.getBalance(sender)
298 | let receiverAfter = web3.eth.getBalance(receiver)
299 | multisigAfter = web3.eth.getBalance(multisig.address)
300 | assert.equal(senderAfter.minus(senderBefore).toString(), toSender.toString())
301 | assert.equal(receiverAfter.minus(receiverBefore).toString(), toReceiver.toString())
302 | assert.equal(multisigAfter.minus(multisigBefore).toString(), toMultisig.mul(-1).toString())
303 | })
304 |
305 | specify('can distribute ERC20 token', async () => {
306 | let multisig = await Multisig.new(sender, receiver, {from: sender})
307 | let toSender = new BigNumber.BigNumber(web3.toWei(3, 'ether'))
308 | let toReceiver = new BigNumber.BigNumber(web3.toWei(2, 'ether'))
309 | counterFactory = new InstantiationFactory(web3, multisig)
310 | let toMultisig = toSender.plus(toReceiver)
311 |
312 | let token = await TestToken.new()
313 | await token.mint(multisig.address, toMultisig)
314 | await token.finishMinting()
315 |
316 | await support.assertTokenBalance(token, multisig.address, toMultisig)
317 | await support.assertTokenBalance(token, sender, 0)
318 | await support.assertTokenBalance(token, receiver, 0)
319 |
320 | let distributeTokenCommand = await counterFactory.delegatecall(distributeToken.execute.request(token.address, sender, receiver, toSender, toReceiver))
321 |
322 | await support.logGas('distribute tokens', counterFactory.execute(distributeTokenCommand))
323 | await support.assertTokenBalance(token, multisig.address, 0)
324 | await support.assertTokenBalance(token, sender, toSender)
325 | await support.assertTokenBalance(token, receiver, toReceiver)
326 | })
327 | })
328 |
--------------------------------------------------------------------------------
/test/unit/Multisig.test.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import * as Web3 from 'web3'
3 | import * as asPromised from 'chai-as-promised'
4 | import { Address, BytecodeManager, InstantiationFactory } from '../support/index'
5 | import * as support from '../support/index'
6 | import * as BigNumber from 'bignumber.js'
7 | import * as contracts from '../../src/index'
8 | import * as wrappers from '../../build/wrappers'
9 |
10 |
11 | chai.use(asPromised)
12 |
13 | const web3 = (global as any).web3 as Web3
14 | const assert = chai.assert
15 | const gaser = new support.Gaser(web3)
16 |
17 | const FAKE_ADDRESS_A: Address = '0x0a00000000000000000000000000000000000000'
18 | const FAKE_ADDRESS_B: Address = '0x0b00000000000000000000000000000000000000'
19 |
20 | const ECRecovery = artifacts.require('ECRecovery.sol')
21 | const LibCommon = artifacts.require('LibCommon.sol')
22 | const Multisig = artifacts.require('Multisig.sol')
23 | const LibMultisig = artifacts.require('LibMultisig.sol')
24 | const DistributeEth = artifacts.require('DistributeEth.sol')
25 |
26 | Multisig.link(LibCommon)
27 | Multisig.link(LibMultisig)
28 |
29 | contract('Multisig', accounts => {
30 | let sender: string
31 | let receiver: string
32 |
33 | let counterFactory: InstantiationFactory
34 | let multisig: contracts.Multisig.Contract
35 | let distributeEth: contracts.DistributeEth.Contract
36 |
37 | let bytecodeManager = new BytecodeManager(web3)
38 |
39 | before(async () => {
40 | sender = accounts[0]
41 | receiver = accounts[1]
42 |
43 | await bytecodeManager.addLink(ECRecovery, 'ECRecovery')
44 | await bytecodeManager.addLink(LibCommon, 'LibCommon')
45 | await bytecodeManager.addLink(LibMultisig, 'LibMultisig')
46 |
47 | distributeEth = await DistributeEth.new()
48 | })
49 |
50 | beforeEach(async () => {
51 | multisig = await Multisig.new(sender, receiver, {from: sender})
52 | counterFactory = new InstantiationFactory(web3, multisig)
53 | })
54 |
55 | describe('.new', () => {
56 | specify('have a valid sender', async () => {
57 | let _multisig = await gaser.gasDiff('Multisig.new', sender, async () => {
58 | return await Multisig.new(sender, receiver, { from: sender })
59 | })
60 | let actualSender = (await _multisig.state())[0]
61 | assert.equal(actualSender, sender, 'multisig must have a valid sender after construction')
62 | })
63 |
64 | specify('have a valid receiver', async () => {
65 | let actualReceiver = (await multisig.state())[1]
66 | assert.equal(actualReceiver, receiver, 'multisig must have a valid receiver after construction')
67 | })
68 |
69 | specify('have a valid nonce', async () => {
70 | let actualNonce = (await multisig.state())[2].toNumber()
71 | assert.equal(actualNonce, 0, 'multisig must have a valid nonce after construction')
72 | })
73 | })
74 |
75 | describe('.execute', () => {
76 | let amount = new BigNumber.BigNumber(100)
77 |
78 | specify('ok', async () => {
79 | web3.eth.sendTransaction({from: sender, to: multisig.address, value: amount})
80 |
81 | let transfer = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0x', new BigNumber.BigNumber(0), 0)
82 | let execution = multisig.doCall(transfer.destination, transfer.value, transfer.callBytecode, transfer.senderSig, transfer.receiverSig)
83 | // tslint:disable-next-line:await-promise
84 | await assert.isFulfilled(gaser.logGas('Multisig.execute: Transfer Ether', execution), 'transfer transaction')
85 | assert.equal(web3.eth.getBalance(FAKE_ADDRESS_A).toString(), amount.toString())
86 | })
87 |
88 | specify('not if wrong bytecode', async () => {
89 | let t = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0xdead', new BigNumber.BigNumber(0), 0)
90 | // tslint:disable-next-line:await-promise
91 | await assert.isRejected(multisig.doCall(t.destination, t.value, t.callBytecode, t.senderSig, t.receiverSig))
92 | })
93 |
94 | specify('not if wrong senderSig', async () => {
95 | let t = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0x', new BigNumber.BigNumber(0), 0)
96 | // tslint:disable-next-line:await-promise
97 | await assert.isRejected(multisig.doCall(t.destination, t.value, t.callBytecode, '0xdead', t.receiverSig))
98 | })
99 |
100 | specify('not if wrong receiverSig', async () => {
101 | let t = await counterFactory.raw(FAKE_ADDRESS_A, amount, '0x', new BigNumber.BigNumber(0), 0)
102 | // tslint:disable-next-line:await-promise
103 | await assert.isRejected(multisig.doCall(t.destination, t.value, t.callBytecode, t.senderSig, t.receiverSig))
104 | })
105 | })
106 |
107 | describe('.executeDelegate', () => {
108 | let TestDelegatecallArtifact = artifacts.require('TestDelegatecall.sol')
109 | let testDelegatecall: wrappers.TestDelegatecall.Contract
110 | let amount = new BigNumber.BigNumber(100)
111 | let amountA = amount
112 | let amountB = amount.mul(2)
113 |
114 | before(async () => {
115 | testDelegatecall = await TestDelegatecallArtifact.new()
116 | })
117 |
118 | beforeEach(() => {
119 | web3.eth.sendTransaction({from: sender, to: multisig.address, value: amountA.plus(amountB)})
120 | })
121 |
122 | specify('ok', async () => {
123 | let transfer = distributeEth.execute.request(FAKE_ADDRESS_A, FAKE_ADDRESS_B, amountA, amountB)
124 | let command = await counterFactory.delegatecall(transfer)
125 | let beforeA = web3.eth.getBalance(FAKE_ADDRESS_A)
126 | let beforeB = web3.eth.getBalance(FAKE_ADDRESS_B)
127 | let execution = multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig)
128 | // tslint:disable-next-line:await-promise
129 | await assert.isFulfilled(gaser.logGas('multisig.doDelegate: DistributeEth.execute', execution))
130 | let afterA = web3.eth.getBalance(FAKE_ADDRESS_A)
131 | let afterB = web3.eth.getBalance(FAKE_ADDRESS_B)
132 | assert.equal(afterA.minus(beforeA).toString(), amountA.toString())
133 | assert.equal(afterB.minus(beforeB).toString(), amountB.toString())
134 | })
135 |
136 | specify('not if failed call', async () => {
137 | let call = testDelegatecall.execute.request(amountA, amountB)
138 | let command = await counterFactory.delegatecall(call)
139 | // tslint:disable-next-line:await-promise
140 | await assert.isRejected(multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig))
141 | })
142 |
143 | specify('not if wrong bytecode', async () => {
144 | let call = testDelegatecall.execute.request(amountA, amountB)
145 | call.params[0].data = '0xdead'
146 | let command = await counterFactory.delegatecall(call)
147 | // tslint:disable-next-line:await-promise
148 | await assert.isRejected(multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig))
149 | })
150 |
151 | specify('not if wrong signature', async () => {
152 | let call = testDelegatecall.execute.request(amountA, amountB)
153 | let command = await counterFactory.delegatecall(call)
154 | command.callBytecode = '0xdead'
155 | // tslint:disable-next-line:await-promise
156 | await assert.isRejected(multisig.doDelegate(command.destination, command.callBytecode, command.senderSig, command.receiverSig))
157 | })
158 | })
159 | })
160 |
--------------------------------------------------------------------------------
/test/unit/PublicRegistry.test.ts:
--------------------------------------------------------------------------------
1 | import * as Web3 from 'web3'
2 | import * as chai from 'chai'
3 | import * as BigNumber from 'bignumber.js'
4 | import * as asPromised from 'chai-as-promised'
5 | import * as contracts from '../../src/index'
6 | import * as util from 'ethereumjs-util'
7 | import * as support from '../support'
8 | import * as wrappers from '../../build/wrappers'
9 |
10 | chai.use(asPromised)
11 |
12 | const web3 = (global as any).web3 as Web3
13 | const assert = chai.assert
14 | const gaser = new support.Gaser(web3)
15 |
16 | const PublicRegistry = artifacts.require('PublicRegistry.sol')
17 | const TestContract = artifacts.require('TestContract.sol')
18 |
19 | contract('PublicRegistry', accounts => {
20 | let instance: contracts.PublicRegistry.Contract
21 |
22 | let sender = accounts[0]
23 |
24 | before(async () => {
25 | instance = await gaser.gasDiff('PublicRegistry.new', sender, async () => {
26 | return await PublicRegistry.new({ from: sender })
27 | })
28 | })
29 |
30 | let nonce = util.addHexPrefix(new BigNumber.BigNumber(1).toString(16))
31 | let probe = 42
32 | let bytecode = support.constructorBytecode(web3, TestContract, probe)
33 |
34 | describe('.deploy', () => {
35 | specify('instantiate the contract', async () => {
36 | await gaser.gasDiff('TestContract.new', sender, async () => {
37 | await TestContract.new(nonce)
38 | })
39 | let tx = await gaser.logGas('PublicRegistry.deploy: TestContract.new', instance.deploy(bytecode, nonce, { from: accounts[0] }))
40 | let expectedId = await instance.counterfactualAddress(bytecode, nonce)
41 | assert.equal(tx.logs[0].args.id, expectedId)
42 |
43 | let address = tx.logs[0].args.deployed
44 | let testInstance = await TestContract.at(address)
45 | let actualNonce = await testInstance.nonce()
46 | assert.equal(actualNonce.toString(), probe.toString())
47 | })
48 | })
49 |
50 | describe('.resolve', () => {
51 | specify('returns actual address', async () => {
52 | await instance.deploy(bytecode, nonce, { from: accounts[0] })
53 | let counterfactualAddress = await instance.counterfactualAddress(bytecode, nonce)
54 |
55 | let address = await instance.resolve(counterfactualAddress)
56 | let testInstance = await TestContract.at(address)
57 | let actualNonce = await testInstance.nonce()
58 | assert.equal(actualNonce.toString(), probe.toString())
59 | })
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/truffle.js:
--------------------------------------------------------------------------------
1 | const GAS_LIMIT = 2700000
2 |
3 | module.exports = {
4 | networks: {
5 | development: {
6 | network_id: "*",
7 | host: "localhost",
8 | port: 8545,
9 | gas: GAS_LIMIT
10 | },
11 | ropsten: {
12 | network_id: 3,
13 | host: "localhost",
14 | port: 8545,
15 | from: '0x1d612c43acf0c1bf855fe32a0beb782293b1bfb8',
16 | gas: GAS_LIMIT
17 | },
18 | kovan: {
19 | network_id: 42,
20 | host: "localhost",
21 | port: 8545,
22 | gas: GAS_LIMIT
23 | },
24 | main: {
25 | host: "localhost",
26 | port: 8545,
27 | network_id: 1,
28 | gas: GAS_LIMIT
29 | },
30 | rinkeby: {
31 | host: "localhost",
32 | port: 8545,
33 | network_id: 4,
34 | from: '0x13d1be93e913d910245a069c67fc4c45a3d0b2fc',
35 | gas: GAS_LIMIT
36 | }
37 | },
38 | solc: {
39 | optimizer: {
40 | enabled: true,
41 | runs: 200
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "lib": [ "es2017" ],
6 | "sourceMap": true, // Generate corresponding '.map' file
7 | "declaration": true, // Generate corresponding '.d.ts' file
8 | "strict": true, // Enable all strict type-checking options
9 | "noImplicitReturns": true // Report error when not all code paths in function return a value
10 | },
11 | "exclude": [
12 | "node_modules/abstract-leveldown/index.d.ts"
13 | ],
14 | "include": [
15 | "build/wrappers/*.ts",
16 | "node_modules/**/*/index.d.ts",
17 | "src/globals.d.ts",
18 | "src/**/*.ts",
19 | "migrations/**/*.ts",
20 | "test/**/*.ts",
21 | "types/**/*.d.ts"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": "tslint-config-standard",
4 | "rules": {
5 | "no-consecutive-blank-lines": [false]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/types/ethereumjs-abi/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'ethereumjs-abi' {
2 | export function soliditySHA3 (types: string[], values: any[]): Buffer
3 | export function rawEncode (types: string[], values: any[]): Buffer
4 | export function encodeSingle (type: string, value: any): Buffer
5 | export function rawDecode (types: string[], data: Buffer): A
6 | }
7 |
--------------------------------------------------------------------------------