Update Wallet to new Wallet Code (#4805)

* Update Wallet Version

* Update Wallet Library

* Update Wallets Bytecodes

* Typo

* Separate Deploy in Contract API

* Use the new Wallet ABI // Update wallet code

* WIP .// Deploy from Wallet

* Update Wallet contract

* Contract Deployment for Wallet

* Working deployments for Single Owned Wallet contracts

* Linting

* Create a Wallet from a Wallet

* Linting

* Fix Signer transactions // Add Gas Used for transactions

* Deploy wallet contract fix

* Fix too high gas estimate for Wallet Contract Deploys

* Final piece ; deploying from Wallet owned by wallet

* Update Wallet Code

* Updated the Wallet Codes

* Fixing Wallet Deployments

* Add Support for older wallets

* Linting
This commit is contained in:
Nicolas Gotchac 2017-03-07 20:19:55 +01:00 committed by Jaco Greeff
parent 973bb63dca
commit 4d08e7b0ae
20 changed files with 2227 additions and 917 deletions

View File

@ -107,34 +107,26 @@ export default class Contract {
}); });
} }
deploy (options, values, statecb) { deploy (options, values, statecb = () => {}) {
const setState = (state) => { statecb(null, { state: 'estimateGas' });
if (!statecb) {
return;
}
return statecb(null, state);
};
setState({ state: 'estimateGas' });
return this return this
.deployEstimateGas(options, values) .deployEstimateGas(options, values)
.then(([gasEst, gas]) => { .then(([gasEst, gas]) => {
options.gas = gas.toFixed(0); options.gas = gas.toFixed(0);
setState({ state: 'postTransaction', gas }); statecb(null, { state: 'postTransaction', gas });
const _options = this._encodeOptions(this.constructors[0], options, values); const encodedOptions = this._encodeOptions(this.constructors[0], options, values);
return this._api.parity return this._api.parity
.postTransaction(_options) .postTransaction(encodedOptions)
.then((requestId) => { .then((requestId) => {
setState({ state: 'checkRequest', requestId }); statecb(null, { state: 'checkRequest', requestId });
return this._pollCheckRequest(requestId); return this._pollCheckRequest(requestId);
}) })
.then((txhash) => { .then((txhash) => {
setState({ state: 'getTransactionReceipt', txhash }); statecb(null, { state: 'getTransactionReceipt', txhash });
return this._pollTransactionReceipt(txhash, gas); return this._pollTransactionReceipt(txhash, gas);
}) })
.then((receipt) => { .then((receipt) => {
@ -142,14 +134,13 @@ export default class Contract {
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`); throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
} }
setState({ state: 'hasReceipt', receipt }); statecb(null, { state: 'hasReceipt', receipt });
this._receipt = receipt; this._receipt = receipt;
this._address = receipt.contractAddress; this._address = receipt.contractAddress;
return this._address; return this._address;
});
}) })
.then((address) => { .then((address) => {
setState({ state: 'getCode' }); statecb(null, { state: 'getCode' });
return this._api.eth.getCode(this._address); return this._api.eth.getCode(this._address);
}) })
.then((code) => { .then((code) => {
@ -157,9 +148,10 @@ export default class Contract {
throw new Error('Contract not deployed, getCode returned 0x'); throw new Error('Contract not deployed, getCode returned 0x');
} }
setState({ state: 'completed' }); statecb(null, { state: 'completed' });
return this._address; return this._address;
}); });
});
} }
parseEventLogs (logs) { parseEventLogs (logs) {

View File

@ -0,0 +1,466 @@
[
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "removeOwner",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_addr",
"type": "address"
}
],
"name": "isOwner",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_numOwners",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_lastDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "resetSpentToday",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_spentToday",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "addOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_required",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_h",
"type": "bytes32"
}
],
"name": "confirm",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "execute",
"outputs": [
{
"name": "_r",
"type": "bytes32"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
}
],
"name": "revoke",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newRequired",
"type": "uint256"
}
],
"name": "changeRequirement",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
},
{
"name": "_owner",
"type": "address"
}
],
"name": "hasConfirmed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "ownerIndex",
"type": "uint256"
}
],
"name": "getOwner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
}
],
"name": "kill",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "changeOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"inputs": [
{
"name": "_owners",
"type": "address[]"
},
{
"name": "_required",
"type": "uint256"
},
{
"name": "_daylimit",
"type": "uint256"
}
],
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Confirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Revoke",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
}
],
"name": "OwnerRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newRequirement",
"type": "uint256"
}
],
"name": "RequirementChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "_from",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "SingleTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "MultiTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "initiator",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "ConfirmationNeeded",
"type": "event"
}
]

View File

@ -1 +1,476 @@
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}] [
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "removeOwner",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_addr",
"type": "address"
}
],
"name": "isOwner",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_numOwners",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_lastDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "resetSpentToday",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_spentToday",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "addOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_required",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_h",
"type": "bytes32"
}
],
"name": "confirm",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "execute",
"outputs": [
{
"name": "_r",
"type": "bytes32"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
}
],
"name": "revoke",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newRequired",
"type": "uint256"
}
],
"name": "changeRequirement",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
},
{
"name": "_owner",
"type": "address"
}
],
"name": "hasConfirmed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "ownerIndex",
"type": "uint256"
}
],
"name": "getOwner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
}
],
"name": "kill",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "changeOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"inputs": [
{
"name": "_owners",
"type": "address[]"
},
{
"name": "_required",
"type": "uint256"
},
{
"name": "_daylimit",
"type": "uint256"
}
],
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Confirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Revoke",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
}
],
"name": "OwnerRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newRequirement",
"type": "uint256"
}
],
"name": "RequirementChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "_from",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
},
{
"indexed": false,
"name": "created",
"type": "address"
}
],
"name": "SingleTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
},
{
"indexed": false,
"name": "created",
"type": "address"
}
],
"name": "MultiTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "initiator",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "ConfirmationNeeded",
"type": "event"
}
]

File diff suppressed because one or more lines are too long

View File

@ -8,12 +8,13 @@
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed. // interior is executed.
pragma solidity ^0.4.6;
contract multisig { pragma solidity ^0.4.9;
contract WalletEvents {
// EVENTS // EVENTS
// this contract can accept a confirmation, in which case // this contract only has six types of events: it can accept a confirmation, in which case
// we record owner and operation (hash) alongside it. // we record owner and operation (hash) alongside it.
event Confirmation(address owner, bytes32 operation); event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation); event Revoke(address owner, bytes32 operation);
@ -29,38 +30,38 @@ contract multisig {
// Funds has arrived into the wallet (record how much). // Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value); event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data); event SingleTransact(address owner, uint value, address to, bytes data, address created);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
// Confirmation still needed for a transaction. // Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
} }
contract multisigAbi is multisig { contract WalletAbi {
function isOwner(address _addr) returns (bool); // Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external;
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool); // Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) external;
function confirm(bytes32 _h) returns(bool); function addOwner(address _owner) external;
function removeOwner(address _owner) external;
function changeRequirement(uint _newRequired) external;
function isOwner(address _addr) constant returns (bool);
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit); function setDailyLimit(uint _newLimit) external;
function addOwner(address _owner); function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) returns (bool o_success);
function removeOwner(address _owner);
function changeRequirement(uint _newRequired);
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation);
function changeOwner(address _from, address _to);
function execute(address _to, uint _value, bytes _data) returns(bool);
} }
contract WalletLibrary is multisig { contract WalletLibrary is WalletEvents {
// TYPES // TYPES
// struct for the status of a pending operation. // struct for the status of a pending operation.
@ -77,10 +78,6 @@ contract WalletLibrary is multisig {
bytes data; bytes data;
} }
/******************************
***** MULTI OWNED SECTION ****
******************************/
// MODIFIERS // MODIFIERS
// simple single-sig function modifier. // simple single-sig function modifier.
@ -98,23 +95,29 @@ contract WalletLibrary is multisig {
// METHODS // METHODS
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// constructor is given number of sigs required to do protected "onlymanyowners" transactions // constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them. // as well as the selection of addresses capable of confirming them.
function initMultiowned(address[] _owners, uint _required) { function initMultiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1; m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender); m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1; m_ownerIndex[uint(msg.sender)] = 1;
m_required = _required;
for (uint i = 0; i < _owners.length; ++i) for (uint i = 0; i < _owners.length; ++i)
{ {
m_owners[2 + i] = uint(_owners[i]); m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i; m_ownerIndex[uint(_owners[i])] = 2 + i;
} }
m_required = _required;
} }
// Revokes a prior confirmation of the given operation // Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) { function revoke(bytes32 _operation) external {
uint ownerIndex = m_ownerIndex[uint(msg.sender)]; uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner // make sure they're an owner
if (ownerIndex == 0) return; if (ownerIndex == 0) return;
@ -128,7 +131,7 @@ contract WalletLibrary is multisig {
} }
// Replaces an owner `_from` with another `_to`. // Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) { function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_to)) return; if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)]; uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return; if (ownerIndex == 0) return;
@ -140,7 +143,7 @@ contract WalletLibrary is multisig {
OwnerChanged(_from, _to); OwnerChanged(_from, _to);
} }
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) { function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return; if (isOwner(_owner)) return;
clearPending(); clearPending();
@ -154,7 +157,7 @@ contract WalletLibrary is multisig {
OwnerAdded(_owner); OwnerAdded(_owner);
} }
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) { function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)]; uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return; if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return; if (m_required > m_numOwners - 1) return;
@ -166,19 +169,23 @@ contract WalletLibrary is multisig {
OwnerRemoved(_owner); OwnerRemoved(_owner);
} }
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) { function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return; if (_newRequired > m_numOwners) return;
m_required = _newRequired; m_required = _newRequired;
clearPending(); clearPending();
RequirementChanged(_newRequired); RequirementChanged(_newRequired);
} }
function isOwner(address _addr) returns (bool) { // Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) constant returns (bool) {
return m_ownerIndex[uint(_addr)] > 0; return m_ownerIndex[uint(_addr)] > 0;
} }
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
var pending = m_pending[_operation]; var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)]; uint ownerIndex = m_ownerIndex[uint(_owner)];
@ -190,6 +197,88 @@ contract WalletLibrary is multisig {
return !(pending.ownersDone & ownerIndexBit == 0); return !(pending.ownersDone & ownerIndexBit == 0);
} }
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
}
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
suicide(_to);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
// first, take the opportunity to check that we're under the daily limit.
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
// yes - just execute the call.
address created;
if (_to == 0) {
created = create(_value, _data);
} else {
if (!_to.call.value(_value)(_data))
throw;
}
SingleTransact(msg.sender, _value, _to, _data, created);
} else {
// determine our operation hash.
o_hash = sha3(msg.data, block.number);
// store if it's new
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
m_txs[o_hash].to = _to;
m_txs[o_hash].value = _value;
m_txs[o_hash].data = _data;
}
if (!confirm(o_hash)) {
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
}
}
}
function create(uint _value, bytes _code) internal returns (address o_addr) {
assembly {
o_addr := create(_value, add(_code, 0x20), mload(_code))
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
address created;
if (m_txs[_h].to == 0) {
created = create(m_txs[_h].value, m_txs[_h].data);
} else {
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
throw;
}
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS // INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) { function confirmAndCheck(bytes32 _operation) internal returns (bool) {
@ -244,45 +333,6 @@ contract WalletLibrary is multisig {
} }
} }
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
delete m_pendingIndex;
}
/******************************
****** DAY LIMIT SECTION *****
******************************/
// MODIFIERS
// simple modifier for daily limit.
modifier limitedDaily(uint _value) {
if (underLimit(_value))
_;
}
// METHODS
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) {
m_spentToday = 0;
}
// INTERNAL METHODS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false. // returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) { function underLimit(uint _value) internal onlyowner returns (bool) {
@ -303,74 +353,26 @@ contract WalletLibrary is multisig {
// determines today's index. // determines today's index.
function today() private constant returns (uint) { return now / 1 days; } function today() private constant returns (uint) { return now / 1 days; }
function clearPending() internal {
/******************************
********* WALLET SECTION *****
******************************/
// METHODS
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initMultiowned(_owners, _required);
initDaylimit(_daylimit) ;
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) {
suicide(_to);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) {
// first, take the opportunity to check that we're under the daily limit.
if (underLimit(_value)) {
SingleTransact(msg.sender, _value, _to, _data);
// yes - just execute the call.
_callValue =_to.call.value(_value)(_data);
} else {
// determine our operation hash.
bytes32 _r = sha3(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) {
m_txs[_r].to = _to;
m_txs[_r].value = _value;
m_txs[_r].data = _data;
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
}
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
if (m_txs[_h].to != 0) {
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS
function clearWalletPending() internal {
uint length = m_pendingIndex.length; uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
for (uint i = 0; i < length; ++i) {
delete m_txs[m_pendingIndex[i]]; delete m_txs[m_pendingIndex[i]];
clearPending();
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
}
delete m_pendingIndex;
} }
// FIELDS // FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// the number of owners that must confirm the same operation before it is run. // the number of owners that must confirm the same operation before it is run.
uint m_required; uint public m_required;
// pointer used to find a free slot in m_owners // pointer used to find a free slot in m_owners
uint m_numOwners; uint public m_numOwners;
uint public m_dailyLimit; uint public m_dailyLimit;
uint public m_spentToday; uint public m_spentToday;
@ -378,8 +380,8 @@ contract WalletLibrary is multisig {
// list of owners // list of owners
uint[256] m_owners; uint[256] m_owners;
uint constant c_maxOwners = 250;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup // index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex; mapping(uint => uint) m_ownerIndex;
// the ongoing operations. // the ongoing operations.
@ -390,8 +392,7 @@ contract WalletLibrary is multisig {
mapping (bytes32 => Transaction) m_txs; mapping (bytes32 => Transaction) m_txs;
} }
contract Wallet is WalletEvents {
contract Wallet is multisig {
// WALLET CONSTRUCTOR // WALLET CONSTRUCTOR
// calls the `initWallet` method of the Library in this context // calls the `initWallet` method of the Library in this context
@ -435,11 +436,11 @@ contract Wallet is multisig {
// As return statement unavailable in fallback, explicit the method here // As return statement unavailable in fallback, explicit the method here
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
return _walletLibrary.delegatecall(msg.data); return _walletLibrary.delegatecall(msg.data);
} }
function isOwner(address _addr) returns (bool) { function isOwner(address _addr) constant returns (bool) {
return _walletLibrary.delegatecall(msg.data); return _walletLibrary.delegatecall(msg.data);
} }

View File

@ -8,7 +8,8 @@
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed. // interior is executed.
pragma solidity ^0.4.6;
pragma solidity ^0.4.9;
contract multiowned { contract multiowned {
@ -130,7 +131,7 @@ contract multiowned {
return address(m_owners[ownerIndex + 1]); return address(m_owners[ownerIndex + 1]);
} }
function isOwner(address _addr) returns (bool) { function isOwner(address _addr) constant returns (bool) {
return m_ownerIndex[uint(_addr)] > 0; return m_ownerIndex[uint(_addr)] > 0;
} }
@ -230,14 +231,6 @@ contract multiowned {
// uses is specified in the modifier. // uses is specified in the modifier.
contract daylimit is multiowned { contract daylimit is multiowned {
// MODIFIERS
// simple modifier for daily limit.
modifier limitedDaily(uint _value) {
if (underLimit(_value))
_;
}
// METHODS // METHODS
// constructor - stores initial daily limit and records the present day's index. // constructor - stores initial daily limit and records the present day's index.
@ -291,18 +284,17 @@ contract multisig {
// Funds has arrived into the wallet (record how much). // Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value); event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data); event SingleTransact(address owner, uint value, address to, bytes data, address created);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
// Confirmation still needed for a transaction. // Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
// FUNCTIONS // FUNCTIONS
// TODO: document // TODO: document
function changeOwner(address _from, address _to) external; function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function execute(address _to, uint _value, bytes _data) external returns (bytes32); function confirm(bytes32 _h) external returns (bool o_success);
function confirm(bytes32 _h) returns (bool);
} }
// usage: // usage:
@ -343,30 +335,53 @@ contract Wallet is multisig, multiowned, daylimit {
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide // If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways. // and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) { function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
// first, take the opportunity to check that we're under the daily limit. // first, take the opportunity to check that we're under the daily limit.
if (underLimit(_value)) { if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
SingleTransact(msg.sender, _value, _to, _data);
// yes - just execute the call. // yes - just execute the call.
_to.call.value(_value)(_data); address created;
return 0; if (_to == 0) {
created = create(_value, _data);
} else {
if (!_to.call.value(_value)(_data))
throw;
} }
SingleTransact(msg.sender, _value, _to, _data, created);
} else {
// determine our operation hash. // determine our operation hash.
_r = sha3(msg.data, block.number); o_hash = sha3(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) { // store if it's new
m_txs[_r].to = _to; if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
m_txs[_r].value = _value; m_txs[o_hash].to = _to;
m_txs[_r].data = _data; m_txs[o_hash].value = _value;
ConfirmationNeeded(_r, msg.sender, _value, _to, _data); m_txs[o_hash].data = _data;
}
if (!confirm(o_hash)) {
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
}
}
}
function create(uint _value, bytes _code) internal returns (address o_addr) {
assembly {
o_addr := create(_value, add(_code, 0x20), mload(_code))
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
} }
} }
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided. // to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
if (m_txs[_h].to != 0) { if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); address created;
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); if (m_txs[_h].to == 0) {
created = create(m_txs[_h].value, m_txs[_h].data);
} else {
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
throw;
}
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
delete m_txs[_h]; delete m_txs[_h];
return true; return true;
} }

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { omitBy } from 'lodash';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -106,9 +105,6 @@ export default class WalletDetails extends Component {
renderMultisigDetails () { renderMultisigDetails () {
const { accounts, wallet, errors } = this.props; const { accounts, wallet, errors } = this.props;
// Wallets cannot create contracts
const _accounts = omitBy(accounts, (a) => a.wallet);
return ( return (
<Form> <Form>
<Input <Input
@ -148,7 +144,7 @@ export default class WalletDetails extends Component {
/> />
<AddressSelect <AddressSelect
accounts={ _accounts } accounts={ accounts }
error={ errors.account } error={ errors.account }
hint={ hint={
<FormattedMessage <FormattedMessage

View File

@ -22,10 +22,11 @@ import Contract from '~/api/contract';
import { ERROR_CODES } from '~/api/transport/error'; import { ERROR_CODES } from '~/api/transport/error';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { wallet as walletAbi } from '~/contracts/abi'; import { wallet as walletAbi } from '~/contracts/abi';
import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet'; import { wallet as walletCode, walletLibrary as walletLibraryCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
import { validateUint, validateAddress, validateName } from '~/util/validation'; import { validateUint, validateAddress, validateName } from '~/util/validation';
import { toWei } from '~/api/util/wei'; import { toWei } from '~/api/util/wei';
import { deploy } from '~/util/tx';
import WalletsUtils from '~/util/wallets'; import WalletsUtils from '~/util/wallets';
const STEPS = { const STEPS = {
@ -179,6 +180,8 @@ export default class CreateWalletStore {
this.wallet.owners = owners; this.wallet.owners = owners;
this.wallet.required = require.toNumber(); this.wallet.required = require.toNumber();
this.wallet.dailylimit = dailylimit.limit; this.wallet.dailylimit = dailylimit.limit;
this.wallet = this.getWalletWithMeta(this.wallet);
}); });
return this.addWallet(this.wallet); return this.addWallet(this.wallet);
@ -202,21 +205,51 @@ export default class CreateWalletStore {
return null; // exception when registry is not available return null; // exception when registry is not available
}) })
.then((address) => { .then((address) => {
const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase(); if (!address || /^(0x)?0*$/.test(address)) {
const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress) return null;
? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress) }
: fullWalletCode;
// Check that it's actually the expected code
return this.api.eth
.getCode(address)
.then((code) => {
const strippedCode = code.replace(/^0x/, '');
// The actual deployed code is included in the wallet
// library code (which might have some more data)
if (walletLibraryCode.indexOf(strippedCode) >= 0) {
return address;
}
return null;
});
})
.then((address) => {
let code = fullWalletCode;
if (address) {
const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase();
code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress);
} else {
console.warn('wallet library has not been found in the registry');
}
const options = { const options = {
data: code, data: code,
from: account from: account
}; };
return this.api const contract = this.api.newContract(walletAbi);
.newContract(walletAbi)
.deploy(options, [ owners, required, daylimit ], this.onDeploymentState); this.wallet = this.getWalletWithMeta(this.wallet);
return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState);
}) })
.then((address) => { .then((address) => {
if (!address || /^(0x)?0*$/.test(address)) {
return false;
}
this.deployed = true; this.deployed = true;
this.wallet.address = address; this.wallet.address = address;
return this.addWallet(this.wallet); return this.addWallet(this.wallet);
@ -233,26 +266,37 @@ export default class CreateWalletStore {
} }
@action addWallet = (wallet) => { @action addWallet = (wallet) => {
const { address, name, description } = wallet; const { address, name, metadata } = wallet;
return Promise return Promise
.all([ .all([
this.api.parity.setAccountName(address, name), this.api.parity.setAccountName(address, name),
this.api.parity.setAccountMeta(address, { this.api.parity.setAccountMeta(address, metadata)
abi: walletAbi,
wallet: true,
timestamp: Date.now(),
deleted: false,
description,
name,
tags: ['wallet']
})
]) ])
.then(() => { .then(() => {
this.step = 'INFO'; this.step = 'INFO';
}); });
} }
getWalletWithMeta = (wallet) => {
const { name, description } = wallet;
const metadata = {
abi: walletAbi,
wallet: true,
timestamp: Date.now(),
deleted: false,
tags: [ 'wallet' ],
description,
name
};
return {
...wallet,
metadata
};
}
onDeploymentState = (error, data) => { onDeploymentState = (error, data) => {
if (error) { if (error) {
return console.error('createWallet::onDeploymentState', error); return console.error('createWallet::onDeploymentState', error);
@ -298,6 +342,15 @@ export default class CreateWalletStore {
); );
return; return;
case 'confirmationNeeded':
this.deployState = (
<FormattedMessage
id='createWallet.states.confirmationNeeded'
defaultMessage='The contract deployment needs confirmations from other owners of the Wallet'
/>
);
return;
case 'completed': case 'completed':
this.deployState = ( this.deployState = (
<FormattedMessage <FormattedMessage

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { pick, omitBy } from 'lodash'; import { pick } from 'lodash';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -23,6 +23,7 @@ import { connect } from 'react-redux';
import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui'; import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
import { CancelIcon, DoneIcon } from '~/ui/Icons'; import { CancelIcon, DoneIcon } from '~/ui/Icons';
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation'; import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
import { deploy, deployEstimateGas } from '~/util/tx';
import DetailsStep from './DetailsStep'; import DetailsStep from './DetailsStep';
import ParametersStep from './ParametersStep'; import ParametersStep from './ParametersStep';
@ -73,7 +74,7 @@ class DeployContract extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired, api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired store: PropTypes.object.isRequired
} };
static propTypes = { static propTypes = {
accounts: PropTypes.object.isRequired, accounts: PropTypes.object.isRequired,
@ -422,9 +423,9 @@ class DeployContract extends Component {
from: fromAddress from: fromAddress
}; };
api const contract = api.newContract(abiParsed);
.newContract(abiParsed)
.deployEstimateGas(options, params) deployEstimateGas(contract, options, params)
.then(([gasEst, gas]) => { .then(([gasEst, gas]) => {
this.gasStore.setEstimated(gasEst.toFixed(0)); this.gasStore.setEstimated(gasEst.toFixed(0));
this.gasStore.setGas(gas.toFixed(0)); this.gasStore.setGas(gas.toFixed(0));
@ -490,6 +491,17 @@ class DeployContract extends Component {
const { api, store } = this.context; const { api, store } = this.context;
const { source } = this.props; const { source } = this.props;
const { abiParsed, code, description, name, params, fromAddress } = this.state; const { abiParsed, code, description, name, params, fromAddress } = this.state;
const metadata = {
abi: abiParsed,
contract: true,
deleted: false,
timestamp: Date.now(),
name,
description,
source
};
const options = { const options = {
data: code, data: code,
from: fromAddress from: fromAddress
@ -499,28 +511,25 @@ class DeployContract extends Component {
const contract = api.newContract(abiParsed); const contract = api.newContract(abiParsed);
contract deploy(contract, options, params, metadata, this.onDeploymentState)
.deploy(options, params, this.onDeploymentState)
.then((address) => { .then((address) => {
const blockNumber = contract._receipt // No contract address given, might need some confirmations
// from the wallet owners...
if (!address || /^(0x)?0*$/.test(address)) {
return false;
}
metadata.blockNumber = contract._receipt
? contract.receipt.blockNumber.toNumber() ? contract.receipt.blockNumber.toNumber()
: null; : null;
return Promise.all([ return Promise.all([
api.parity.setAccountName(address, name), api.parity.setAccountName(address, name),
api.parity.setAccountMeta(address, { api.parity.setAccountMeta(address, metadata)
abi: abiParsed,
contract: true,
timestamp: Date.now(),
deleted: false,
blockNumber,
description,
source
})
]) ])
.then(() => { .then(() => {
console.log(`contract deployed at ${address}`); console.log(`contract deployed at ${address}`);
this.setState({ step: 'DEPLOYMENT', address }); this.setState({ step: 'COMPLETED', address });
}); });
}) })
.catch((error) => { .catch((error) => {
@ -589,6 +598,17 @@ class DeployContract extends Component {
}); });
return; return;
case 'confirmationNeeded':
this.setState({
deployState: (
<FormattedMessage
id='deployContract.state.confirmationNeeded'
defaultMessage='The operation needs confirmations from the other owners of the contract'
/>
)
});
return;
case 'completed': case 'completed':
this.setState({ this.setState({
deployState: ( deployState: (
@ -614,17 +634,14 @@ class DeployContract extends Component {
function mapStateToProps (initState, initProps) { function mapStateToProps (initState, initProps) {
const { accounts } = initProps; const { accounts } = initProps;
// Skip Wallet accounts : they can't create Contracts const fromAddresses = Object.keys(accounts);
const _accounts = omitBy(accounts, (a) => a.wallet);
const fromAddresses = Object.keys(_accounts);
return (state) => { return (state) => {
const balances = pick(state.balances.balances, fromAddresses); const balances = pick(state.balances.balances, fromAddresses);
const { gasLimit } = state.nodeStatus; const { gasLimit } = state.nodeStatus;
return { return {
accounts: _accounts, accounts,
balances, balances,
gasLimit gasLimit
}; };

View File

@ -401,6 +401,7 @@ class WalletSettings extends Component {
const cancelBtn = ( const cancelBtn = (
<Button <Button
icon={ <CancelIcon /> } icon={ <CancelIcon /> }
key='cancelBtn'
label={ label={
<FormattedMessage <FormattedMessage
id='walletSettings.buttons.cancel' id='walletSettings.buttons.cancel'
@ -414,6 +415,7 @@ class WalletSettings extends Component {
const closeBtn = ( const closeBtn = (
<Button <Button
icon={ <CancelIcon /> } icon={ <CancelIcon /> }
key='closeBtn'
label={ label={
<FormattedMessage <FormattedMessage
id='walletSettings.buttons.close' id='walletSettings.buttons.close'
@ -427,6 +429,7 @@ class WalletSettings extends Component {
const sendingBtn = ( const sendingBtn = (
<Button <Button
icon={ <DoneIcon /> } icon={ <DoneIcon /> }
key='sendingBtn'
label={ label={
<FormattedMessage <FormattedMessage
id='walletSettings.buttons.sending' id='walletSettings.buttons.sending'
@ -440,6 +443,7 @@ class WalletSettings extends Component {
const nextBtn = ( const nextBtn = (
<Button <Button
icon={ <NextIcon /> } icon={ <NextIcon /> }
key='nextBtn'
label={ label={
<FormattedMessage <FormattedMessage
id='walletSettings.buttons.next' id='walletSettings.buttons.next'
@ -454,6 +458,7 @@ class WalletSettings extends Component {
const sendBtn = ( const sendBtn = (
<Button <Button
icon={ <NextIcon /> } icon={ <NextIcon /> }
key='sendBtn'
label={ label={
<FormattedMessage <FormattedMessage
id='walletSettings.buttons.send' id='walletSettings.buttons.send'

View File

@ -49,7 +49,7 @@ export default class Personal {
.filter((address) => { .filter((address) => {
const account = accountsInfo[address]; const account = accountsInfo[address];
return !account.uuid && account.meta.deleted; return !account.uuid && account.meta && account.meta.deleted;
}) })
.map((address) => this._api.parity.removeAddress(address)) .map((address) => this._api.parity.removeAddress(address))
); );

View File

@ -26,7 +26,6 @@ import WalletsUtils from '~/util/wallets';
import { wallet as WalletAbi } from '~/contracts/abi'; import { wallet as WalletAbi } from '~/contracts/abi';
export function personalAccountsInfo (accountsInfo) { export function personalAccountsInfo (accountsInfo) {
const addresses = [];
const accounts = {}; const accounts = {};
const contacts = {}; const contacts = {};
const contracts = {}; const contracts = {};
@ -35,10 +34,9 @@ export function personalAccountsInfo (accountsInfo) {
Object.keys(accountsInfo || {}) Object.keys(accountsInfo || {})
.map((address) => Object.assign({}, accountsInfo[address], { address })) .map((address) => Object.assign({}, accountsInfo[address], { address }))
.filter((account) => account.uuid || !account.meta.deleted) .filter((account) => account.meta && (account.uuid || !account.meta.deleted))
.forEach((account) => { .forEach((account) => {
if (account.uuid) { if (account.uuid) {
addresses.push(account.address);
accounts[account.address] = account; accounts[account.address] = account;
} else if (account.meta.wallet) { } else if (account.meta.wallet) {
account.wallet = true; account.wallet = true;
@ -87,18 +85,45 @@ export function personalAccountsInfo (accountsInfo) {
return []; return [];
}) })
.then((_wallets) => { .then((_wallets) => {
_wallets.forEach((wallet) => { // We want to separate owned wallets and other wallets
// However, wallets can be owned by wallets, that can
// be owned by an account...
let otherWallets = [].concat(_wallets);
let prevLength;
let nextLength;
// If no more other wallets, or if the size decreased, continue...
do {
prevLength = otherWallets.length;
otherWallets = otherWallets
.map((wallet) => {
const addresses = Object.keys(accounts);
const owners = wallet.owners.map((o) => o.address); const owners = wallet.owners.map((o) => o.address);
// Owners ∩ Addresses not null : Wallet is owned // Owners ∩ Addresses not null : Wallet is owned
// by one of the accounts // by one of the accounts
if (intersection(owners, addresses).length > 0) { if (intersection(owners, addresses).length > 0) {
accounts[wallet.address] = wallet; accounts[wallet.address] = wallet;
} else { return false;
contacts[wallet.address] = wallet;
} }
return wallet;
})
.filter((wallet) => wallet);
nextLength = otherWallets.length;
} while (nextLength < prevLength);
// And other wallets to contacts...
otherWallets.forEach((wallet) => {
contacts[wallet.address] = wallet;
}); });
// Cache the _real_ accounts for
// WalletsUtils (used for sending transactions)
WalletsUtils.cacheAccounts(accounts);
dispatch(_personalAccountsInfo({ dispatch(_personalAccountsInfo({
accountsInfo, accountsInfo,
accounts, accounts,

View File

@ -431,22 +431,7 @@ function parseLogs (logs) {
return; return;
} }
const { wallet } = getState(); const WalletSignatures = WalletsUtils.getWalletSignatures();
const { contract } = wallet;
const walletInstance = contract.instance;
const signatures = {
OwnerChanged: toHex(walletInstance.OwnerChanged.signature),
OwnerAdded: toHex(walletInstance.OwnerAdded.signature),
OwnerRemoved: toHex(walletInstance.OwnerRemoved.signature),
RequirementChanged: toHex(walletInstance.RequirementChanged.signature),
Confirmation: toHex(walletInstance.Confirmation.signature),
Revoke: toHex(walletInstance.Revoke.signature),
Deposit: toHex(walletInstance.Deposit.signature),
SingleTransact: toHex(walletInstance.SingleTransact.signature),
MultiTransact: toHex(walletInstance.MultiTransact.signature),
ConfirmationNeeded: toHex(walletInstance.ConfirmationNeeded.signature)
};
const updates = {}; const updates = {};
@ -459,25 +444,25 @@ function parseLogs (logs) {
}; };
switch (eventSignature) { switch (eventSignature) {
case signatures.OwnerChanged: case WalletSignatures.OwnerChanged:
case signatures.OwnerAdded: case WalletSignatures.OwnerAdded:
case signatures.OwnerRemoved: case WalletSignatures.OwnerRemoved:
updates[address] = { updates[address] = {
...prev, ...prev,
[ UPDATE_OWNERS ]: true [ UPDATE_OWNERS ]: true
}; };
return; return;
case signatures.RequirementChanged: case WalletSignatures.RequirementChanged:
updates[address] = { updates[address] = {
...prev, ...prev,
[ UPDATE_REQUIRE ]: true [ UPDATE_REQUIRE ]: true
}; };
return; return;
case signatures.ConfirmationNeeded: case WalletSignatures.ConfirmationNeeded:
case signatures.Confirmation: case WalletSignatures.Confirmation:
case signatures.Revoke: case WalletSignatures.Revoke:
const operation = bytesToHex(log.params.operation.value); const operation = bytesToHex(log.params.operation.value);
updates[address] = { updates[address] = {
@ -489,9 +474,11 @@ function parseLogs (logs) {
return; return;
case signatures.Deposit: case WalletSignatures.Deposit:
case signatures.SingleTransact: case WalletSignatures.SingleTransact:
case signatures.MultiTransact: case WalletSignatures.MultiTransact:
case WalletSignatures.Old.SingleTransact:
case WalletSignatures.Old.MultiTransact:
updates[address] = { updates[address] = {
...prev, ...prev,
[ UPDATE_TRANSACTIONS ]: true [ UPDATE_TRANSACTIONS ]: true

View File

@ -120,6 +120,18 @@ class MethodDecoding extends Component {
<span className={ styles.highlight }> <span className={ styles.highlight }>
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>) { gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
</span> </span>
{
transaction.gasUsed
? (
<span>
<span>used</span>
<span className={ styles.highlight }>
{ transaction.gasUsed.toFormat(0) } gas
</span>
</span>
)
: null
}
<span> for a total transaction value of </span> <span> for a total transaction value of </span>
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span> <span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
{ this.renderMinBlock() } { this.renderMinBlock() }

View File

@ -51,6 +51,7 @@ export default class Store {
return bnB.comparedTo(bnA); return bnB.comparedTo(bnA);
}); });
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0)); this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
}); });
} }
@ -85,26 +86,53 @@ export default class Store {
this._subscriptionId = 0; this._subscriptionId = 0;
} }
loadTransactions (_txhashes) { loadTransactions (_txhashes = []) {
const txhashes = _txhashes.filter((hash) => !this.transactions[hash] || this._pendingHashes.includes(hash)); const promises = _txhashes
.filter((txhash) => !this.transactions[txhash] || this._pendingHashes.includes(txhash))
.map((txhash) => {
return Promise
.all([
this._api.eth.getTransactionByHash(txhash),
this._api.eth.getTransactionReceipt(txhash)
])
.then(([
transaction = {},
transactionReceipt = {}
]) => {
return {
...transactionReceipt,
...transaction
};
});
});
if (!txhashes || !txhashes.length) { if (!promises.length) {
return; return;
} }
Promise Promise
.all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash))) .all(promises)
.then((_transactions) => { .then((_transactions) => {
const transactions = _transactions.filter((tx) => tx); const blockNumbers = [];
const transactions = _transactions
.filter((tx) => tx && tx.hash)
.reduce((txs, tx) => {
txs[tx.hash] = tx;
this.addTransactions( if (tx.blockNumber && tx.blockNumber.gt(0)) {
transactions.reduce((transactions, tx, index) => { blockNumbers.push(tx.blockNumber.toNumber());
transactions[txhashes[index]] = tx; }
return transactions;
}, {})
);
this.loadBlocks(transactions.map((tx) => tx.blockNumber ? tx.blockNumber.toNumber() : 0)); return txs;
}, {});
// No need to add transactions if there are none
if (Object.keys(transactions).length === 0) {
return false;
}
this.addTransactions(transactions);
this.loadBlocks(blockNumbers);
}) })
.catch((error) => { .catch((error) => {
console.warn('loadTransactions', error); console.warn('loadTransactions', error);

View File

@ -27,7 +27,7 @@ import styles from './txList.css';
class TxList extends Component { class TxList extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired
} };
static propTypes = { static propTypes = {
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
@ -36,7 +36,7 @@ class TxList extends Component {
PropTypes.object PropTypes.object
]).isRequired, ]).isRequired,
netVersion: PropTypes.string.isRequired netVersion: PropTypes.string.isRequired
} };
store = new Store(this.context.api); store = new Store(this.context.api);

View File

@ -61,17 +61,6 @@ export function estimateGas (_func, _options, _values = []) {
const { func, options, values } = callArgs; const { func, options, values } = callArgs;
return func._estimateGas(options, values); return func._estimateGas(options, values);
})
.then((gas) => {
return WalletsUtils
.isWallet(_func.contract.api, _options.from)
.then((isWallet) => {
if (isWallet) {
return gas.mul(1.5);
}
return gas;
});
}); });
} }
@ -84,6 +73,114 @@ export function postTransaction (_func, _options, _values = []) {
}); });
} }
export function deploy (contract, _options, values, metadata = {}, statecb = () => {}) {
const options = { ..._options };
const { api } = contract;
const address = options.from;
return WalletsUtils
.isWallet(api, address)
.then((isWallet) => {
if (!isWallet) {
return contract.deploy(options, values, statecb);
}
statecb(null, { state: 'estimateGas' });
return deployEstimateGas(contract, options, values)
.then(([gasEst, gas]) => {
options.gas = gas.toFixed(0);
statecb(null, { state: 'postTransaction', gas });
return WalletsUtils.getDeployArgs(contract, options, values);
})
.then((callArgs) => {
const { func, options, values } = callArgs;
return func._postTransaction(options, values)
.then((requestId) => {
statecb(null, { state: 'checkRequest', requestId });
return contract._pollCheckRequest(requestId);
})
.then((txhash) => {
statecb(null, { state: 'getTransactionReceipt', txhash });
return contract._pollTransactionReceipt(txhash, options.gas);
})
.then((receipt) => {
if (receipt.gasUsed.eq(options.gas)) {
throw new Error(`Contract not deployed, gasUsed == ${options.gas.toFixed(0)}`);
}
const logs = WalletsUtils.parseLogs(api, receipt.logs || []);
const confirmationLog = logs.find((log) => log.event === 'ConfirmationNeeded');
const transactionLog = logs.find((log) => log.event === 'SingleTransact');
if (!confirmationLog && !transactionLog) {
throw new Error('Something went wrong in the Wallet Contract (no logs have been emitted)...');
}
// Confirmations are needed from the other owners
if (confirmationLog) {
const operationHash = api.util.bytesToHex(confirmationLog.params.operation.value);
// Add the contract to pending contracts
WalletsUtils.addPendingContract(address, operationHash, metadata);
statecb(null, { state: 'confirmationNeeded' });
return;
}
// Set the contract address in the receip
receipt.contractAddress = transactionLog.params.created.value;
const contractAddress = receipt.contractAddress;
statecb(null, { state: 'hasReceipt', receipt });
contract._receipt = receipt;
contract._address = contractAddress;
statecb(null, { state: 'getCode' });
return api.eth.getCode(contractAddress)
.then((code) => {
if (code === '0x') {
throw new Error('Contract not deployed, getCode returned 0x');
}
statecb(null, { state: 'completed' });
return contractAddress;
});
});
});
});
}
export function deployEstimateGas (contract, _options, values) {
const options = { ..._options };
const { api } = contract;
const address = options.from;
return WalletsUtils
.isWallet(api, address)
.then((isWallet) => {
if (!isWallet) {
return contract.deployEstimateGas(options, values);
}
return WalletsUtils
.getDeployArgs(contract, options, values)
.then((callArgs) => {
const { func, options, values } = callArgs;
return func.estimateGas(options, values);
})
.then((gasEst) => {
return [gasEst, gasEst.mul(1.05)];
});
});
}
export function patchApi (api) { export function patchApi (api) {
api.patch = { api.patch = {
...api.patch, ...api.patch,

View File

@ -16,59 +16,154 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { intersection, range, uniq } from 'lodash'; import { intersection, range, uniq } from 'lodash';
import store from 'store';
import Abi from '~/abi';
import Contract from '~/api/contract'; import Contract from '~/api/contract';
import { bytesToHex, toHex } from '~/api/util/format'; import { bytesToHex, toHex } from '~/api/util/format';
import { validateAddress } from '~/util/validation'; import { validateAddress } from '~/util/validation';
import WalletAbi from '~/contracts/abi/wallet.json'; import WalletAbi from '~/contracts/abi/wallet.json';
import OldWalletAbi from '~/contracts/abi/old-wallet.json';
const LS_PENDING_CONTRACTS_KEY = '_parity::wallets::pendingContracts';
const _cachedWalletLookup = {}; const _cachedWalletLookup = {};
let _cachedAccounts = {};
const walletAbi = new Abi(WalletAbi);
const oldWalletAbi = new Abi(OldWalletAbi);
const walletEvents = walletAbi.events.reduce((events, event) => {
events[event.name] = event;
return events;
}, {});
const oldWalletEvents = oldWalletAbi.events.reduce((events, event) => {
events[event.name] = event;
return events;
}, {});
const WalletSignatures = {
OwnerChanged: toHex(walletEvents.OwnerChanged.signature),
OwnerAdded: toHex(walletEvents.OwnerAdded.signature),
OwnerRemoved: toHex(walletEvents.OwnerRemoved.signature),
RequirementChanged: toHex(walletEvents.RequirementChanged.signature),
Confirmation: toHex(walletEvents.Confirmation.signature),
Revoke: toHex(walletEvents.Revoke.signature),
Deposit: toHex(walletEvents.Deposit.signature),
SingleTransact: toHex(walletEvents.SingleTransact.signature),
MultiTransact: toHex(walletEvents.MultiTransact.signature),
ConfirmationNeeded: toHex(walletEvents.ConfirmationNeeded.signature),
Old: {
SingleTransact: toHex(oldWalletEvents.SingleTransact.signature),
MultiTransact: toHex(oldWalletEvents.MultiTransact.signature)
}
};
export default class WalletsUtils { export default class WalletsUtils {
static getWalletSignatures () {
return WalletSignatures;
}
static getPendingContracts () {
return store.get(LS_PENDING_CONTRACTS_KEY) || {};
}
static setPendingContracts (contracts = {}) {
return store.set(LS_PENDING_CONTRACTS_KEY, contracts);
}
static removePendingContract (operationHash) {
const nextContracts = WalletsUtils.getPendingContracts();
delete nextContracts[operationHash];
WalletsUtils.setPendingContracts(nextContracts);
}
static addPendingContract (address, operationHash, metadata) {
const nextContracts = {
...WalletsUtils.getPendingContracts(),
[ operationHash ]: {
address,
metadata,
operationHash
}
};
WalletsUtils.setPendingContracts(nextContracts);
}
static cacheAccounts (accounts) {
_cachedAccounts = accounts;
}
static getCallArgs (api, options, values = []) { static getCallArgs (api, options, values = []) {
const walletContract = new Contract(api, WalletAbi); const walletContract = new Contract(api, WalletAbi);
const walletAddress = options.from;
const promises = [ return WalletsUtils
api.parity.accountsInfo(), .fetchOwners(walletContract.at(walletAddress))
WalletsUtils.fetchOwners(walletContract.at(options.from)) .then((owners) => {
]; const addresses = Object.keys(_cachedAccounts);
const ownerAddress = intersection(addresses, owners).pop();
return Promise if (!ownerAddress) {
.all(promises)
.then(([ accounts, owners ]) => {
const addresses = Object.keys(accounts);
const owner = intersection(addresses, owners).pop();
if (!owner) {
return false; return false;
} }
return owner; const account = _cachedAccounts[ownerAddress];
}) const _options = { ...options };
.then((owner) => { const { to, value = new BigNumber(0), data } = _options;
if (!owner) {
return false;
}
const _options = Object.assign({}, options);
const { from, to, value = new BigNumber(0), data } = options;
delete _options.data; delete _options.data;
const nextValues = [ to, value, data ]; const nextValues = [ to, value, data ];
const nextOptions = { const nextOptions = {
..._options, ..._options,
from: owner, from: ownerAddress,
to: from, to: walletAddress,
value: new BigNumber(0) value: new BigNumber(0)
}; };
const execFunc = walletContract.instance.execute; const execFunc = walletContract.instance.execute;
const callArgs = { func: execFunc, options: nextOptions, values: nextValues };
return { func: execFunc, options: nextOptions, values: nextValues }; if (!account.wallet) {
return callArgs;
}
const nextData = walletContract.getCallData(execFunc, nextOptions, nextValues);
return WalletsUtils.getCallArgs(api, { ...nextOptions, data: nextData }, nextValues);
}); });
} }
static getDeployArgs (contract, options, values) {
const { api } = contract;
const func = contract.constructors[0];
options.data = contract.getCallData(func, options, values);
options.to = '0x';
return WalletsUtils
.getCallArgs(api, options, values)
.then((callArgs) => {
if (!callArgs) {
console.error('no call args', callArgs);
throw new Error('you do not own this wallet');
}
return callArgs;
});
}
static parseLogs (api, logs = []) {
const walletContract = new Contract(api, WalletAbi);
return walletContract.parseEventLogs(logs);
}
/** /**
* Check whether the given address could be * Check whether the given address could be
* a Wallet. The result is cached in order not * a Wallet. The result is cached in order not
@ -199,16 +294,18 @@ export default class WalletsUtils {
} }
static fetchTransactions (walletContract) { static fetchTransactions (walletContract) {
const walletInstance = walletContract.instance; const { api } = walletContract;
const signatures = { const pendingContracts = WalletsUtils.getPendingContracts();
single: toHex(walletInstance.SingleTransact.signature),
multi: toHex(walletInstance.MultiTransact.signature),
deposit: toHex(walletInstance.Deposit.signature)
};
return walletContract return walletContract
.getAllLogs({ .getAllLogs({
topics: [ [ signatures.single, signatures.multi, signatures.deposit ] ] topics: [ [
WalletSignatures.SingleTransact,
WalletSignatures.MultiTransact,
WalletSignatures.Deposit,
WalletSignatures.Old.SingleTransact,
WalletSignatures.Old.MultiTransact
] ]
}) })
.then((logs) => { .then((logs) => {
return logs.sort((logA, logB) => { return logs.sort((logA, logB) => {
@ -226,11 +323,11 @@ export default class WalletsUtils {
const signature = toHex(log.topics[0]); const signature = toHex(log.topics[0]);
const value = log.params.value.value; const value = log.params.value.value;
const from = signature === signatures.deposit const from = signature === WalletSignatures.Deposit
? log.params['_from'].value ? log.params['_from'].value
: walletContract.address; : walletContract.address;
const to = signature === signatures.deposit const to = signature === WalletSignatures.Deposit
? walletContract.address ? walletContract.address
: log.params.to.value; : log.params.to.value;
@ -240,8 +337,53 @@ export default class WalletsUtils {
from, to, value from, to, value
}; };
if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
transaction.creates = log.params.created.value;
delete transaction.to;
}
if (log.params.operation) { if (log.params.operation) {
transaction.operation = bytesToHex(log.params.operation.value); const operation = bytesToHex(log.params.operation.value);
// Add the pending contract to the contracts
if (pendingContracts[operation]) {
const { metadata } = pendingContracts[operation];
const contractName = metadata.name;
metadata.blockNumber = log.blockNumber;
// The contract creation might not be in the same log,
// but must be in the same transaction (eg. Contract creation
// from Wallet within a Wallet)
api.eth
.getTransactionReceipt(log.transactionHash)
.then((transactionReceipt) => {
const transactionLogs = WalletsUtils.parseLogs(api, transactionReceipt.logs);
const creationLog = transactionLogs.find((log) => {
return log.params.created && !/^(0x)?0*$/.test(log.params.created.value);
});
if (!creationLog) {
return false;
}
const contractAddress = creationLog.params.created.value;
return Promise
.all([
api.parity.setAccountName(contractAddress, contractName),
api.parity.setAccountMeta(contractAddress, metadata)
])
.then(() => {
WalletsUtils.removePendingContract(operation);
});
})
.catch((error) => {
console.error('adding wallet contract', error);
});
}
transaction.operation = operation;
} }
if (log.params.data) { if (log.params.data) {

View File

@ -41,8 +41,9 @@ export default class SignerStore {
this.balances = Object.assign({}, this.balances, balances); this.balances = Object.assign({}, this.balances, balances);
} }
@action setLocalHashes = (localHashes) => { @action setLocalHashes = (localHashes = []) => {
if (!isEqual(localHashes, this.localHashes)) { // Use slice to make sure they are both Arrays (MobX uses Objects for Observable Arrays)
if (!isEqual(localHashes.slice(), this.localHashes.slice())) {
this.localHashes = localHashes; this.localHashes = localHashes;
} }
} }

View File

@ -71,7 +71,7 @@ export default class WalletTransactions extends Component {
} }
const txRows = transactions.slice(0, 15).map((transaction, index) => { const txRows = transactions.slice(0, 15).map((transaction, index) => {
const { transactionHash, blockNumber, from, to, value, data } = transaction; const { transactionHash, data } = transaction;
return ( return (
<TxRow <TxRow
@ -79,12 +79,9 @@ export default class WalletTransactions extends Component {
netVersion={ netVersion } netVersion={ netVersion }
key={ `${transactionHash}_${index}` } key={ `${transactionHash}_${index}` }
tx={ { tx={ {
blockNumber,
from,
hash: transactionHash, hash: transactionHash,
input: data && bytesToHex(data) || '', input: data && bytesToHex(data) || '',
to, ...transaction
value
} } } }
/> />
); );