[beta] UI backports (#4809)

* 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

* SMS Faucet (#4774)

* Faucet

* Remove flakey button-index testing

* Only display faucet when sms verified (mainnet)

* simplify availability checks

* WIP

* Resuest from verified -> verified

* Update endpoint, display response text

* Error icon on errors

* Parse hash text response

* Use /api/:address endpoint

* hash -> data

* Adjust sms-certified message

* Fix SectionList hovering issue (#4749)

* Fix SectionList Items hover when <3 items

* Even easier...

* lint (new)
This commit is contained in:
Jaco Greeff 2017-03-08 10:43:59 +01:00 committed by Arkadiy Paronyan
parent 3b56e8eded
commit c4196a5de3
32 changed files with 2656 additions and 993 deletions

View File

@ -107,34 +107,26 @@ export default class Contract {
});
}
deploy (options, values, statecb) {
const setState = (state) => {
if (!statecb) {
return;
}
return statecb(null, state);
};
setState({ state: 'estimateGas' });
deploy (options, values, statecb = () => {}) {
statecb(null, { state: 'estimateGas' });
return this
.deployEstimateGas(options, values)
.then(([gasEst, gas]) => {
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
.postTransaction(_options)
.postTransaction(encodedOptions)
.then((requestId) => {
setState({ state: 'checkRequest', requestId });
statecb(null, { state: 'checkRequest', requestId });
return this._pollCheckRequest(requestId);
})
.then((txhash) => {
setState({ state: 'getTransactionReceipt', txhash });
statecb(null, { state: 'getTransactionReceipt', txhash });
return this._pollTransactionReceipt(txhash, gas);
})
.then((receipt) => {
@ -142,14 +134,13 @@ export default class Contract {
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
}
setState({ state: 'hasReceipt', receipt });
statecb(null, { state: 'hasReceipt', receipt });
this._receipt = receipt;
this._address = receipt.contractAddress;
return this._address;
});
})
.then((address) => {
setState({ state: 'getCode' });
statecb(null, { state: 'getCode' });
return this._api.eth.getCode(this._address);
})
.then((code) => {
@ -157,9 +148,10 @@ export default class Contract {
throw new Error('Contract not deployed, getCode returned 0x');
}
setState({ state: 'completed' });
statecb(null, { state: 'completed' });
return this._address;
});
});
}
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
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
pragma solidity ^0.4.6;
contract multisig {
pragma solidity ^0.4.9;
contract WalletEvents {
// 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.
event Confirmation(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).
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).
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).
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.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
}
contract multisigAbi is multisig {
function isOwner(address _addr) returns (bool);
contract WalletAbi {
// 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.
function setDailyLimit(uint _newLimit);
function setDailyLimit(uint _newLimit) external;
function addOwner(address _owner);
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);
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) returns (bool o_success);
}
contract WalletLibrary is multisig {
contract WalletLibrary is WalletEvents {
// TYPES
// struct for the status of a pending operation.
@ -77,10 +78,6 @@ contract WalletLibrary is multisig {
bytes data;
}
/******************************
***** MULTI OWNED SECTION ****
******************************/
// MODIFIERS
// simple single-sig function modifier.
@ -98,23 +95,29 @@ contract WalletLibrary is multisig {
// 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
// as well as the selection of addresses capable of confirming them.
function initMultiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
m_required = _required;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
}
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) {
function revoke(bytes32 _operation) external {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
@ -128,7 +131,7 @@ contract WalletLibrary is multisig {
}
// 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;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
@ -140,7 +143,7 @@ contract WalletLibrary is multisig {
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) {
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return;
clearPending();
@ -154,7 +157,7 @@ contract WalletLibrary is multisig {
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)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
@ -166,19 +169,23 @@ contract WalletLibrary is multisig {
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) {
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
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;
}
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
@ -190,6 +197,88 @@ contract WalletLibrary is multisig {
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
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
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
@ -303,74 +353,26 @@ contract WalletLibrary is multisig {
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
/******************************
********* 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 {
function clearPending() internal {
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]];
clearPending();
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
}
delete m_pendingIndex;
}
// FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// 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
uint m_numOwners;
uint public m_numOwners;
uint public m_dailyLimit;
uint public m_spentToday;
@ -378,8 +380,8 @@ contract WalletLibrary is multisig {
// list of 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
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
@ -390,8 +392,7 @@ contract WalletLibrary is multisig {
mapping (bytes32 => Transaction) m_txs;
}
contract Wallet is multisig {
contract Wallet is WalletEvents {
// WALLET CONSTRUCTOR
// 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
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
return _walletLibrary.delegatecall(msg.data);
}
function isOwner(address _addr) returns (bool) {
function isOwner(address _addr) constant returns (bool) {
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
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
pragma solidity ^0.4.6;
pragma solidity ^0.4.9;
contract multiowned {
@ -130,7 +131,7 @@ contract multiowned {
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;
}
@ -230,14 +231,6 @@ contract multiowned {
// uses is specified in the modifier.
contract daylimit is multiowned {
// 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.
@ -291,18 +284,17 @@ contract multisig {
// Funds has arrived into the wallet (record how much).
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).
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).
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.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
// FUNCTIONS
// TODO: document
function changeOwner(address _from, address _to) external;
function execute(address _to, uint _value, bytes _data) external returns (bytes32);
function confirm(bytes32 _h) returns (bool);
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) external returns (bool o_success);
}
// 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
// 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 _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.
if (underLimit(_value)) {
SingleTransact(msg.sender, _value, _to, _data);
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
// yes - just execute the call.
_to.call.value(_value)(_data);
return 0;
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.
_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);
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) {
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);
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;
}

View File

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

View File

@ -22,10 +22,11 @@ import Contract from '~/api/contract';
import { ERROR_CODES } from '~/api/transport/error';
import Contracts from '~/contracts';
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 { toWei } from '~/api/util/wei';
import { deploy } from '~/util/tx';
import WalletsUtils from '~/util/wallets';
const STEPS = {
@ -179,6 +180,8 @@ export default class CreateWalletStore {
this.wallet.owners = owners;
this.wallet.required = require.toNumber();
this.wallet.dailylimit = dailylimit.limit;
this.wallet = this.getWalletWithMeta(this.wallet);
});
return this.addWallet(this.wallet);
@ -202,21 +205,51 @@ export default class CreateWalletStore {
return null; // exception when registry is not available
})
.then((address) => {
const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase();
const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress)
? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress)
: fullWalletCode;
if (!address || /^(0x)?0*$/.test(address)) {
return null;
}
// 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 = {
data: code,
from: account
};
return this.api
.newContract(walletAbi)
.deploy(options, [ owners, required, daylimit ], this.onDeploymentState);
const contract = this.api.newContract(walletAbi);
this.wallet = this.getWalletWithMeta(this.wallet);
return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState);
})
.then((address) => {
if (!address || /^(0x)?0*$/.test(address)) {
return false;
}
this.deployed = true;
this.wallet.address = address;
return this.addWallet(this.wallet);
@ -233,26 +266,37 @@ export default class CreateWalletStore {
}
@action addWallet = (wallet) => {
const { address, name, description } = wallet;
const { address, name, metadata } = wallet;
return Promise
.all([
this.api.parity.setAccountName(address, name),
this.api.parity.setAccountMeta(address, {
abi: walletAbi,
wallet: true,
timestamp: Date.now(),
deleted: false,
description,
name,
tags: ['wallet']
})
this.api.parity.setAccountMeta(address, metadata)
])
.then(() => {
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) => {
if (error) {
return console.error('createWallet::onDeploymentState', error);
@ -298,6 +342,15 @@ export default class CreateWalletStore {
);
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':
this.deployState = (
<FormattedMessage

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// 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 React, { Component, PropTypes } from 'react';
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 { CancelIcon, DoneIcon } from '~/ui/Icons';
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
import { deploy, deployEstimateGas } from '~/util/tx';
import DetailsStep from './DetailsStep';
import ParametersStep from './ParametersStep';
@ -73,7 +74,7 @@ class DeployContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
};
static propTypes = {
accounts: PropTypes.object.isRequired,
@ -422,9 +423,9 @@ class DeployContract extends Component {
from: fromAddress
};
api
.newContract(abiParsed)
.deployEstimateGas(options, params)
const contract = api.newContract(abiParsed);
deployEstimateGas(contract, options, params)
.then(([gasEst, gas]) => {
this.gasStore.setEstimated(gasEst.toFixed(0));
this.gasStore.setGas(gas.toFixed(0));
@ -490,6 +491,17 @@ class DeployContract extends Component {
const { api, store } = this.context;
const { source } = this.props;
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 = {
data: code,
from: fromAddress
@ -499,28 +511,25 @@ class DeployContract extends Component {
const contract = api.newContract(abiParsed);
contract
.deploy(options, params, this.onDeploymentState)
deploy(contract, options, params, metadata, this.onDeploymentState)
.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()
: null;
return Promise.all([
api.parity.setAccountName(address, name),
api.parity.setAccountMeta(address, {
abi: abiParsed,
contract: true,
timestamp: Date.now(),
deleted: false,
blockNumber,
description,
source
})
api.parity.setAccountMeta(address, metadata)
])
.then(() => {
console.log(`contract deployed at ${address}`);
this.setState({ step: 'DEPLOYMENT', address });
this.setState({ step: 'COMPLETED', address });
});
})
.catch((error) => {
@ -589,6 +598,17 @@ class DeployContract extends Component {
});
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':
this.setState({
deployState: (
@ -614,17 +634,14 @@ class DeployContract extends Component {
function mapStateToProps (initState, initProps) {
const { accounts } = initProps;
// Skip Wallet accounts : they can't create Contracts
const _accounts = omitBy(accounts, (a) => a.wallet);
const fromAddresses = Object.keys(_accounts);
const fromAddresses = Object.keys(accounts);
return (state) => {
const balances = pick(state.balances.balances, fromAddresses);
const { gasLimit } = state.nodeStatus;
return {
accounts: _accounts,
accounts,
balances,
gasLimit
};

View File

@ -0,0 +1,162 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { txLink } from '~/3rdparty/etherscan/links';
import { Button, ModalBox, Portal, ShortenedHash } from '~/ui';
import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '~/ui/Icons';
import Store from './store';
@observer
export default class Faucet extends Component {
static propTypes = {
address: PropTypes.string.isRequired,
netVersion: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired
}
store = new Store(this.props.netVersion, this.props.address);
render () {
const { error, isBusy, isCompleted } = this.store;
let icon = <DialIcon />;
if (isCompleted) {
icon = error
? <ErrorIcon />
: <DoneIcon />;
}
return (
<Portal
buttons={ this.renderActions() }
busy={ isBusy }
isSmallModal
onClose={ this.onClose }
open
title={
<FormattedMessage
id='faucet.title'
defaultMessage='Kovan ETH Faucet'
/>
}
>
<ModalBox
icon={ icon }
summary={
isCompleted
? this.renderSummaryDone()
: this.renderSummaryRequest()
}
/>
</Portal>
);
}
renderActions = () => {
const { canTransact, isBusy, isCompleted } = this.store;
return isCompleted || isBusy
? (
<Button
disabled={ isBusy }
icon={ <DoneIcon /> }
key='done'
label={
<FormattedMessage
id='faucet.buttons.done'
defaultMessage='close'
/>
}
onClick={ this.onClose }
/>
)
: [
<Button
icon={ <CloseIcon /> }
key='close'
label={
<FormattedMessage
id='faucet.buttons.close'
defaultMessage='close'
/>
}
onClick={ this.onClose }
/>,
<Button
disabled={ !canTransact }
icon={ <SendIcon /> }
key='request'
label={
<FormattedMessage
id='faucet.buttons.request'
defaultMessage='request'
/>
}
onClick={ this.onExecute }
/>
];
}
renderSummaryDone () {
const { error, responseText, responseTxHash } = this.store;
return (
<div>
<FormattedMessage
id='faucet.summary.done'
defaultMessage='Your Kovan ETH has been requested from the faucet which responded with -'
/>
{
error
? (
<p>{ error }</p>
)
: (
<p>
<span>{ responseText }&nbsp;</span>
<a href={ txLink(responseTxHash, false, '42') } target='_blank'>
<ShortenedHash data={ responseTxHash } />
</a>
</p>
)
}
</div>
);
}
renderSummaryRequest () {
return (
<FormattedMessage
id='faucet.summary.info'
defaultMessage='To request a deposit of Kovan ETH to this address, you need to ensure that the address is sms-verified on the mainnet. Once executed the faucet will deposit Kovan ETH into the current account.'
/>
);
}
onClose = () => {
this.props.onClose();
}
onExecute = () => {
return this.store.makeItRain();
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './faucet';

View File

@ -0,0 +1,126 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, computed, observable, transaction } from 'mobx';
import apiutil from '~/api/util';
const ENDPOINT = 'http://faucet.kovan.network/api/';
export default class Store {
@observable addressReceive = null;
@observable addressVerified = null;
@observable error = null;
@observable responseText = null;
@observable responseTxHash = null;
@observable isBusy = false;
@observable isCompleted = false;
@observable isDestination = false;
@observable isDone = false;
constructor (netVersion, address) {
transaction(() => {
this.setDestination(netVersion === '42');
this.setAddressReceive(address);
this.setAddressVerified(address);
});
}
@computed get canTransact () {
return !this.isBusy && this.addressReceiveValid && this.addressVerifiedValid;
}
@computed get addressReceiveValid () {
return apiutil.isAddressValid(this.addressReceive);
}
@computed get addressVerifiedValid () {
return apiutil.isAddressValid(this.addressVerified);
}
@action setAddressReceive = (address) => {
this.addressReceive = address;
}
@action setAddressVerified = (address) => {
this.addressVerified = address;
}
@action setBusy = (isBusy) => {
this.isBusy = isBusy;
}
@action setCompleted = (isCompleted) => {
transaction(() => {
this.setBusy(false);
this.isCompleted = isCompleted;
});
}
@action setDestination = (isDestination) => {
this.isDestination = isDestination;
}
@action setError = (error) => {
if (error.indexOf('not certified') !== -1) {
this.error = `${error}. Please ensure that this account is sms certified on the mainnet.`;
} else {
this.error = error;
}
}
@action setResponse = (response) => {
this.responseText = response.result;
this.responseTxHash = response.tx;
}
makeItRain = () => {
this.setBusy(true);
const options = {
method: 'GET',
mode: 'cors'
};
const url = `${ENDPOINT}${this.addressVerified}`;
return fetch(url, options)
.then((response) => {
if (!response.ok) {
return null;
}
return response.json();
})
.catch(() => {
return null;
})
.then((response) => {
transaction(() => {
if (!response || response.error) {
this.setError(
response
? response.error
: 'Unable to complete request to the faucet, the server may be unavailable. Please try again later.'
);
} else {
this.setResponse(response);
}
this.setCompleted(true);
});
});
}
}

View File

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

View File

@ -24,6 +24,7 @@ export DeleteAccount from './DeleteAccount';
export DeployContract from './DeployContract';
export EditMeta from './EditMeta';
export ExecuteContract from './ExecuteContract';
export Faucet from './Faucet';
export FirstRun from './FirstRun';
export LoadContract from './LoadContract';
export PasswordManager from './PasswordManager';

View File

@ -49,7 +49,7 @@ export default class Personal {
.filter((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))
);

View File

@ -26,7 +26,6 @@ import WalletsUtils from '~/util/wallets';
import { wallet as WalletAbi } from '~/contracts/abi';
export function personalAccountsInfo (accountsInfo) {
const addresses = [];
const accounts = {};
const contacts = {};
const contracts = {};
@ -35,10 +34,9 @@ export function personalAccountsInfo (accountsInfo) {
Object.keys(accountsInfo || {})
.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) => {
if (account.uuid) {
addresses.push(account.address);
accounts[account.address] = account;
} else if (account.meta.wallet) {
account.wallet = true;
@ -87,18 +85,45 @@ export function personalAccountsInfo (accountsInfo) {
return [];
})
.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);
// Owners ∩ Addresses not null : Wallet is owned
// by one of the accounts
if (intersection(owners, addresses).length > 0) {
accounts[wallet.address] = wallet;
} else {
contacts[wallet.address] = wallet;
return false;
}
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({
accountsInfo,
accounts,

View File

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

View File

@ -31,6 +31,7 @@ export DashboardIcon from 'material-ui/svg-icons/action/dashboard';
export DeleteIcon from 'material-ui/svg-icons/action/delete';
export DevelopIcon from 'material-ui/svg-icons/action/description';
export DoneIcon from 'material-ui/svg-icons/action/done-all';
export DialIcon from 'material-ui/svg-icons/communication/dialpad';
export EditIcon from 'material-ui/svg-icons/content/create';
export ErrorIcon from 'material-ui/svg-icons/alert/error';
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';

View File

@ -120,6 +120,18 @@ class MethodDecoding extends Component {
<span className={ styles.highlight }>
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
</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 className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
{ this.renderMinBlock() }

View File

@ -22,13 +22,13 @@ import styles from './modalBox.css';
export default class ModalBox extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
children: PropTypes.node,
icon: PropTypes.node.isRequired,
summary: nodeOrStringProptype()
}
render () {
const { children, icon } = this.props;
const { icon } = this.props;
return (
<div className={ styles.body }>
@ -37,11 +37,23 @@ export default class ModalBox extends Component {
</div>
<div className={ styles.content }>
{ this.renderSummary() }
{ this.renderBody() }
</div>
</div>
);
}
renderBody () {
const { children } = this.props;
if (!children) {
return null;
}
return (
<div className={ styles.body }>
{ children }
</div>
</div>
</div>
);
}

View File

@ -17,7 +17,6 @@
$transition: all 0.25s;
$widthNormal: 33.33%;
$widthShrunk: 29%;
$widthExpanded: 42%;
.section {
@ -39,18 +38,19 @@ $widthExpanded: 42%;
display: flex;
justify-content: center;
/* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */
/* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for
/* case where <> 3 columns are required should the need arrise from a UI pov. */
.item {
box-sizing: border-box;
display: flex;
flex: 0 1 $widthNormal;
max-width: $widthNormal;
opacity: 0.85;
padding: 0.25em;
/* https://www.binarymoon.co.uk/2014/02/fixing-css-transitions-in-google-chrome/ */
transform: translateZ(0);
transition: $transition;
width: 0;
&:hover {
opacity: 1;
@ -58,21 +58,12 @@ $widthExpanded: 42%;
}
}
&:hover {
.item {
&.stretchOn {
flex: 0 1 $widthShrunk;
max-width: $widthShrunk;
&:hover {
.item.stretchOn:hover {
flex: 0 0 $widthExpanded;
max-width: $widthExpanded;
}
}
}
}
}
}
.section + .section {
margin-top: 1em;

View File

@ -51,6 +51,7 @@ export default class Store {
return bnB.comparedTo(bnA);
});
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
});
}
@ -85,26 +86,53 @@ export default class Store {
this._subscriptionId = 0;
}
loadTransactions (_txhashes) {
const txhashes = _txhashes.filter((hash) => !this.transactions[hash] || this._pendingHashes.includes(hash));
loadTransactions (_txhashes = []) {
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;
}
Promise
.all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash)))
.all(promises)
.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(
transactions.reduce((transactions, tx, index) => {
transactions[txhashes[index]] = tx;
return transactions;
}, {})
);
if (tx.blockNumber && tx.blockNumber.gt(0)) {
blockNumbers.push(tx.blockNumber.toNumber());
}
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) => {
console.warn('loadTransactions', error);

View File

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

View File

@ -61,17 +61,6 @@ export function estimateGas (_func, _options, _values = []) {
const { func, options, values } = callArgs;
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) {
api.patch = {
...api.patch,

View File

@ -16,59 +16,154 @@
import BigNumber from 'bignumber.js';
import { intersection, range, uniq } from 'lodash';
import store from 'store';
import Abi from '~/abi';
import Contract from '~/api/contract';
import { bytesToHex, toHex } from '~/api/util/format';
import { validateAddress } from '~/util/validation';
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 = {};
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 {
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 = []) {
const walletContract = new Contract(api, WalletAbi);
const walletAddress = options.from;
const promises = [
api.parity.accountsInfo(),
WalletsUtils.fetchOwners(walletContract.at(options.from))
];
return WalletsUtils
.fetchOwners(walletContract.at(walletAddress))
.then((owners) => {
const addresses = Object.keys(_cachedAccounts);
const ownerAddress = intersection(addresses, owners).pop();
return Promise
.all(promises)
.then(([ accounts, owners ]) => {
const addresses = Object.keys(accounts);
const owner = intersection(addresses, owners).pop();
if (!owner) {
if (!ownerAddress) {
return false;
}
return owner;
})
.then((owner) => {
if (!owner) {
return false;
}
const _options = Object.assign({}, options);
const { from, to, value = new BigNumber(0), data } = options;
const account = _cachedAccounts[ownerAddress];
const _options = { ...options };
const { to, value = new BigNumber(0), data } = _options;
delete _options.data;
const nextValues = [ to, value, data ];
const nextOptions = {
..._options,
from: owner,
to: from,
from: ownerAddress,
to: walletAddress,
value: new BigNumber(0)
};
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
* a Wallet. The result is cached in order not
@ -199,16 +294,18 @@ export default class WalletsUtils {
}
static fetchTransactions (walletContract) {
const walletInstance = walletContract.instance;
const signatures = {
single: toHex(walletInstance.SingleTransact.signature),
multi: toHex(walletInstance.MultiTransact.signature),
deposit: toHex(walletInstance.Deposit.signature)
};
const { api } = walletContract;
const pendingContracts = WalletsUtils.getPendingContracts();
return walletContract
.getAllLogs({
topics: [ [ signatures.single, signatures.multi, signatures.deposit ] ]
topics: [ [
WalletSignatures.SingleTransact,
WalletSignatures.MultiTransact,
WalletSignatures.Deposit,
WalletSignatures.Old.SingleTransact,
WalletSignatures.Old.MultiTransact
] ]
})
.then((logs) => {
return logs.sort((logA, logB) => {
@ -226,11 +323,11 @@ export default class WalletsUtils {
const signature = toHex(log.topics[0]);
const value = log.params.value.value;
const from = signature === signatures.deposit
const from = signature === WalletSignatures.Deposit
? log.params['_from'].value
: walletContract.address;
const to = signature === signatures.deposit
const to = signature === WalletSignatures.Deposit
? walletContract.address
: log.params.to.value;
@ -240,8 +337,53 @@ export default class WalletsUtils {
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) {
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) {

View File

@ -22,11 +22,11 @@ import { bindActionCreators } from 'redux';
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
import HardwareStore from '~/mobx/hardwareStore';
import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals';
import { DeleteAccount, EditMeta, Faucet, PasswordManager, Shapeshift, Transfer, Verification } from '~/modals';
import { setVisibleAccounts } from '~/redux/providers/personalActions';
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
import { Actionbar, Button, Page } from '~/ui';
import { DeleteIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
import DeleteAddress from '../Address/Delete';
@ -48,6 +48,8 @@ class Account extends Component {
accounts: PropTypes.object,
balances: PropTypes.object,
certifications: PropTypes.object,
netVersion: PropTypes.string.isRequired,
params: PropTypes.object
}
@ -97,6 +99,7 @@ class Account extends Component {
<div>
{ this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) }
{ this.renderFaucetDialog() }
{ this.renderFundDialog() }
{ this.renderPasswordDialog(account) }
{ this.renderTransferDialog(account, balance) }
@ -117,8 +120,35 @@ class Account extends Component {
);
}
isKovan = (netVersion) => {
return netVersion === '42';
}
isMainnet = (netVersion) => {
return netVersion === '1';
}
isFaucettable = (netVersion, certifications, address) => {
return this.isKovan(netVersion) || (
this.isMainnet(netVersion) &&
this.isSmsCertified(certifications, address)
);
}
isSmsCertified = (_certifications, address) => {
const certifications = _certifications && _certifications[address]
? _certifications[address].filter((cert) => cert.name.indexOf('smsverification') === 0)
: [];
return certifications.length !== 0;
}
renderActionbar (account, balance) {
const { certifications, netVersion } = this.props;
const { address } = this.props.params;
const showTransferButton = !!(balance && balance.tokens);
const isVerifiable = this.isMainnet(netVersion);
const isFaucettable = this.isFaucettable(netVersion, certifications, address);
const buttons = [
<Button
@ -149,9 +179,11 @@ class Account extends Component {
}
onClick={ this.store.toggleFundDialog }
/>,
isVerifiable
? (
<Button
icon={ <VerifyIcon /> }
key='sms-verification'
key='verification'
label={
<FormattedMessage
id='account.button.verify'
@ -159,7 +191,24 @@ class Account extends Component {
/>
}
onClick={ this.store.toggleVerificationDialog }
/>,
/>
)
: null,
isFaucettable
? (
<Button
icon={ <DialIcon /> }
key='faucet'
label={
<FormattedMessage
id='account.button.faucet'
defaultMessage='Kovan ETH'
/>
}
onClick={ this.store.toggleFaucetDialog }
/>
)
: null,
<Button
icon={ <EditIcon /> }
key='editmeta'
@ -253,6 +302,24 @@ class Account extends Component {
);
}
renderFaucetDialog () {
const { netVersion } = this.props;
if (!this.store.isFaucetVisible) {
return null;
}
const { address } = this.props.params;
return (
<Faucet
address={ address }
netVersion={ netVersion }
onClose={ this.store.toggleFaucetDialog }
/>
);
}
renderFundDialog () {
if (!this.store.isFundVisible) {
return null;
@ -317,10 +384,14 @@ class Account extends Component {
function mapStateToProps (state) {
const { accounts } = state.personal;
const { balances } = state.balances;
const certifications = state.certifications;
const { netVersion } = state.nodeStatus;
return {
accounts,
balances
balances,
certifications,
netVersion
};
}

View File

@ -80,57 +80,16 @@ describe('views/Account', () => {
describe('sub-renderers', () => {
describe('renderActionBar', () => {
let bar;
let barShallow;
beforeEach(() => {
render();
bar = instance.renderActionbar({ tokens: {} });
barShallow = shallow(bar);
});
it('renders the bar', () => {
expect(bar.type).to.match(/Actionbar/);
});
// TODO: Finding by index is not optimal, however couldn't find a better method atm
// since we cannot find by key (prop not visible in shallow debug())
describe('clicks', () => {
it('toggles transfer on click', () => {
barShallow.find('Button').at(0).simulate('click');
expect(store.isTransferVisible).to.be.true;
});
it('toggles fund on click', () => {
barShallow.find('Button').at(1).simulate('click');
expect(store.isFundVisible).to.be.true;
});
it('toggles fund on click', () => {
barShallow.find('Button').at(1).simulate('click');
expect(store.isFundVisible).to.be.true;
});
it('toggles verify on click', () => {
barShallow.find('Button').at(2).simulate('click');
expect(store.isVerificationVisible).to.be.true;
});
it('toggles edit on click', () => {
barShallow.find('Button').at(3).simulate('click');
expect(store.isEditVisible).to.be.true;
});
it('toggles password on click', () => {
barShallow.find('Button').at(4).simulate('click');
expect(store.isPasswordVisible).to.be.true;
});
it('toggles delete on click', () => {
barShallow.find('Button').at(5).simulate('click');
expect(store.isDeleteVisible).to.be.true;
});
});
});
describe('renderDeleteDialog', () => {

View File

@ -19,6 +19,7 @@ import { action, observable } from 'mobx';
export default class Store {
@observable isDeleteVisible = false;
@observable isEditVisible = false;
@observable isFaucetVisible = false;
@observable isFundVisible = false;
@observable isPasswordVisible = false;
@observable isTransferVisible = false;
@ -32,6 +33,10 @@ export default class Store {
this.isEditVisible = !this.isEditVisible;
}
@action toggleFaucetDialog = () => {
this.isFaucetVisible = !this.isFaucetVisible;
}
@action toggleFundDialog = () => {
this.isFundVisible = !this.isFundVisible;
}

View File

@ -31,6 +31,7 @@ describe('views/Account/Store', () => {
it('sets all modal visibility to false', () => {
expect(store.isDeleteVisible).to.be.false;
expect(store.isEditVisible).to.be.false;
expect(store.isFaucetVisible).to.be.false;
expect(store.isFundVisible).to.be.false;
expect(store.isPasswordVisible).to.be.false;
expect(store.isTransferVisible).to.be.false;
@ -53,6 +54,13 @@ describe('views/Account/Store', () => {
});
});
describe('toggleFaucetDialog', () => {
it('toggles the visibility', () => {
store.toggleFaucetDialog();
expect(store.isFaucetVisible).to.be.true;
});
});
describe('toggleFundDialog', () => {
it('toggles the visibility', () => {
store.toggleFundDialog();

View File

@ -41,8 +41,9 @@ export default class SignerStore {
this.balances = Object.assign({}, this.balances, balances);
}
@action setLocalHashes = (localHashes) => {
if (!isEqual(localHashes, this.localHashes)) {
@action setLocalHashes = (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;
}
}

View File

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