// 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 . import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import CircularProgress from 'material-ui/CircularProgress'; import { TypedInput, InputAddress } from '../Form'; 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.isRequired, token: PropTypes.object, transaction: PropTypes.object, historic: PropTypes.bool } 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 (
); } return (
{ this.renderAction() } { this.renderGas() }
); } renderGas () { const { historic, transaction } = this.props; const { gas, gasPrice } = transaction; if (!gas || !gasPrice) { return null; } const gasValue = gas.mul(gasPrice); return (
{ historic ? 'Provided' : 'Provides' } { gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/ETH) for a total transaction value of { this.renderEtherValue(gasValue) } { this.renderMinBlock() }
); } renderMinBlock () { const { historic, transaction } = this.props; const { minBlock } = transaction; if (!minBlock || minBlock.eq(0)) { return null; } return ( , { historic ? 'Submitted' : 'Submission' } at block #{ minBlock.toFormat(0) } ); } 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 ascii = api.util.hexToAscii(transaction.input || transaction.data); return { value: ascii, valid: ASCII_INPUT.test(ascii) }; } renderInputValue () { const { transaction } = this.props; 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) + '...'; return (
with the { type === 'ascii' ? 'input' : 'data' }   { textToShow }
); } 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 (
{ historic ? 'Transferred' : 'Will transfer' } { this.renderTokenValue(value.value) } to
{ this.renderAddressName(address) }
); } } renderDeploy () { const { historic, transaction } = this.props; const { methodInputs } = this.state; if (!historic) { return (
Will deploy a contract.
); } return (
Deployed a contract at address
{ this.renderAddressName(transaction.creates, false) }
{ methodInputs && methodInputs.length ? 'with the following parameters:' : ''}
{ this.renderInputs() }
); } renderValueReceipt () { const { historic, transaction } = this.props; const { isContract } = this.state; return (
{ historic ? 'Received' : 'Will receive' } { this.renderEtherValue(transaction.value) } from { isContract ? 'the contract' : '' }
{ this.renderAddressName(transaction.from) } { this.renderInputValue() }
); } renderValueTransfer () { const { historic, transaction } = this.props; const { isContract } = this.state; return (
{ historic ? 'Transferred' : 'Will transfer' } { this.renderEtherValue(transaction.value) } to { isContract ? 'the contract' : '' }
{ this.renderAddressName(transaction.to) } { this.renderInputValue() }
); } renderSignatureMethod () { const { historic, transaction } = this.props; const { methodName, methodInputs } = this.state; return (
{ historic ? 'Executed' : 'Will execute' } the { methodName } function on the contract
{ this.renderAddressName(transaction.to) }
transferring { this.renderEtherValue(transaction.value) } { methodInputs.length ? ', passing the following parameters:' : '.' }
{ this.renderInputs() }
); } renderUnknownMethod () { const { historic, transaction } = this.props; return (
{ historic ? 'Executed' : 'Will execute' } an unknown/unregistered method on the contract
{ this.renderAddressName(transaction.to) }
transferring { this.renderEtherValue(transaction.value) } .
); } 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 ( ); }); return inputs; } renderValue (value) { const { api } = this.context; if (api.util.isArray(value)) { return api.util.bytesToHex(value); } return value.toString(); } renderTokenValue (value) { const { token } = this.props; return ( { value.div(token.format).toFormat(5) } { token.tag } ); } renderEtherValue (value) { const { api } = this.context; const ether = api.util.fromWei(value); return ( { ether.toFormat(5) } ETH ); } renderAddressName (address, withName = true) { return (
); } 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.balances; const { address } = initProps; const token = (tokens || {})[address]; return () => { return { token }; }; } export default connect( mapStateToProps, null )(MethodDecoding);