Ui 2 move to packages/* (#6113)
* Move secureApi to shell
* Extract isTestnet test
* Use mobx + subscriptions for status
* Re-add status indicator
* Add lerna
* Move intial packages to js/packages
* Move 3rdparty/{email,sms}-verification to correct location
* Move package.json & README to library src
* Move tests for library packages
* Move views & dapps to packages
* Move i18n to root
* Move shell to actual src (main app)
* Remove ~ references
* Change ~ to root (explicit imports)
* Finalise convert of ~
* Move views into dapps as well
* Move dapps to packages/
* Fix references
* Update css
* Update test spec locations
* Update tests
* Case fix
* Skip flakey tests
* Update enzyme
* Skip previously ignored tests
* Allow empty api for hw
* Re-add theme for embed
This commit is contained in:
17
js/packages/ui/MethodDecoding/index.js
Normal file
17
js/packages/ui/MethodDecoding/index.js
Normal 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 './methodDecoding';
|
||||
106
js/packages/ui/MethodDecoding/methodDecoding.css
Normal file
106
js/packages/ui/MethodDecoding/methodDecoding.css
Normal file
@@ -0,0 +1,106 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
}
|
||||
|
||||
.clickable {
|
||||
border: 1px dashed rgba(255, 255, 255, 0.4);
|
||||
padding: 0.1em 0.3em;
|
||||
margin: 0.1em 0.1em;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.noSelect {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.details {
|
||||
line-height: 1.75em;
|
||||
}
|
||||
|
||||
.details,
|
||||
.gasDetails {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.gasDetails {
|
||||
padding-top: 0.75em;
|
||||
font-size: 0.75em;
|
||||
line-height: 1.5em;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.description {
|
||||
line-height: 2em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.name,
|
||||
.highlight {
|
||||
color: #333;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.name,
|
||||
.inputs,
|
||||
.highlight {
|
||||
}
|
||||
|
||||
.inputs, .addressContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-top: -16px;
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
.inputs [data-address-img] {
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
}
|
||||
|
||||
.etherValue,
|
||||
.tokenValue {
|
||||
}
|
||||
|
||||
.address {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.identityicon {
|
||||
margin-bottom: -10px;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.inputData {
|
||||
word-break: break-all;
|
||||
}
|
||||
678
js/packages/ui/MethodDecoding/methodDecoding.js
Normal file
678
js/packages/ui/MethodDecoding/methodDecoding.js
Normal file
@@ -0,0 +1,678 @@
|
||||
// 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 BigNumber from 'bignumber.js';
|
||||
import moment from 'moment';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import IdentityIcon from '../IdentityIcon';
|
||||
import { TypedInput, Label } from '../Form';
|
||||
import Loading from '../Loading';
|
||||
import MethodDecodingStore from './methodDecodingStore';
|
||||
|
||||
import styles from './methodDecoding.css';
|
||||
|
||||
const ASCII_INPUT = /^[a-z0-9\s,?;.:/!()-_@'"#]+$/i;
|
||||
const TOKEN_METHODS = {
|
||||
'0xa9059cbb': 'transfer(to,value)'
|
||||
};
|
||||
|
||||
class MethodDecoding extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string,
|
||||
compact: PropTypes.bool,
|
||||
token: PropTypes.object,
|
||||
transaction: PropTypes.object,
|
||||
historic: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
address: '',
|
||||
compact: false,
|
||||
historic: false
|
||||
};
|
||||
|
||||
state = {
|
||||
contractAddress: null,
|
||||
methodName: null,
|
||||
methodInputs: null,
|
||||
methodParams: null,
|
||||
methodSignature: null,
|
||||
isContract: false,
|
||||
isDeploy: false,
|
||||
isReceived: false,
|
||||
isLoading: true,
|
||||
expandInput: false,
|
||||
inputType: 'auto'
|
||||
};
|
||||
|
||||
methodDecodingStore = MethodDecodingStore.get(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
const { address, transaction } = this.props;
|
||||
|
||||
this
|
||||
.methodDecodingStore
|
||||
.lookup(address, transaction)
|
||||
.then((lookup) => {
|
||||
const newState = {
|
||||
methodName: lookup.name,
|
||||
methodInputs: lookup.inputs,
|
||||
methodParams: lookup.params,
|
||||
methodSignature: lookup.signature,
|
||||
|
||||
isContract: lookup.contract,
|
||||
isDeploy: lookup.deploy,
|
||||
isLoading: false,
|
||||
isReceived: lookup.received
|
||||
};
|
||||
|
||||
this.setState(newState);
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { transaction } = this.props;
|
||||
const { isLoading } = this.state;
|
||||
|
||||
if (!transaction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={ styles.loading }>
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
{ this.renderAction() }
|
||||
{ this.renderGas() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderGas () {
|
||||
const { compact, historic, transaction } = this.props;
|
||||
const { gas, gasPrice, value } = transaction;
|
||||
|
||||
if (!gas || !gasPrice || compact) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gasProvided = (
|
||||
<span className={ styles.highlight }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.gasValues'
|
||||
defaultMessage='{gas} gas ({gasPrice}M/{tag})'
|
||||
values={ {
|
||||
gas: gas.toFormat(0),
|
||||
gasPrice: gasPrice.div(1000000).toFormat(0),
|
||||
tag: <small>ETH</small>
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
const totalEthValue = (
|
||||
<span className={ styles.highlight }>
|
||||
{ this.renderEtherValue(gas.mul(gasPrice).plus(value || 0)) }
|
||||
</span>
|
||||
);
|
||||
const gasUsed = transaction.gasUsed
|
||||
? (
|
||||
<span className={ styles.highlight }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.gasUsed'
|
||||
defaultMessage=' ({gas} gas used)'
|
||||
values={ {
|
||||
gas: transaction.gasUsed.toFormat(0)
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className={ styles.gasDetails }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.txValues'
|
||||
defaultMessage='{historic, select, true {Provided} false {Provides}} {gasProvided}{gasUsed} for a total transaction value of {totalEthValue}'
|
||||
values={ {
|
||||
historic,
|
||||
gasProvided,
|
||||
gasUsed,
|
||||
totalEthValue
|
||||
} }
|
||||
/>
|
||||
{ this.renderMinBlock() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMinBlock () {
|
||||
const { historic, transaction } = this.props;
|
||||
const { condition } = transaction;
|
||||
|
||||
if (!condition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const blockCondition = new BigNumber(condition.block || 0);
|
||||
|
||||
if (blockCondition.gt(0)) {
|
||||
const blockNumber = (
|
||||
<span className={ styles.highlight }>
|
||||
#{ blockCondition.toFormat(0) }
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.condition.block'
|
||||
defaultMessage='{historic, select, true {Will be submitted} false {To be submitted}} at block {blockNumber}'
|
||||
values={ {
|
||||
historic,
|
||||
blockNumber
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (condition.time) {
|
||||
const timestamp = (
|
||||
<span className={ styles.highlight }>
|
||||
{ moment(condition.time).format('LLLL') }
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.condition.time'
|
||||
defaultMessage='{historic, select, true {Will be submitted} false {To be submitted}} {timestamp}'
|
||||
values={ {
|
||||
historic,
|
||||
timestamp
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderAction () {
|
||||
const { token } = this.props;
|
||||
const { methodName, methodInputs, methodSignature, isDeploy, isReceived, isContract } = this.state;
|
||||
|
||||
if (isDeploy) {
|
||||
return this.renderDeploy();
|
||||
}
|
||||
|
||||
if (isContract && methodSignature) {
|
||||
if (token && TOKEN_METHODS[methodSignature] && methodInputs) {
|
||||
return this.renderTokenAction();
|
||||
}
|
||||
|
||||
if (methodName) {
|
||||
return this.renderSignatureMethod();
|
||||
}
|
||||
|
||||
return this.renderUnknownMethod();
|
||||
}
|
||||
|
||||
return isReceived
|
||||
? this.renderValueReceipt()
|
||||
: this.renderValueTransfer();
|
||||
}
|
||||
|
||||
getAscii () {
|
||||
const { api } = this.context;
|
||||
const { transaction } = this.props;
|
||||
const value = api.util.hexToAscii(transaction.input || transaction.data);
|
||||
|
||||
return {
|
||||
value,
|
||||
valid: ASCII_INPUT.test(value)
|
||||
};
|
||||
}
|
||||
|
||||
renderInputValue () {
|
||||
const { compact, transaction } = this.props;
|
||||
|
||||
if (compact) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { expandInput, inputType } = this.state;
|
||||
const input = transaction.input || transaction.data;
|
||||
|
||||
if (!/^(0x)?([0]*[1-9a-f]+[0]*)+$/.test(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ascii = this.getAscii();
|
||||
const type = inputType === 'auto'
|
||||
? (ascii.valid ? 'ascii' : 'raw')
|
||||
: inputType;
|
||||
|
||||
const text = type === 'ascii'
|
||||
? ascii.value
|
||||
: input;
|
||||
|
||||
const expandable = text.length > 50;
|
||||
const textToShow = expandInput || !expandable
|
||||
? text
|
||||
: text.slice(0, 50) + '...';
|
||||
|
||||
const inputDesc = (
|
||||
<span
|
||||
onClick={ this.toggleInputType }
|
||||
className={ [ styles.clickable, styles.noSelect ].join(' ') }
|
||||
>
|
||||
{
|
||||
type === 'ascii'
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.input.input'
|
||||
defaultMessage='input'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.input.data'
|
||||
defaultMessage='data'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
);
|
||||
const inputValue = (
|
||||
<span
|
||||
onClick={ this.toggleInputExpand }
|
||||
className={ expandable ? styles.clickable : '' }
|
||||
>
|
||||
<code className={ styles.inputData }>
|
||||
{ textToShow }
|
||||
</code>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.input.withInput'
|
||||
defaultMessage='with the {inputDesc} {inputValue}'
|
||||
values={ {
|
||||
inputDesc,
|
||||
inputValue
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTokenAction () {
|
||||
const { historic } = this.props;
|
||||
const { methodSignature, methodInputs } = this.state;
|
||||
const [to, value] = methodInputs;
|
||||
const address = to.value;
|
||||
|
||||
switch (TOKEN_METHODS[methodSignature]) {
|
||||
case 'transfer(to,value)':
|
||||
default:
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.token.transfer'
|
||||
defaultMessage='{historic, select, true {Transferred} false {Will transfer}} {value} to {address}'
|
||||
values={ {
|
||||
historic,
|
||||
value: (
|
||||
<span className={ styles.highlight }>
|
||||
{ this.renderTokenValue(value.value) }
|
||||
</span>
|
||||
),
|
||||
address: this.renderAddressName(address)
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderDeploy () {
|
||||
const { compact, historic, transaction } = this.props;
|
||||
const { methodInputs } = this.state;
|
||||
const { value } = transaction;
|
||||
|
||||
if (!historic) {
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.deploy.willDeploy'
|
||||
defaultMessage='Will deploy a contract'
|
||||
/>
|
||||
{
|
||||
value && value.gt(0)
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.deploy.withValue'
|
||||
defaultMessage=', sending {value}'
|
||||
values={ {
|
||||
value: this.renderEtherValue(value)
|
||||
} }
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.deploy.address'
|
||||
defaultMessage='Deployed a contract at address '
|
||||
/>
|
||||
</div>
|
||||
{ this.renderAddressName(transaction.creates, false) }
|
||||
{
|
||||
!compact && methodInputs && methodInputs.length
|
||||
? (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.deploy.params'
|
||||
defaultMessage='with the following parameters:'
|
||||
/>
|
||||
<div className={ styles.inputs }>
|
||||
{ this.renderInputs() }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValueReceipt () {
|
||||
const { historic, transaction } = this.props;
|
||||
const { isContract } = this.state;
|
||||
|
||||
const valueEth = (
|
||||
<span className={ styles.highlight }>
|
||||
{ this.renderEtherValue(transaction.value) }
|
||||
</span>
|
||||
);
|
||||
const aContract = isContract
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.receive.contract'
|
||||
defaultMessage='the contract '
|
||||
/>
|
||||
)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.receive.info'
|
||||
defaultMessage='{historic, select, true {Received} false {Will receive}} {valueEth} from {aContract}{address}'
|
||||
values={ {
|
||||
historic,
|
||||
valueEth,
|
||||
aContract,
|
||||
address: this.renderAddressName(transaction.from)
|
||||
} }
|
||||
/>
|
||||
{ this.renderInputValue() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValueTransfer () {
|
||||
const { historic, transaction } = this.props;
|
||||
const { isContract } = this.state;
|
||||
|
||||
const valueEth = (
|
||||
<span className={ styles.highlight }>
|
||||
{ this.renderEtherValue(transaction.value) }
|
||||
</span>
|
||||
);
|
||||
const aContract = isContract
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.transfer.contract'
|
||||
defaultMessage='the contract '
|
||||
/>
|
||||
)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.transfer.info'
|
||||
defaultMessage='{historic, select, true {Transferred} false {Will transfer}} {valueEth} to {aContract}{address}'
|
||||
values={ {
|
||||
historic,
|
||||
valueEth,
|
||||
aContract,
|
||||
address: this.renderAddressName(transaction.to)
|
||||
} }
|
||||
/>
|
||||
{ this.renderInputValue() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSignatureMethod () {
|
||||
const { compact, historic, transaction } = this.props;
|
||||
const { methodName, methodInputs } = this.state;
|
||||
|
||||
const showInputs = !compact && methodInputs && methodInputs.length > 0;
|
||||
const showEth = !!(transaction.value && transaction.value.gt(0));
|
||||
|
||||
const method = (
|
||||
<span className={ styles.name }>
|
||||
{ methodName }
|
||||
</span>
|
||||
);
|
||||
const ethValue = showEth && (
|
||||
<span className={ styles.highlight }>
|
||||
{ this.renderEtherValue(transaction.value) }
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<div className={ styles.description }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.signature.info'
|
||||
defaultMessage='{historic, select, true {Executed} false {Will execute}} the {method} function on the contract {address} {showEth, select, true {transferring {ethValue}} false {}} {showInputs, select, false {} true {passing the following {inputLength, plural, one {parameter} other {parameters}}}}'
|
||||
values={ {
|
||||
historic,
|
||||
method,
|
||||
ethValue,
|
||||
showEth,
|
||||
showInputs,
|
||||
address: this.renderAddressName(transaction.to),
|
||||
inputLength: methodInputs.length
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
showInputs
|
||||
? (
|
||||
<div className={ styles.inputs }>
|
||||
{ this.renderInputs() }
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderUnknownMethod () {
|
||||
const { historic, transaction } = this.props;
|
||||
|
||||
const method = (
|
||||
<span className={ styles.name }>
|
||||
an unknown/unregistered
|
||||
</span>
|
||||
);
|
||||
const ethValue = (
|
||||
<span className={ styles.highlight }>
|
||||
{ this.renderEtherValue(transaction.value) }
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ styles.details }>
|
||||
<FormattedMessage
|
||||
id='ui.methodDecoding.unknown.info'
|
||||
defaultMessage='{historic, select, true {Executed} false {Will execute}} the {method} on the contract {address} transferring {ethValue}.'
|
||||
values={ {
|
||||
historic,
|
||||
method,
|
||||
ethValue,
|
||||
address: this.renderAddressName(transaction.to)
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInputs () {
|
||||
const { methodInputs } = this.state;
|
||||
|
||||
if (!methodInputs || methodInputs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const inputs = methodInputs.map((input, index) => {
|
||||
const label = input.name
|
||||
? `${input.name}: ${input.type}`
|
||||
: input.type;
|
||||
|
||||
return (
|
||||
<TypedInput
|
||||
allowCopy
|
||||
className={ styles.input }
|
||||
label={ label }
|
||||
key={ index }
|
||||
param={ input.type }
|
||||
readOnly
|
||||
value={ input.value }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
renderTokenValue (value) {
|
||||
const { token } = this.props;
|
||||
|
||||
return (
|
||||
<span className={ styles.tokenValue }>
|
||||
{ value.div(token.format).toFormat(5) }<small> { token.tag }</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderEtherValue (value) {
|
||||
const { api } = this.context;
|
||||
const ether = api.util.fromWei(value);
|
||||
|
||||
return (
|
||||
<span className={ styles.etherValue }>
|
||||
{ ether.toFormat(5) }<small> ETH</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderAddressName (address, withName = true) {
|
||||
return (
|
||||
<div className={ styles.addressContainer }>
|
||||
<IdentityIcon
|
||||
address={ address }
|
||||
center
|
||||
inline
|
||||
/>
|
||||
<Label>{address}</Label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
toggleInputExpand = () => {
|
||||
if (window.getSelection && window.getSelection().type === 'Range') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
expandInput: !this.state.expandInput
|
||||
});
|
||||
}
|
||||
|
||||
toggleInputType = () => {
|
||||
const { inputType } = this.state;
|
||||
|
||||
if (inputType !== 'auto') {
|
||||
return this.setState({
|
||||
inputType: this.state.inputType === 'raw' ? 'ascii' : 'raw'
|
||||
});
|
||||
}
|
||||
|
||||
const ascii = this.getAscii();
|
||||
|
||||
return this.setState({
|
||||
inputType: ascii.valid ? 'raw' : 'ascii'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const { tokens } = initState;
|
||||
const { transaction } = initProps;
|
||||
|
||||
const token = Object.values(tokens).find((token) => token.address === transaction.to);
|
||||
|
||||
return () => {
|
||||
return { token };
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(MethodDecoding);
|
||||
367
js/packages/ui/MethodDecoding/methodDecodingStore.js
Normal file
367
js/packages/ui/MethodDecoding/methodDecodingStore.js
Normal file
@@ -0,0 +1,367 @@
|
||||
// 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 Abi from '@parity/abi';
|
||||
import { decodeMethodInput } from '@parity/api/util/decode';
|
||||
import Contracts from '@parity/shared/contracts';
|
||||
import * as abis from '@parity/shared/contracts/abi';
|
||||
|
||||
const CONTRACT_CREATE = '0x60606040';
|
||||
|
||||
let instance = null;
|
||||
|
||||
export default class MethodDecodingStore {
|
||||
api = null;
|
||||
|
||||
_bytecodes = {};
|
||||
_contractsAbi = {};
|
||||
_isContract = {};
|
||||
_methods = {};
|
||||
|
||||
constructor (api, contracts = {}) {
|
||||
this.api = api;
|
||||
|
||||
// Load the signatures from the local ABIs
|
||||
Object.keys(abis).forEach((abiKey) => {
|
||||
this.loadFromAbi(abis[abiKey]);
|
||||
});
|
||||
|
||||
this.addContracts(contracts);
|
||||
}
|
||||
|
||||
addContracts (contracts = {}) {
|
||||
// Load the User defined contracts
|
||||
Object.values(contracts).forEach((contract) => {
|
||||
if (!contract || !contract.meta || !contract.meta.abi) {
|
||||
return;
|
||||
}
|
||||
this.loadFromAbi(contract.meta.abi, contract.address);
|
||||
});
|
||||
}
|
||||
|
||||
loadFromAbi (_abi, contractAddress) {
|
||||
let abi;
|
||||
|
||||
try {
|
||||
abi = new Abi(_abi);
|
||||
} catch (error) {
|
||||
console.warn('loadFromAbi', error, _abi);
|
||||
}
|
||||
|
||||
if (!abi) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (contractAddress) {
|
||||
this._contractsAbi[contractAddress] = abi;
|
||||
}
|
||||
|
||||
abi
|
||||
.functions
|
||||
.map((f) => ({ sign: f.signature, abi: f.abi }))
|
||||
.forEach((mapping) => {
|
||||
const sign = (/^0x/.test(mapping.sign) ? '' : '0x') + mapping.sign;
|
||||
|
||||
this._methods[sign] = mapping.abi;
|
||||
});
|
||||
}
|
||||
|
||||
static get (api, contracts = {}) {
|
||||
if (!instance) {
|
||||
instance = new MethodDecodingStore(api, contracts);
|
||||
}
|
||||
|
||||
// Set API if not set yet
|
||||
if (!instance.api) {
|
||||
instance.api = api;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static loadContracts (contracts = {}) {
|
||||
if (!instance) {
|
||||
// Just create the instance with null API
|
||||
MethodDecodingStore.get(null, contracts);
|
||||
} else {
|
||||
instance.addContracts(contracts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a transaction in the context of the given
|
||||
* address
|
||||
*
|
||||
* @param {String} address The address contract
|
||||
* @param {Object} transaction The transaction to lookup
|
||||
* @return {Promise} The result of the lookup. Resolves with:
|
||||
* {
|
||||
* contract: Boolean,
|
||||
* deploy: Boolean,
|
||||
* inputs: Array,
|
||||
* name: String,
|
||||
* params: Array,
|
||||
* received: Boolean,
|
||||
* signature: String
|
||||
* }
|
||||
*/
|
||||
lookup (currentAddress, transaction) {
|
||||
const result = {};
|
||||
|
||||
if (!transaction) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const isReceived = transaction.to === currentAddress;
|
||||
const contractAddress = isReceived ? transaction.from : transaction.to;
|
||||
const input = transaction.input || transaction.data;
|
||||
|
||||
result.input = input;
|
||||
result.received = isReceived;
|
||||
|
||||
// No input, should be a ETH transfer
|
||||
if (!input || input === '0x') {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
if (!transaction.to) {
|
||||
return this.decodeContractCreation(result);
|
||||
}
|
||||
|
||||
let signature;
|
||||
|
||||
try {
|
||||
const decodeCallDataResult = this.api.util.decodeCallData(input);
|
||||
|
||||
signature = decodeCallDataResult.signature;
|
||||
} catch (e) {}
|
||||
|
||||
// Contract deployment
|
||||
if (!signature || signature === CONTRACT_CREATE || transaction.creates) {
|
||||
const address = contractAddress || transaction.creates;
|
||||
|
||||
return this.isContractCreation(input, address)
|
||||
.then((isContractCreation) => {
|
||||
if (!isContractCreation) {
|
||||
result.contract = false;
|
||||
result.deploy = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return this.decodeContractCreation(result, address);
|
||||
});
|
||||
}
|
||||
|
||||
return this
|
||||
.isContract(contractAddress)
|
||||
.then((isContract) => {
|
||||
result.contract = isContract;
|
||||
|
||||
if (!isContract) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { signature, paramdata } = this.api.util.decodeCallData(input);
|
||||
|
||||
result.signature = signature;
|
||||
result.params = paramdata;
|
||||
|
||||
return this
|
||||
.fetchMethodAbi(signature)
|
||||
.then((abi) => {
|
||||
let methodName = null;
|
||||
let methodInputs = null;
|
||||
|
||||
if (abi) {
|
||||
methodName = abi.name;
|
||||
methodInputs = this.api.util
|
||||
.decodeMethodInput(abi, paramdata)
|
||||
.map((value, index) => {
|
||||
const { name, type } = abi.inputs[index];
|
||||
|
||||
return { name, type, value };
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
name: methodName,
|
||||
inputs: methodInputs
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('lookup', error);
|
||||
});
|
||||
}
|
||||
|
||||
decodeContractCreation (data, contractAddress = '') {
|
||||
const result = {
|
||||
...data,
|
||||
contract: true,
|
||||
deploy: true
|
||||
};
|
||||
|
||||
const { input } = data;
|
||||
const abi = this._contractsAbi[contractAddress];
|
||||
|
||||
if (!abi || !abi.constructors || abi.constructors.length === 0) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const constructorAbi = abi.constructors[0];
|
||||
|
||||
const rawInput = /^(?:0x)?(.*)$/.exec(input)[1];
|
||||
|
||||
return this
|
||||
.getCode(contractAddress)
|
||||
.then((code) => {
|
||||
if (!code || /^(0x)0*?$/.test(code)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const rawCode = /^(?:0x)?(.*)$/.exec(code)[1];
|
||||
const codeOffset = rawInput.indexOf(rawCode);
|
||||
|
||||
if (codeOffset === -1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Params are the last bytes of the transaction Input
|
||||
// (minus the bytecode). It seems that they are repeated
|
||||
// twice
|
||||
const params = rawInput.slice(codeOffset + rawCode.length);
|
||||
const paramsBis = params.slice(params.length / 2);
|
||||
|
||||
let decodedInputs;
|
||||
|
||||
try {
|
||||
decodedInputs = decodeMethodInput(constructorAbi, params);
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
if (!decodedInputs) {
|
||||
decodedInputs = decodeMethodInput(constructorAbi, paramsBis);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (decodedInputs && decodedInputs.length > 0) {
|
||||
result.inputs = decodedInputs
|
||||
.map((value, index) => {
|
||||
const type = constructorAbi.inputs[index].kind.type;
|
||||
|
||||
return { type, value };
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
fetchMethodAbi (signature) {
|
||||
if (this._methods[signature] !== undefined) {
|
||||
return Promise.resolve(this._methods[signature]);
|
||||
}
|
||||
|
||||
this._methods[signature] = Contracts.get(this.api)
|
||||
.signatureReg
|
||||
.lookup(signature)
|
||||
.then((method) => {
|
||||
let abi = null;
|
||||
|
||||
if (method && method.length) {
|
||||
abi = this.api.util.methodToAbi(method);
|
||||
}
|
||||
|
||||
this._methods[signature] = abi;
|
||||
return this._methods[signature];
|
||||
});
|
||||
|
||||
return Promise.resolve(this._methods[signature]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks (and caches) if the given address is a
|
||||
* Contract or not, from its fetched bytecode
|
||||
*/
|
||||
isContract (contractAddress) {
|
||||
// If zero address, it isn't a contract
|
||||
if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (this._isContract[contractAddress]) {
|
||||
return Promise.resolve(this._isContract[contractAddress]);
|
||||
}
|
||||
|
||||
this._isContract[contractAddress] = this
|
||||
.getCode(contractAddress)
|
||||
.then((bytecode) => {
|
||||
// Is a contract if the address contains *valid* bytecode
|
||||
const _isContract = bytecode && /^(0x)?([0]*[1-9a-f]+[0]*)+$/.test(bytecode);
|
||||
|
||||
this._isContract[contractAddress] = _isContract;
|
||||
return this._isContract[contractAddress];
|
||||
});
|
||||
|
||||
return Promise.resolve(this._isContract[contractAddress]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the input resulted in a contract creation
|
||||
* by checking that the contract address code contains
|
||||
* a part of the input, or vice-versa
|
||||
*/
|
||||
isContractCreation (input, contractAddress) {
|
||||
return this.api.eth
|
||||
.getCode(contractAddress)
|
||||
.then((code) => {
|
||||
if (/^(0x)?0*$/.test(code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const strippedCode = code.replace(/^0x/, '');
|
||||
const strippedInput = input.replace(/^0x/, '');
|
||||
|
||||
return strippedInput.indexOf(strippedInput) >= 0 || strippedCode.indexOf(strippedInput) >= 0;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
getCode (contractAddress) {
|
||||
// If zero address, resolve to '0x'
|
||||
if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) {
|
||||
return Promise.resolve('0x');
|
||||
}
|
||||
|
||||
if (this._bytecodes[contractAddress]) {
|
||||
return Promise.resolve(this._bytecodes[contractAddress]);
|
||||
}
|
||||
|
||||
this._bytecodes[contractAddress] = this.api.eth
|
||||
.getCode(contractAddress)
|
||||
.then((bytecode) => {
|
||||
this._bytecodes[contractAddress] = bytecode;
|
||||
return this._bytecodes[contractAddress];
|
||||
});
|
||||
|
||||
return Promise.resolve(this._bytecodes[contractAddress]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user