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:
parent
65ca2f9a07
commit
8d1964bc3b
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -314,7 +314,6 @@ class DeployContract extends Component {
|
||||
return (
|
||||
<ParametersStep
|
||||
{ ...this.state }
|
||||
accounts={ accounts }
|
||||
onParamsChange={ this.onParamsChange }
|
||||
readOnly={ readOnly }
|
||||
/>
|
||||
|
@ -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 }
|
||||
/>
|
||||
|
@ -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 };
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 = [
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -212,6 +212,7 @@ export default class ConsensysWalletUtils {
|
||||
|
||||
const transaction = {
|
||||
transactionHash: log.transactionHash,
|
||||
transactionIndex: log.transactionIndex,
|
||||
blockNumber: log.blockNumber
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user