Show contract parameters in MethodDecoding (#4024)
* Add decoding of Inner Contract Deployment params #3715 * Fixed TypedInput when formatted value * Fix TypedInput * PR Grumble * Add test to `Param.toParams`
This commit is contained in:
committed by
Jaco Greeff
parent
ddeb06d9cc
commit
d16ab5eac5
@@ -14,12 +14,11 @@
|
||||
// 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 React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import CircularProgress from 'material-ui/CircularProgress';
|
||||
|
||||
import { Input, InputAddress } from '../Form';
|
||||
import { TypedInput, InputAddress } from '../Form';
|
||||
import MethodDecodingStore from './methodDecodingStore';
|
||||
|
||||
import styles from './methodDecoding.css';
|
||||
@@ -245,6 +244,7 @@ class MethodDecoding extends Component {
|
||||
|
||||
renderDeploy () {
|
||||
const { historic, transaction } = this.props;
|
||||
const { methodInputs } = this.state;
|
||||
|
||||
if (!historic) {
|
||||
return (
|
||||
@@ -261,6 +261,14 @@ class MethodDecoding extends Component {
|
||||
</div>
|
||||
|
||||
{ this.renderAddressName(transaction.creates, false) }
|
||||
|
||||
<div>
|
||||
{ methodInputs && methodInputs.length ? 'with the following parameters:' : ''}
|
||||
</div>
|
||||
|
||||
<div className={ styles.inputs }>
|
||||
{ this.renderInputs() }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -364,39 +372,31 @@ class MethodDecoding extends Component {
|
||||
renderInputs () {
|
||||
const { methodInputs } = this.state;
|
||||
|
||||
return methodInputs.map((input, index) => {
|
||||
switch (input.type) {
|
||||
case 'address':
|
||||
return (
|
||||
<InputAddress
|
||||
disabled
|
||||
text
|
||||
key={ index }
|
||||
className={ styles.input }
|
||||
value={ input.value }
|
||||
label={ input.type } />
|
||||
);
|
||||
if (!methodInputs || methodInputs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default:
|
||||
return (
|
||||
<Input
|
||||
readOnly
|
||||
allowCopy
|
||||
key={ index }
|
||||
className={ styles.input }
|
||||
value={ this.renderValue(input.value) }
|
||||
label={ input.type } />
|
||||
);
|
||||
}
|
||||
const inputs = methodInputs.map((input, index) => {
|
||||
return (
|
||||
<TypedInput
|
||||
allowCopy
|
||||
className={ styles.input }
|
||||
label={ input.type }
|
||||
key={ index }
|
||||
param={ input.type }
|
||||
readOnly
|
||||
value={ this.renderValue(input.value) }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
renderValue (value) {
|
||||
const { api } = this.context;
|
||||
|
||||
if (api.util.isInstanceOf(value, BigNumber)) {
|
||||
return value.toFormat(0);
|
||||
} else if (api.util.isArray(value)) {
|
||||
if (api.util.isArray(value)) {
|
||||
return api.util.bytesToHex(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import Contracts from '~/contracts';
|
||||
import Abi from '~/abi';
|
||||
import * as abis from '~/contracts/abi';
|
||||
|
||||
import { decodeMethodInput } from '~/api/util/decode';
|
||||
|
||||
const CONTRACT_CREATE = '0x60606040';
|
||||
|
||||
let instance = null;
|
||||
@@ -26,6 +28,8 @@ export default class MethodDecodingStore {
|
||||
|
||||
api = null;
|
||||
|
||||
_bytecodes = {};
|
||||
_contractsAbi = {};
|
||||
_isContract = {};
|
||||
_methods = {};
|
||||
|
||||
@@ -46,12 +50,17 @@ export default class MethodDecodingStore {
|
||||
if (!contract || !contract.meta || !contract.meta.abi) {
|
||||
return;
|
||||
}
|
||||
this.loadFromAbi(contract.meta.abi);
|
||||
this.loadFromAbi(contract.meta.abi, contract.address);
|
||||
});
|
||||
}
|
||||
|
||||
loadFromAbi (_abi) {
|
||||
loadFromAbi (_abi, contractAddress) {
|
||||
const abi = new Abi(_abi);
|
||||
|
||||
if (contractAddress && abi) {
|
||||
this._contractsAbi[contractAddress] = abi;
|
||||
}
|
||||
|
||||
abi
|
||||
.functions
|
||||
.map((f) => ({ sign: f.signature, abi: f.abi }))
|
||||
@@ -111,6 +120,7 @@ export default class MethodDecodingStore {
|
||||
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
|
||||
@@ -118,17 +128,20 @@ export default class MethodDecodingStore {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
try {
|
||||
const { signature } = this.api.util.decodeCallData(input);
|
||||
let signature;
|
||||
|
||||
if (signature === CONTRACT_CREATE || transaction.creates) {
|
||||
result.contract = true;
|
||||
return Promise.resolve({ ...result, deploy: true });
|
||||
}
|
||||
try {
|
||||
const decodeCallDataResult = this.api.util.decodeCallData(input);
|
||||
signature = decodeCallDataResult.signature;
|
||||
} catch (e) {}
|
||||
|
||||
// Contract deployment
|
||||
if (!signature || signature === CONTRACT_CREATE || transaction.creates) {
|
||||
return this.decodeContractCreation(result, contractAddress || transaction.creates);
|
||||
}
|
||||
|
||||
return this
|
||||
.isContract(contractAddress || transaction.creates)
|
||||
.isContract(contractAddress)
|
||||
.then((isContract) => {
|
||||
result.contract = isContract;
|
||||
|
||||
@@ -140,11 +153,6 @@ export default class MethodDecodingStore {
|
||||
result.signature = signature;
|
||||
result.params = paramdata;
|
||||
|
||||
// Contract deployment
|
||||
if (!signature) {
|
||||
return Promise.resolve({ ...result, deploy: true });
|
||||
}
|
||||
|
||||
return this
|
||||
.fetchMethodAbi(signature)
|
||||
.then((abi) => {
|
||||
@@ -173,6 +181,68 @@ export default class MethodDecodingStore {
|
||||
});
|
||||
}
|
||||
|
||||
decodeContractCreation (data, contractAddress) {
|
||||
const result = {
|
||||
...data,
|
||||
contract: true,
|
||||
deploy: true
|
||||
};
|
||||
|
||||
const { input } = data;
|
||||
const abi = this._contractsAbi[contractAddress];
|
||||
|
||||
if (!input || !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]);
|
||||
@@ -209,7 +279,7 @@ export default class MethodDecodingStore {
|
||||
return Promise.resolve(this._isContract[contractAddress]);
|
||||
}
|
||||
|
||||
this._isContract[contractAddress] = this.api.eth
|
||||
this._isContract[contractAddress] = this
|
||||
.getCode(contractAddress)
|
||||
.then((bytecode) => {
|
||||
// Is a contract if the address contains *valid* bytecode
|
||||
@@ -222,4 +292,24 @@ export default class MethodDecodingStore {
|
||||
return Promise.resolve(this._isContract[contractAddress]);
|
||||
}
|
||||
|
||||
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