Fix wallet view (#6597)

* Add safe fail for empty logs

* Filter transactions

* Add more logging

* Fix Wallet Creation and wallet tx list

* Remove logs

* Prevent selecting twice same wallet owner

* Fix tests

* Remove unused props

* Remove unused props
This commit is contained in:
Nicolas Gotchac 2017-10-09 13:11:18 +02:00 committed by Arkadiy Paronyan
parent 65ca2f9a07
commit 8d1964bc3b
17 changed files with 221 additions and 82 deletions

View File

@ -16,18 +16,21 @@
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { Form, TypedInput, Input, AddressSelect, InputAddress } from '~/ui';
import styles from '../createWallet.css';
export default class WalletDetails extends Component {
class WalletDetails extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
wallet: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
walletType: PropTypes.string.isRequired
walletType: PropTypes.string.isRequired,
knownAddresses: PropTypes.array
};
render () {
@ -103,7 +106,10 @@ export default class WalletDetails extends Component {
}
renderMultisigDetails () {
const { accounts, wallet, errors } = this.props;
const { accounts, knownAddresses, wallet, errors } = this.props;
const allowedOwners = knownAddresses
// Exclude sender and already owners of the wallet
.filter((address) => !wallet.owners.includes(address) && address !== wallet.account);
return (
<Form>
@ -163,7 +169,7 @@ export default class WalletDetails extends Component {
/>
<TypedInput
accounts={ accounts }
allowedValues={ allowedOwners }
label={
<FormattedMessage
id='createWallet.details.ownersMulti.label'
@ -249,3 +255,21 @@ export default class WalletDetails extends Component {
this.props.onChange({ daylimit });
}
}
function mapStateToProps (initState) {
const { accounts, contacts, contracts } = initState.personal;
const knownAddresses = [].concat(
Object.keys(accounts),
Object.keys(contacts),
Object.keys(contracts)
);
return () => ({
knownAddresses
});
}
export default connect(
mapStateToProps,
null
)(WalletDetails);

View File

@ -25,6 +25,22 @@ import { ACCOUNTS } from '../createWallet.test.js';
let component;
let onChange;
function createRedux () {
return {
dispatch: sinon.stub(),
subscribe: sinon.stub(),
getState: () => {
return {
personal: {
accounts: {},
contacts: {},
contracts: {}
}
};
}
};
}
function render (walletType = 'MULTISIG') {
onChange = sinon.stub();
component = shallow(
@ -36,7 +52,12 @@ function render (walletType = 'MULTISIG') {
owners: []
} }
walletType={ walletType }
/>
/>,
{
context: {
store: createRedux()
}
}
);
return component;

View File

@ -283,7 +283,8 @@ export default class CreateWalletStore {
const owners = _wallet.owners.filter((owner) => !/^(0x)?0*$/.test(owner));
if (_wallet.required > owners.length) {
// Real number of owners is owners + creator
if (_wallet.required > owners.length + 1) {
requiredValidation.valueError = 'the number of required validators should be lower or equal the number of owners';
}

View File

@ -43,7 +43,6 @@ export default class ParametersStep extends Component {
};
static propTypes = {
accounts: PropTypes.object.isRequired,
onParamsChange: PropTypes.func.isRequired,
inputs: PropTypes.array,
@ -60,7 +59,7 @@ export default class ParametersStep extends Component {
}
renderConstructorInputs () {
const { accounts, params, paramsError } = this.props;
const { params, paramsError } = this.props;
const { inputs } = this.props;
if (!inputs || !inputs.length) {
@ -78,7 +77,6 @@ export default class ParametersStep extends Component {
return (
<div key={ index } className={ styles.funcparams }>
<TypedInput
accounts={ accounts }
error={ error }
isEth={ false }
label={ label }

View File

@ -314,7 +314,6 @@ class DeployContract extends Component {
return (
<ParametersStep
{ ...this.state }
accounts={ accounts }
onParamsChange={ this.onParamsChange }
readOnly={ readOnly }
/>

View File

@ -177,7 +177,7 @@ export default class DetailsStep extends Component {
}
renderParameters () {
const { accounts, func, values, valuesError, onValueChange } = this.props;
const { func, values, valuesError, onValueChange } = this.props;
if (!func) {
return null;
@ -197,7 +197,6 @@ export default class DetailsStep extends Component {
value={ values[index] }
error={ valuesError[index] }
onChange={ onChange }
accounts={ accounts }
param={ input.type }
isEth={ false }
/>

View File

@ -34,7 +34,6 @@ class WalletSettings extends Component {
};
static propTypes = {
accountsInfo: PropTypes.object.isRequired,
wallet: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
senders: PropTypes.object.isRequired
@ -74,7 +73,7 @@ class WalletSettings extends Component {
default:
case 'EDIT':
const { errors, fromString, wallet } = this.store;
const { accountsInfo, senders } = this.props;
const { senders } = this.props;
return (
<Form>
@ -143,7 +142,6 @@ class WalletSettings extends Component {
}
value={ wallet.owners.slice() }
onChange={ this.store.onOwnersChange }
accounts={ accountsInfo }
param='address[]'
/>
@ -443,13 +441,13 @@ class WalletSettings extends Component {
}
function mapStateToProps (initState, initProps) {
const { accountsInfo, accounts } = initState.personal;
const { accounts } = initState.personal;
const { owners } = initProps.wallet;
const senders = pick(accounts, owners);
return () => {
return { accountsInfo, senders };
return { senders };
};
}

View File

@ -14,6 +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 { eq } from 'lodash';
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
@ -93,6 +94,18 @@ class AddressSelect extends Component {
}
componentWillReceiveProps (nextProps) {
if (!eq(Object.keys(this.props.accounts), Object.keys(nextProps.accounts))) {
return this.setValues(nextProps);
}
if (!eq(Object.keys(this.props.contacts), Object.keys(nextProps.contacts))) {
return this.setValues(nextProps);
}
if (!eq(Object.keys(this.props.contracts), Object.keys(nextProps.contracts))) {
return this.setValues(nextProps);
}
if (this.store.values && this.store.values.length > 0) {
return;
}

View File

@ -165,7 +165,8 @@ export default class AddressSelectStore {
const contactsN = Object.keys(contacts).length;
if (accountsN + contractsN + contactsN === 0) {
return;
this.initValues = [];
return this.handleChange();
}
this.initValues = [

View File

@ -14,6 +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 } from 'lodash';
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
@ -28,6 +29,7 @@ class InputAddressSelect extends Component {
contracts: PropTypes.object.isRequired,
allowCopy: PropTypes.bool,
allowedValues: PropTypes.array,
className: PropTypes.string,
error: nodeOrStringProptype(),
hint: nodeOrStringProptype(),
@ -38,16 +40,33 @@ class InputAddressSelect extends Component {
};
render () {
const { accounts, allowCopy, className, contacts, contracts, label, hint, error, value, onChange, readOnly } = this.props;
const { accounts, allowCopy, allowedValues, className, contacts, contracts, label, hint, error, value, onChange, readOnly } = this.props;
// Add the currently selected value to the list
// of allowed values, if any given
const nextAllowedValues = allowedValues
? [].concat(allowedValues, value || [])
: null;
const filteredAccounts = nextAllowedValues
? pick(accounts, nextAllowedValues)
: accounts;
const filteredContacts = nextAllowedValues
? pick(contacts, nextAllowedValues)
: accounts;
const filteredContracts = nextAllowedValues
? pick(contracts, nextAllowedValues)
: accounts;
return (
<AddressSelect
allowCopy={ allowCopy }
allowInput
accounts={ accounts }
accounts={ filteredAccounts }
className={ className }
contacts={ contacts }
contracts={ contracts }
contacts={ filteredContacts }
contracts={ filteredContracts }
error={ error }
hint={ hint }
label={ label }

View File

@ -40,8 +40,8 @@ export default class TypedInput extends Component {
PropTypes.string
]).isRequired,
accounts: PropTypes.object,
allowCopy: PropTypes.bool,
allowedValues: PropTypes.array,
className: PropTypes.string,
error: PropTypes.any,
hint: nodeOrStringProptype(),
@ -97,7 +97,7 @@ export default class TypedInput extends Component {
const { type } = param;
if (type === ABI_TYPES.ARRAY) {
const { accounts, className, label } = this.props;
const { allowedValues, className, label } = this.props;
const { subtype, length } = param;
const value = this.getValue() || param.default;
@ -113,8 +113,8 @@ export default class TypedInput extends Component {
return (
<TypedInput
accounts={ accounts }
allowCopy={ allowCopy }
allowedValues={ allowedValues }
className={ className }
key={ `${subtype.type}_${index}` }
onChange={ onChange }
@ -340,13 +340,13 @@ export default class TypedInput extends Component {
}
renderAddress () {
const { accounts, allowCopy, className, label, error, hint, readOnly } = this.props;
const { allowCopy, allowedValues, className, label, error, hint, readOnly } = this.props;
const value = this.getValue();
return (
<InputAddressSelect
allowCopy={ allowCopy }
accounts={ accounts }
allowedValues={ allowedValues }
className={ className }
error={ error }
hint={ hint }

View File

@ -78,13 +78,39 @@ export default class WalletsUtils {
.delegateCall(api, walletContract.address, 'fetchTransactions', [ walletContract ])
.then((transactions) => {
return transactions.sort((txA, txB) => {
const comp = txB.blockNumber.comparedTo(txA.blockNumber);
const bnA = txA.blockNumber;
const bnB = txB.blockNumber;
if (!bnA) {
console.warn('could not find block number in transaction', txA);
return 1;
}
if (!bnB) {
console.warn('could not find block number in transaction', txB);
return -1;
}
const comp = bnA.comparedTo(bnB);
if (comp !== 0) {
return comp;
}
return txB.transactionIndex.comparedTo(txA.transactionIndex);
const txIdxA = txA.transactionIndex;
const txIdxB = txB.transactionIndex;
if (!txIdxA) {
console.warn('could not find transaction index in transaction', txA);
return 1;
}
if (!txIdxB) {
console.warn('could not find transaction index in transaction', txB);
return -1;
}
return txIdxA.comparedTo(txIdxB);
});
});
}

View File

@ -212,6 +212,7 @@ export default class ConsensysWalletUtils {
const transaction = {
transactionHash: log.transactionHash,
transactionIndex: log.transactionIndex,
blockNumber: log.blockNumber
};

View File

@ -130,7 +130,21 @@ export default class FoundationWalletUtils {
.ConfirmationNeeded
.getAllLogs()
.then((logs) => {
return logs.map((log) => ({
return logs
.filter((log) => {
if (!log.blockNumber) {
console.warn('got a log without blockNumber', log);
return false;
}
if (!log.transactionIndex) {
console.warn('got a log without transactionIndex', log);
return false;
}
return true;
})
.map((log) => ({
initiator: log.params.initiator.value,
to: log.params.to.value,
data: log.params.data.value,
@ -144,13 +158,39 @@ export default class FoundationWalletUtils {
})
.then((logs) => {
return logs.sort((logA, logB) => {
const comp = logA.blockNumber.comparedTo(logB.blockNumber);
const bnA = logA.blockNumber;
const bnB = logA.blockNumber;
if (!bnA) {
console.warn('could not find block number in log', logA);
return 1;
}
if (!bnB) {
console.warn('could not find block number in log', logB);
return -1;
}
const comp = bnA.comparedTo(bnB);
if (comp !== 0) {
return comp;
}
return logA.transactionIndex.comparedTo(logB.transactionIndex);
const txIdxA = logA.transactionIndex;
const txIdxB = logB.transactionIndex;
if (!txIdxA) {
console.warn('could not find transaction index in log', logA);
return 1;
}
if (!txIdxB) {
console.warn('could not find transaction index in log', logB);
return -1;
}
return txIdxA.comparedTo(txIdxB);
});
})
.then((pendingTxs) => {
@ -205,7 +245,8 @@ export default class FoundationWalletUtils {
] ]
})
.then((logs) => {
const transactions = logs.map((log) => {
const transactions = logs
.map((log) => {
const signature = toHex(log.topics[0]);
const value = log.params.value.value;
@ -219,10 +260,16 @@ export default class FoundationWalletUtils {
const transaction = {
transactionHash: log.transactionHash,
transactionIndex: log.transactionIndex,
blockNumber: log.blockNumber,
from, to, value
};
if (!transaction.blockNumber) {
console.warn('log without block number', log);
return null;
}
if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
transaction.creates = log.params.created.value;
delete transaction.to;
@ -238,7 +285,8 @@ export default class FoundationWalletUtils {
}
return transaction;
});
})
.filter((tx) => tx);
return transactions;
});

View File

@ -35,7 +35,6 @@ class InputQuery extends Component {
};
static propTypes = {
accountsInfo: PropTypes.object.isRequired,
contract: PropTypes.object.isRequired,
inputs: arrayOrObjectProptype().isRequired,
outputs: arrayOrObjectProptype().isRequired,
@ -122,7 +121,7 @@ class InputQuery extends Component {
renderResults () {
const { results, isLoading } = this.state;
const { accountsInfo, outputs } = this.props;
const { outputs } = this.props;
if (isLoading) {
return (
@ -143,7 +142,6 @@ class InputQuery extends Component {
.map((out, index) => {
const input = (
<TypedInput
accounts={ accountsInfo }
allowCopy
isEth={ false }
param={ out.type }

View File

@ -29,7 +29,6 @@ export default class Queries extends Component {
}
static propTypes = {
accountsInfo: PropTypes.object.isRequired,
contract: PropTypes.object,
values: PropTypes.object
}
@ -94,12 +93,11 @@ export default class Queries extends Component {
renderInputQuery (fn) {
const { abi, name, signature } = fn;
const { accountsInfo, contract } = this.props;
const { contract } = this.props;
return (
<div className={ styles.container } key={ fn.signature }>
<InputQuery
accountsInfo={ accountsInfo }
className={ styles.method }
inputs={ abi.inputs }
outputs={ abi.outputs }
@ -144,13 +142,11 @@ export default class Queries extends Component {
return null;
}
const { accountsInfo } = this.props;
const { name, type } = output;
const label = `${name ? `${name}: ` : ''}${type}`;
return (
<TypedInput
accounts={ accountsInfo }
allowCopy
key={ key }
isEth={ false }

View File

@ -45,7 +45,6 @@ class Contract extends Component {
setVisibleAccounts: PropTypes.func.isRequired,
accounts: PropTypes.object,
accountsInfo: PropTypes.object,
contracts: PropTypes.object,
netVersion: PropTypes.string.isRequired,
params: PropTypes.object
@ -128,7 +127,7 @@ class Contract extends Component {
}
render () {
const { accountsInfo, contracts, netVersion, params } = this.props;
const { contracts, netVersion, params } = this.props;
const { allEvents, contract, queryValues, loadingEvents } = this.state;
const account = contracts[params.address];
@ -150,7 +149,6 @@ class Contract extends Component {
{ this.renderBlockNumber(account.meta) }
</Header>
<Queries
accountsInfo={ accountsInfo }
contract={ contract }
values={ queryValues }
/>
@ -530,12 +528,11 @@ class Contract extends Component {
}
function mapStateToProps (state) {
const { accounts, accountsInfo, contracts } = state.personal;
const { accounts, contracts } = state.personal;
const { netVersion } = state.nodeStatus;
return {
accounts,
accountsInfo,
contracts,
netVersion
};