Enhanced MethodDecoding in Transactions list (#3454)
* Better MethodDecoding UI // No empty params #3452 * Display input if ASCII in transactions #3434 * Fixes UI for #3454 * Better MethodDecoding contract detection #3454
This commit is contained in:
parent
a7574a1108
commit
18f570327e
@ -17,3 +17,15 @@
|
|||||||
export function bytesToHex (bytes) {
|
export function bytesToHex (bytes) {
|
||||||
return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join('');
|
return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hex2Ascii (_hex) {
|
||||||
|
const hex = /^(?:0x)?(.*)$/.exec(_hex.toString())[1];
|
||||||
|
|
||||||
|
let str = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
|
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
|
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
|
||||||
import { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
|
import { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
|
||||||
import { bytesToHex } from './format';
|
import { bytesToHex, hex2Ascii } from './format';
|
||||||
import { fromWei, toWei } from './wei';
|
import { fromWei, toWei } from './wei';
|
||||||
import { sha3 } from './sha3';
|
import { sha3 } from './sha3';
|
||||||
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
|
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
|
||||||
@ -30,6 +30,7 @@ export default {
|
|||||||
isInstanceOf,
|
isInstanceOf,
|
||||||
isString,
|
isString,
|
||||||
bytesToHex,
|
bytesToHex,
|
||||||
|
hex2Ascii,
|
||||||
createIdentityImg,
|
createIdentityImg,
|
||||||
decodeCallData,
|
decodeCallData,
|
||||||
decodeMethodInput,
|
decodeMethodInput,
|
||||||
|
@ -144,7 +144,7 @@ export default class Input extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCopyButton () {
|
renderCopyButton () {
|
||||||
const { allowCopy, hideUnderline, label, hint, floatCopy } = this.props;
|
const { allowCopy, label, hint, floatCopy } = this.props;
|
||||||
const { value } = this.state;
|
const { value } = this.state;
|
||||||
|
|
||||||
if (!allowCopy) {
|
if (!allowCopy) {
|
||||||
@ -159,7 +159,7 @@ export default class Input extends Component {
|
|||||||
? allowCopy
|
? allowCopy
|
||||||
: value;
|
: value;
|
||||||
|
|
||||||
if (hideUnderline && !label) {
|
if (!label) {
|
||||||
style.marginBottom = 2;
|
style.marginBottom = 2;
|
||||||
} else if (label && !hint) {
|
} else if (label && !hint) {
|
||||||
style.marginBottom = 4;
|
style.marginBottom = 4;
|
||||||
@ -182,6 +182,7 @@ export default class Input extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onChange = (event, value) => {
|
onChange = (event, value) => {
|
||||||
|
event.persist();
|
||||||
this.setValue(value, () => {
|
this.setValue(value, () => {
|
||||||
this.props.onChange && this.props.onChange(event, value);
|
this.props.onChange && this.props.onChange(event, value);
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
.input input {
|
.input input {
|
||||||
padding-left: 48px !important;
|
padding-left: 48px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputEmpty input {
|
.inputEmpty input {
|
||||||
|
@ -76,6 +76,7 @@ class InputAddress extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const classes = [disabled ? styles.iconDisabled : styles.icon];
|
const classes = [disabled ? styles.iconDisabled : styles.icon];
|
||||||
|
|
||||||
if (!label) {
|
if (!label) {
|
||||||
classes.push(styles.noLabel);
|
classes.push(styles.noLabel);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,12 @@
|
|||||||
.container {
|
.container {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.details,
|
.details,
|
||||||
.gasDetails {
|
.gasDetails {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
@ -46,7 +52,7 @@
|
|||||||
.highlight {
|
.highlight {
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputs {
|
.inputs, .addressContainer {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,3 +79,7 @@
|
|||||||
margin-bottom: -10px;
|
margin-bottom: -10px;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inputData {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
@ -18,14 +18,14 @@ import BigNumber from 'bignumber.js';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import CircularProgress from 'material-ui/CircularProgress';
|
||||||
|
|
||||||
import Contracts from '../../contracts';
|
import Contracts from '../../contracts';
|
||||||
import IdentityIcon from '../IdentityIcon';
|
|
||||||
import IdentityName from '../IdentityName';
|
|
||||||
import { Input, InputAddress } from '../Form';
|
import { Input, InputAddress } from '../Form';
|
||||||
|
|
||||||
import styles from './methodDecoding.css';
|
import styles from './methodDecoding.css';
|
||||||
|
|
||||||
|
const ASCII_INPUT = /^[a-z0-9\s,?;.:/!()-_@'"#]+$/i;
|
||||||
const CONTRACT_CREATE = '0x60606040';
|
const CONTRACT_CREATE = '0x60606040';
|
||||||
const TOKEN_METHODS = {
|
const TOKEN_METHODS = {
|
||||||
'0xa9059cbb': 'transfer(to,value)'
|
'0xa9059cbb': 'transfer(to,value)'
|
||||||
@ -53,20 +53,36 @@ class MethodDecoding extends Component {
|
|||||||
token: null,
|
token: null,
|
||||||
isContract: false,
|
isContract: false,
|
||||||
isDeploy: false,
|
isDeploy: false,
|
||||||
isReceived: false
|
isReceived: false,
|
||||||
|
isLoading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
this.lookup();
|
const lookupResult = this.lookup();
|
||||||
|
|
||||||
|
if (typeof lookupResult === 'object' && typeof lookupResult.then === 'function') {
|
||||||
|
lookupResult.then(() => this.setState({ isLoading: false }));
|
||||||
|
} else {
|
||||||
|
this.setState({ isLoading: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { transaction } = this.props;
|
const { transaction } = this.props;
|
||||||
|
const { isLoading } = this.state;
|
||||||
|
|
||||||
if (!transaction) {
|
if (!transaction) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.loading }>
|
||||||
|
<CircularProgress size={ 60 } thickness={ 2 } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
{ this.renderAction() }
|
{ this.renderAction() }
|
||||||
@ -82,26 +98,33 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.gasDetails }>
|
<div className={ styles.gasDetails }>
|
||||||
{ historic ? 'Provided' : 'Provides' } <span className={ styles.highlight }>{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)</span> for a total transaction value of <span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
<span>{ historic ? 'Provided' : 'Provides' } </span>
|
||||||
|
<span className={ styles.highlight }>
|
||||||
|
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
|
||||||
|
</span>
|
||||||
|
<span> for a total transaction value of </span>
|
||||||
|
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAction () {
|
renderAction () {
|
||||||
const { methodName, methodInputs, methodSignature, token, isDeploy, isReceived } = this.state;
|
const { methodName, methodInputs, methodSignature, token, isDeploy, isReceived, isContract } = this.state;
|
||||||
|
|
||||||
if (isDeploy) {
|
if (isDeploy) {
|
||||||
return this.renderDeploy();
|
return this.renderDeploy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (methodSignature) {
|
if (isContract && methodSignature) {
|
||||||
if (token && TOKEN_METHODS[methodSignature] && methodInputs) {
|
if (token && TOKEN_METHODS[methodSignature] && methodInputs) {
|
||||||
return this.renderTokenAction();
|
return this.renderTokenAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
return methodName
|
if (methodName) {
|
||||||
? this.renderSignatureMethod()
|
return this.renderSignatureMethod();
|
||||||
: this.renderUnknownMethod();
|
}
|
||||||
|
|
||||||
|
return this.renderUnknownMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
return isReceived
|
return isReceived
|
||||||
@ -109,6 +132,28 @@ class MethodDecoding extends Component {
|
|||||||
: this.renderValueTransfer();
|
: this.renderValueTransfer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderInputValue () {
|
||||||
|
const { api } = this.context;
|
||||||
|
const { transaction } = this.props;
|
||||||
|
|
||||||
|
if (!/^(0x)?([0]*[1-9a-f]+[0]*)+$/.test(transaction.input)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ascii = api.util.hex2Ascii(transaction.input);
|
||||||
|
|
||||||
|
const text = ASCII_INPUT.test(ascii)
|
||||||
|
? ascii
|
||||||
|
: transaction.input;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>with the input </span>
|
||||||
|
<code className={ styles.inputData }>{ text }</code>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderTokenAction () {
|
renderTokenAction () {
|
||||||
const { historic } = this.props;
|
const { historic } = this.props;
|
||||||
const { methodSignature, methodInputs } = this.state;
|
const { methodSignature, methodInputs } = this.state;
|
||||||
@ -120,7 +165,15 @@ class MethodDecoding extends Component {
|
|||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div className={ styles.details }>
|
<div className={ styles.details }>
|
||||||
{ historic ? 'Transferred' : 'Will transfer' } <span className={ styles.highlight }>{ this.renderTokenValue(value.value) }</span> to <span className={ styles.highlight }>{ this.renderAddressName(address) }</span>.
|
<div>
|
||||||
|
<span>{ historic ? 'Transferred' : 'Will transfer' } </span>
|
||||||
|
<span className={ styles.highlight }>
|
||||||
|
{ this.renderTokenValue(value.value) }
|
||||||
|
</span>
|
||||||
|
<span> to </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderAddressName(address) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -139,7 +192,11 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.details }>
|
<div className={ styles.details }>
|
||||||
Deployed a contract at address <span className={ styles.highlight }>{ this.renderAddressName(transaction.creates, false) }</span>
|
<div>
|
||||||
|
<span>Deployed a contract at address </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderAddressName(transaction.creates, false) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -150,7 +207,16 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.details }>
|
<div className={ styles.details }>
|
||||||
{ historic ? 'Received' : 'Will receive' } <span className={ styles.highlight }>{ this.renderEtherValue(transaction.value) }</span> from { isContract ? 'the contract' : '' } <span className={ styles.highlight }>{ this.renderAddressName(transaction.from) }</span>
|
<div>
|
||||||
|
<span>{ historic ? 'Received' : 'Will receive' } </span>
|
||||||
|
<span className={ styles.highlight }>
|
||||||
|
{ this.renderEtherValue(transaction.value) }
|
||||||
|
</span>
|
||||||
|
<span> from { isContract ? 'the contract' : '' } </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderAddressName(transaction.from) }
|
||||||
|
{ this.renderInputValue() }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -161,19 +227,44 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.details }>
|
<div className={ styles.details }>
|
||||||
{ historic ? 'Transferred' : 'Will transfer' } <span className={ styles.highlight }>{ this.renderEtherValue(transaction.value) }</span> to { isContract ? 'the contract' : '' } <span className={ styles.highlight }>{ this.renderAddressName(transaction.to) }</span>
|
<div>
|
||||||
|
<span>{ historic ? 'Transferred' : 'Will transfer' } </span>
|
||||||
|
<span className={ styles.highlight }>
|
||||||
|
{ this.renderEtherValue(transaction.value) }
|
||||||
|
</span>
|
||||||
|
<span> to { isContract ? 'the contract' : '' } </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderAddressName(transaction.to) }
|
||||||
|
{ this.renderInputValue() }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSignatureMethod () {
|
renderSignatureMethod () {
|
||||||
const { historic, transaction } = this.props;
|
const { historic, transaction } = this.props;
|
||||||
const { methodName } = this.state;
|
const { methodName, methodInputs } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.details }>
|
<div className={ styles.details }>
|
||||||
<div className={ styles.description }>
|
<div className={ styles.description }>
|
||||||
{ historic ? 'Executed' : 'Will execute' } the <span className={ styles.name }>{ methodName }</span> function on the contract <span className={ styles.highlight }>{ this.renderAddressName(transaction.to) }</span>, transferring <span className={ styles.highlight }>{ this.renderEtherValue(transaction.value) }</span>, passing the following parameters:
|
<div>
|
||||||
|
<span>{ historic ? 'Executed' : 'Will execute' } the </span>
|
||||||
|
<span className={ styles.name }>{ methodName }</span>
|
||||||
|
<span> function on the contract </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderAddressName(transaction.to) }
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>transferring </span>
|
||||||
|
<span className={ styles.highlight }>
|
||||||
|
{ this.renderEtherValue(transaction.value) }
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{ methodInputs.length ? ', passing the following parameters:' : '.' }
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.inputs }>
|
<div className={ styles.inputs }>
|
||||||
{ this.renderInputs() }
|
{ this.renderInputs() }
|
||||||
@ -187,7 +278,21 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.details }>
|
<div className={ styles.details }>
|
||||||
{ historic ? 'Executed' : 'Will execute' } <span className={ styles.name }>an unknown/unregistered</span> method on the contract <span className={ styles.highlight }>{ this.renderAddressName(transaction.to) }</span>, transferring <span className={ styles.highlight }>{ this.renderEtherValue(transaction.value) }</span>.
|
<div>
|
||||||
|
<span>{ historic ? 'Executed' : 'Will execute' } </span>
|
||||||
|
<span className={ styles.name }>an unknown/unregistered</span>
|
||||||
|
<span> method on the contract </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderAddressName(transaction.to) }
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>transferring </span>
|
||||||
|
<span className={ styles.highlight }>
|
||||||
|
{ this.renderEtherValue(transaction.value) }
|
||||||
|
</span>
|
||||||
|
<span>.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -257,10 +362,14 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
renderAddressName (address, withName = true) {
|
renderAddressName (address, withName = true) {
|
||||||
return (
|
return (
|
||||||
<span className={ styles.address }>
|
<div className={ styles.addressContainer }>
|
||||||
<IdentityIcon center inline address={ address } className={ styles.identityicon } />
|
<InputAddress
|
||||||
{ withName ? <IdentityName address={ address } /> : address }
|
disabled
|
||||||
</span>
|
className={ styles.address }
|
||||||
|
value={ address }
|
||||||
|
text={ withName }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +393,21 @@ class MethodDecoding extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (contractAddress === '0x') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.eth
|
||||||
|
.getCode(contractAddress || transaction.creates)
|
||||||
|
.then((bytecode) => {
|
||||||
|
const isContract = bytecode && /^(0x)?([0]*[1-9a-f]+[0]*)+$/.test(bytecode);
|
||||||
|
|
||||||
|
this.setState({ isContract });
|
||||||
|
|
||||||
|
if (!isContract) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { signature, paramdata } = api.util.decodeCallData(transaction.input);
|
const { signature, paramdata } = api.util.decodeCallData(transaction.input);
|
||||||
this.setState({ methodSignature: signature, methodParams: paramdata });
|
this.setState({ methodSignature: signature, methodParams: paramdata });
|
||||||
|
|
||||||
@ -292,12 +416,10 @@ class MethodDecoding extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise
|
return Contracts.get()
|
||||||
.all([
|
.signatureReg
|
||||||
api.eth.getCode(contractAddress),
|
.lookup(signature)
|
||||||
Contracts.get().signatureReg.lookup(signature)
|
.then((method) => {
|
||||||
])
|
|
||||||
.then(([bytecode, method]) => {
|
|
||||||
let methodInputs = null;
|
let methodInputs = null;
|
||||||
let methodName = null;
|
let methodName = null;
|
||||||
|
|
||||||
@ -319,8 +441,8 @@ class MethodDecoding extends Component {
|
|||||||
method,
|
method,
|
||||||
methodName,
|
methodName,
|
||||||
methodInputs,
|
methodInputs,
|
||||||
bytecode,
|
bytecode
|
||||||
isContract: bytecode && bytecode !== '0x'
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user