// Copyright 2015, 2016 Ethcore (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 BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import CircularProgress from 'material-ui/CircularProgress'; import Contracts from '~/contracts'; import { Input, InputAddress } from '../Form'; import styles from './methodDecoding.css'; const ASCII_INPUT = /^[a-z0-9\s,?;.:/!()-_@'"#]+$/i; const CONTRACT_CREATE = '0x60606040'; const TOKEN_METHODS = { '0xa9059cbb': 'transfer(to,value)' }; class MethodDecoding extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { address: PropTypes.string.isRequired, tokens: PropTypes.object, transaction: PropTypes.object, historic: PropTypes.bool } state = { contractAddress: null, method: null, methodName: null, methodInputs: null, methodParams: null, methodSignature: null, token: null, isContract: false, isDeploy: false, isReceived: false, isLoading: true, expandInput: false, inputType: 'auto' } componentWillMount () { const lookupResult = this.lookup(); if (typeof lookupResult === 'object' && typeof lookupResult.then === 'function') { lookupResult.then(() => this.setState({ isLoading: false })); } else { this.setState({ isLoading: false }); } } 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) }
); } renderAction () { const { methodName, methodInputs, methodSignature, token, 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.hex2Ascii(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; if (!historic) { return (
Will deploy a contract.
); } return (
Deployed a contract at address
{ this.renderAddressName(transaction.creates, false) }
); } 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; return methodInputs.map((input, index) => { switch (input.type) { case 'address': return ( ); default: return ( ); } }); } renderValue (value) { const { api } = this.context; if (api.util.isInstanceOf(value, BigNumber)) { return value.toFormat(0); } else if (api.util.isArray(value)) { return api.util.bytesToHex(value); } return value.toString(); } renderTokenValue (value) { const { token } = this.state; 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' }); } lookup () { const { transaction } = this.props; if (!transaction) { return; } const { api } = this.context; const { address, tokens } = this.props; const isReceived = transaction.to === address; const contractAddress = isReceived ? transaction.from : transaction.to; const input = transaction.input || transaction.data; const token = (tokens || {})[contractAddress]; this.setState({ token, isReceived, contractAddress }); if (!input || input === '0x') { 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(input); this.setState({ methodSignature: signature, methodParams: paramdata }); if (!signature || signature === CONTRACT_CREATE || transaction.creates) { this.setState({ isDeploy: true }); return; } return Contracts.get() .signatureReg .lookup(signature) .then((method) => { let methodInputs = null; let methodName = null; if (method && method.length) { const { methodParams } = this.state; const abi = api.util.methodToAbi(method); methodName = abi.name; methodInputs = api.util .decodeMethodInput(abi, methodParams) .map((value, index) => { const type = abi.inputs[index].type; return { type, value }; }); } this.setState({ method, methodName, methodInputs, bytecode }); }); }) .catch((error) => { console.warn('lookup', error); }); } } function mapStateToProps (state) { const { tokens } = state.balances; return { tokens }; } function mapDispatchToProps (dispatch) { return bindActionCreators({}, dispatch); } export default connect( mapStateToProps, mapDispatchToProps )(MethodDecoding);