Added contract profile api for fiddle

Compiled fiddles are automatically profiled on response of compilation via saga. The profile is passed to the ContractFunctions component for constructor/function view.
This commit is contained in:
emizzle 2018-08-31 18:23:09 +10:00
parent ea63e406e7
commit 69aea01b8b
8 changed files with 153 additions and 79 deletions

View File

@ -168,13 +168,13 @@ export const ensRecords = {
failure: (error) => action(ENS_RECORDS[FAILURE], {error})
};
export const FIDDLE = createRequestTypes('FIDDLE');
export const fiddle = {
post: (codeToCompile, timestamp) => action(FIDDLE[REQUEST], {codeToCompile, timestamp}),
export const FIDDLE_COMPILE = createRequestTypes('FIDDLE_COMPILE');
export const fiddleCompile = {
post: (codeToCompile, timestamp) => action(FIDDLE_COMPILE[REQUEST], {codeToCompile, timestamp}),
success: (fiddle, payload) => {
return action(FIDDLE[SUCCESS], {fiddles: [{...fiddle, ...payload}]});
return action(FIDDLE_COMPILE[SUCCESS], {fiddleCompiles: [{...fiddle, ...payload}]});
},
failure: (error) => action(FIDDLE[FAILURE], {error})
failure: (error) => action(FIDDLE_COMPILE[FAILURE], {error})
};
export const FIDDLE_DEPLOY = createRequestTypes('FIDDLE_DEPLOY');
@ -193,6 +193,15 @@ export const fiddleFile = {
failure: (error) => action(FIDDLE_FILE[FAILURE], {error})
};
export const FIDDLE_PROFILE = createRequestTypes('FIDDLE_PROFILE');
export const fiddleProfile = {
post: (compiledCode, timestamp) => action(FIDDLE_PROFILE[REQUEST], {compiledCode, timestamp}),
success: (fiddleProfile, payload) => {
return action(FIDDLE_PROFILE[SUCCESS], {fiddleProfiles: [{...fiddleProfile, ...payload}]});
},
failure: (error) => action(FIDDLE_PROFILE[FAILURE], {error})
};
export const FILES = createRequestTypes('FILES');
export const files = {
request: () => action(FILES[REQUEST]),

View File

@ -136,7 +136,7 @@ export function websocketGasOracle() {
return new WebSocket(`${constants.wsEndpoint}/blockchain/gas/oracle`);
}
export function postFiddle(payload) {
export function postFiddleCompile(payload) {
return post('/contract/compile', payload);
}
@ -147,3 +147,8 @@ export function postFiddleDeploy(payload) {
export function fetchFiles() {
return get('/files');
}
export function postFiddleProfile(payload) {
return post('/contract/profiler/profile', {compiledContract: payload.compiledCode});
}

View File

@ -4,15 +4,15 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {
fiddle as fiddleAction,
fiddleDeploy as fiddleDeployAction,
fiddleFile as fiddleFileAction
fiddleCompile,
fiddleDeploy,
fiddleFile
} from '../actions';
import Fiddle from '../components/Fiddle';
import FiddleResults from '../components/FiddleResults';
import FiddleResultsSummary from '../components/FiddleResultsSummary';
import scrollToComponent from 'react-scroll-to-component';
import {getFiddle, getFiddleDeploy} from "../reducers/selectors";
import {getFiddleCompile, getFiddleDeploy, getFiddleProfile} from "../reducers/selectors";
import CompilerError from "../components/CompilerError";
import {List, Badge, Button} from 'tabler-react';
import {NavLink} from 'react-router-dom';
@ -65,7 +65,7 @@ class FiddleContainer extends Component {
if (this.compileTimeout) clearTimeout(this.compileTimeout);
this.compileTimeout = setTimeout(() => {
this.setState({loadingMessage: 'Compiling...'});
this.props.postFiddle(newValue, Date.now());
this.props.postFiddleCompile(newValue, Date.now());
}, immediate ? 0 : 1000);
}
@ -81,7 +81,7 @@ class FiddleContainer extends Component {
_onDeployClick(_e) {
this.setState({loadingMessage: 'Deploying...'});
this.props.postFiddleDeploy(this.props.fiddle.compilationResult);
this.props.postFiddleDeploy(this.props.compiledFiddle.compilationResult);
scrollToComponent(this.deployedCardRef || this.fiddleResultsRef.current); // deployedCardRef null on first Deploy click
}
@ -165,15 +165,23 @@ class FiddleContainer extends Component {
}
render() {
const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts, fatalError} = this.props;
const {
compiledFiddle,
profiledFiddle,
loading,
fiddleCompileError,
fiddleDeployError,
deployedFiddle,
fatalError
} = this.props;
const {loadingMessage, value, readOnly} = this.state;
let warnings = [];
let errors = [];
if (fiddle && fiddle.errors) {
warnings = this._renderErrors(fiddle.errors, "warning");
errors = this._renderErrors(fiddle.errors, "error");
if (compiledFiddle && compiledFiddle.errors) {
warnings = this._renderErrors(compiledFiddle.errors, "warning");
errors = this._renderErrors(compiledFiddle.errors, "error");
}
const hasResult = Boolean(fiddle);
const hasResult = Boolean(compiledFiddle);
return (
<React.Fragment>
<h1 className="page-title">Fiddle</h1>
@ -184,11 +192,11 @@ class FiddleContainer extends Component {
isLoading={loading}
loadingMessage={loadingMessage}
showFatalError={Boolean(fatalError)}
showFatalFiddle={Boolean(fiddleError)}
showFatalFiddle={Boolean(fiddleCompileError)}
showFatalFiddleDeploy={Boolean(fiddleDeployError)}
onDeployClick={(e) => this._onDeployClick(e)}
isVisible={Boolean(fatalError || hasResult || loading)}
showDeploy={hasResult && Boolean(fiddle.compilationResult)}
showDeploy={hasResult && Boolean(compiledFiddle.compilationResult)}
onWarningsClick={(e) => this._onErrorSummaryClick(e, this.errorsCardRef)}
onErrorsClick={(e) => this._onErrorSummaryClick(e, this.warningsCardRef)}
onFatalClick={(e) => this._onErrorSummaryClick(e, this.fatalCardRef)}
@ -211,17 +219,17 @@ class FiddleContainer extends Component {
errorsCard={this._renderErrorsCard(errors, "error")}
warningsCard={this._renderErrorsCard(warnings, "warning")}
fatalErrorCard={this._renderFatalCard("Fatal error", fatalError)}
fatalFiddleCard={this._renderFatalCard("Failed to compile", fiddleError)}
fatalFiddleCard={this._renderFatalCard("Failed to compile", fiddleCompileError)}
fatalFiddleDeployCard={this._renderFatalCard("Failed to deploy", fiddleDeployError)}
compiledContractsCard={fiddle && fiddle.compilationResult && this._renderSuccessCard("Contract(s) compiled!",
<ContractFunctions contractProfile={fiddle.compilationResult}
contractFunctions={deployedContracts}
compiledContractsCard={compiledFiddle && compiledFiddle.compilationResult && this._renderSuccessCard("Contract(s) compiled!",
<ContractFunctions contractProfile={profiledFiddle}
contractFunctions={deployedFiddle}
onlyConstructor
postContractFunction={this._onDeployClick}/>
)}
deployedContractsCard={deployedContracts && this._renderSuccessCard("Contract(s) deployed!",
deployedContractsCard={deployedFiddle && this._renderSuccessCard("Contract(s) deployed!",
<Button
to={`/embark/contracts/${deployedContracts}/overview`}
to={`/embark/contracts/${deployedFiddle}/overview`}
RootComponent={NavLink}
>Play with my contract(s)</Button>
)}
@ -232,27 +240,32 @@ class FiddleContainer extends Component {
}
}
function mapStateToProps(state) {
const fiddle = getFiddle(state);
const compiledFiddle = getFiddleCompile(state);
const deployedFiddle = getFiddleDeploy(state);
const profiledFiddle = getFiddleProfile(state);
return {
fiddle: fiddle.data,
deployedContracts: deployedFiddle.data,
fiddleError: fiddle.error,
compiledFiddle: compiledFiddle.data,
deployedFiddle: deployedFiddle.data,
profiledFiddle: profiledFiddle.data,
fiddleCompileError: compiledFiddle.error,
fiddleDeployError: deployedFiddle.error,
fiddleProfileError: profiledFiddle.error,
loading: state.loading,
lastFiddle: fiddle.data ? fiddle.data.codeToCompile : undefined,
lastFiddle: compiledFiddle.data ? compiledFiddle.data.codeToCompile : undefined,
fatalError: state.errorMessage
};
}
FiddleContainer.propTypes = {
fiddle: PropTypes.object,
fiddleError: PropTypes.string,
compiledFiddle: PropTypes.object,
fiddleCompileError: PropTypes.string,
fiddleDeployError: PropTypes.string,
fiddleProfileError: PropTypes.string,
loading: PropTypes.bool,
postFiddle: PropTypes.func,
postFiddleCompile: PropTypes.func,
postFiddleDeploy: PropTypes.func,
deployedContracts: PropTypes.string,
deployedFiddle: PropTypes.string,
profiledFiddle: PropTypes.object,
fetchLastFiddle: PropTypes.func,
lastFiddle: PropTypes.any,
fatalError: PropTypes.string
@ -261,8 +274,8 @@ FiddleContainer.propTypes = {
export default connect(
mapStateToProps,
{
postFiddle: fiddleAction.post,
postFiddleDeploy: fiddleDeployAction.post,
fetchLastFiddle: fiddleFileAction.request
postFiddleCompile: fiddleCompile.post,
postFiddleDeploy: fiddleDeploy.post,
fetchLastFiddle: fiddleFile.request
},
)(FiddleContainer);

View File

@ -19,8 +19,9 @@ const entitiesDefaultState = {
commands: [],
messages: [],
messageChannels: [],
fiddles: [],
fiddleCompiles: [],
fiddleDeploys: [],
fiddleProfiles: [],
versions: [],
plugins: [],
ensRecords: [],

View File

@ -119,12 +119,12 @@ export function getMessages(state) {
return messages;
}
export function getFiddle(state) {
const fiddleCompilation = last(state.entities.fiddles.sort((a, b) => { return (a.timestamp || 0) - (b.timestamp || 0); }));
export function getFiddleCompile(state) {
const fiddleCompilation = last(state.entities.fiddleCompiles.sort((a, b) => { return (a.timestamp || 0) - (b.timestamp || 0); }));
const isNoTempFileError = Boolean(fiddleCompilation && fiddleCompilation.codeToCompile && fiddleCompilation.codeToCompile.error && fiddleCompilation.codeToCompile.error.indexOf('ENOENT') > -1);
return {
data: fiddleCompilation,
error: isNoTempFileError ? undefined : state.errorEntities.fiddles
error: isNoTempFileError ? undefined : state.errorEntities.fiddleCompiles
};
}
@ -135,6 +135,15 @@ export function getFiddleDeploy(state) {
};
}
export function getFiddleProfile(state) {
const fiddleProfile = last(state.entities.fiddleProfiles.sort((a, b) => { return (a.timestamp || 0) - (b.timestamp || 0); }));
const isMissingContractError = Boolean(fiddleProfile && fiddleProfile.compiledContract && fiddleProfile.compiledContract.error && fiddleProfile.compiledContract.error.indexOf('MISSING_PARAM') > -1);
return {
data: fiddleProfile,
error: isMissingContractError ? undefined : state.errorEntities.fiddleProfiles
};
}
export function getEnsRecords(state) {
return state.entities.ensRecords;
}

View File

@ -4,9 +4,9 @@ import {eventChannel} from 'redux-saga';
import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects';
const {account, accounts, block, blocks, transaction, transactions, processes, commands, processLogs,
contracts, contract, contractProfile, messageSend, versions, plugins, messageListen, fiddle,
contracts, contract, contractProfile, messageSend, versions, plugins, messageListen, fiddleCompile,
fiddleDeploy, ensRecord, ensRecords, contractLogs, contractFile, contractFunction, contractDeploy,
fiddleFile, files, gasOracle} = actions;
fiddleFile, files, gasOracle, fiddleProfile} = actions;
function *doRequest(entity, apiFn, payload) {
const {response, error} = yield call(apiFn, payload);
@ -36,8 +36,9 @@ export const fetchContractFile = doRequest.bind(null, contractFile, api.fetchCon
export const fetchLastFiddle = doRequest.bind(null, fiddleFile, api.fetchLastFiddle);
export const postContractFunction = doRequest.bind(null, contractFunction, api.postContractFunction);
export const postContractDeploy = doRequest.bind(null, contractDeploy, api.postContractDeploy);
export const postFiddle = doRequest.bind(null, fiddle, api.postFiddle);
export const postFiddleCompile = doRequest.bind(null, fiddleCompile, api.postFiddleCompile);
export const postFiddleDeploy = doRequest.bind(null, fiddleDeploy, api.postFiddleDeploy);
export const postFiddleProfile = doRequest.bind(null, fiddleProfile, api.postFiddleProfile);
export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage);
export const fetchEnsRecord = doRequest.bind(null, ensRecord, api.fetchEnsRecord);
export const postEnsRecord = doRequest.bind(null, ensRecords, api.postEnsRecord);
@ -136,18 +137,26 @@ export function *watchListenToMessages() {
yield takeEvery(actions.MESSAGE_LISTEN[actions.REQUEST], listenToMessages);
}
export function *watchPostFiddle() {
yield takeEvery(actions.FIDDLE[actions.REQUEST], postFiddle);
export function *watchPostFiddleCompile() {
yield takeEvery(actions.FIDDLE_COMPILE[actions.REQUEST], postFiddleCompile);
}
export function *watchFetchLastFiddleSuccess() {
yield takeEvery(actions.FIDDLE_FILE[actions.SUCCESS], postFiddle);
yield takeEvery(actions.FIDDLE_FILE[actions.SUCCESS], postFiddleCompile);
}
export function *watchPostFiddleDeploy() {
yield takeEvery(actions.FIDDLE_DEPLOY[actions.REQUEST], postFiddleDeploy);
}
export function *watchPostFiddleProfile() {
yield takeEvery(actions.FIDDLE_PROFILE[actions.REQUEST], postFiddleProfile);
}
export function *watchPostFiddleCompileSuccess() {
yield takeEvery(actions.FIDDLE_COMPILE[actions.SUCCESS], postFiddleProfile);
}
export function *watchFetchFiles() {
yield takeEvery(actions.FILES[actions.REQUEST], fetchFiles);
}
@ -254,8 +263,9 @@ export default function *root() {
fork(watchSendMessage),
fork(watchFetchContract),
fork(watchFetchTransaction),
fork(watchPostFiddle),
fork(watchPostFiddleCompile),
fork(watchPostFiddleDeploy),
fork(watchPostFiddleProfile),
fork(watchFetchLastFiddle),
fork(watchFetchLastFiddleSuccess),
fork(watchFetchEnsRecord),

View File

@ -10,11 +10,10 @@ class GasEstimator {
this.fuzzer = new ContractFuzzer(embark);
}
estimateGas(contractName, cb) {
fuzzAndEstimateGas(contract, cb){
const self = this;
let gasMap = {};
self.events.request('contracts:contract', contractName, (contract) => {
let fuzzMap = self.fuzzer.generateFuzz(3, contract);
let fuzzMap = self.fuzzer.generateFuzz(3, contract);
let contractObj = new web3.eth.Contract(contract.abiDefinition, contract.deployedAddress);
async.each(contract.abiDefinition.filter((x) => x.type !== "event"),
(abiMethod, gasCb) => {
@ -66,6 +65,15 @@ class GasEstimator {
cb(null, gasMap, null);
}
);
}
estimateGas(contractNameOrObj, cb) {
if(typeof contractNameOrObj === 'object'){
return this.fuzzAndEstimateGas(contractNameOrObj, cb);
}
this.events.request('contracts:contract', contractNameOrObj, (contract) => {
this.fuzzAndEstimateGas(contract, cb);
});
}
}

View File

@ -13,36 +13,39 @@ class Profiler {
this.registerApi();
}
profileJSON(contractName, returnCb) {
profileContract(contract, returnCb){
const self = this;
let profileObj = {};
profileObj.name = contractName;
profileObj.name = contract.className;
profileObj.methods = [];
self.events.request('contracts:contract', contractName, (contract) => {
if (!contract || !contract.deployedAddress) {
return returnCb("-- couldn't profile " + contractName + " - it's not deployed or could be an interface");
}
self.gasEstimator.estimateGas(contractName, function(_err, gastimates, _name) {
contract.abiDefinition.forEach((abiMethod) => {
let methodName = abiMethod.name;
if (['constructor', 'fallback'].indexOf(abiMethod.type) >= 0) {
methodName = abiMethod.type;
}
if (!contract || !contract.deployedAddress) {
return returnCb("-- couldn't profile " + contract.className + " - it's not deployed or could be an interface");
}
self.gasEstimator.estimateGas(contract, function(_err, gastimates, _name) {
contract.abiDefinition.forEach((abiMethod) => {
let methodName = abiMethod.name;
if (['constructor', 'fallback'].indexOf(abiMethod.type) >= 0) {
methodName = abiMethod.type;
}
profileObj.methods.push({
name: methodName,
payable: abiMethod.payable,
mutability: abiMethod.stateMutability,
inputs: abiMethod.inputs || [],
outputs: abiMethod.outputs || [],
gasEstimates: gastimates && gastimates[methodName]
});
profileObj.methods.push({
name: methodName,
payable: abiMethod.payable,
mutability: abiMethod.stateMutability,
inputs: abiMethod.inputs || [],
outputs: abiMethod.outputs || [],
gasEstimates: gastimates && gastimates[methodName]
});
returnCb(null, profileObj);
});
returnCb(null, profileObj);
});
}
profileJSON(contractName, returnCb) {
this.events.request('contracts:contract', contractName, (contract) => {
this.profileContract(contract, returnCb);
});
}
@ -83,16 +86,32 @@ class Profiler {
}
registerApi() {
const self = this;
let plugin = this.plugins.createPlugin('profiler', {});
plugin.registerAPICall(
this.embark.registerAPICall(
'get',
'/embark-api/profiler/:contractName',
(req, res) => {
let contractName = req.params.contractName;
self.profileJSON(contractName, (err, table) => {
this.profileJSON(contractName, (err, table) => {
if (err) {
return res.send({error: err.message});
}
res.send(table);
});
}
);
this.embark.registerAPICall(
'post',
'/embark-api/profiler/profile',
(req, res) => {
let contract = req.body.compiledContract;
if(!contract){
res.send({error: 'Body parameter \'compiledContract\' is required (MISSING_PARAM).'});
}
this.profileJSON(contract, (err, table) => {
if (err) {
return res.send({error: err.message});
}