From e485eda4250614226dd9ad7cdd0187e338119d00 Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 16 Aug 2018 10:15:31 +0100 Subject: [PATCH 1/6] Show functions --- embark-ui/src/components/ContractFunctions.js | 48 +++++++++++++++++++ embark-ui/src/components/ContractLayout.js | 2 + embark-ui/src/containers/ContractContainer.js | 7 +-- .../containers/ContractFunctionsContainer.js | 45 +++++++++++++++++ 4 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 embark-ui/src/components/ContractFunctions.js create mode 100644 embark-ui/src/containers/ContractFunctionsContainer.js diff --git a/embark-ui/src/components/ContractFunctions.js b/embark-ui/src/components/ContractFunctions.js new file mode 100644 index 00000000..762ae6f3 --- /dev/null +++ b/embark-ui/src/components/ContractFunctions.js @@ -0,0 +1,48 @@ +import PropTypes from "prop-types"; +import React from 'react'; +import { + Page, + Grid, + Form, + Button, + Card +} from "tabler-react"; + +const ContractFunction = ({method}) => ( + + + + + {method.name} + + {method.inputs.length > 0 && + + {method.inputs.map(input => ( + + + + ))} + + } + + + + + + +); + +const ContractFunctions = ({contractProfile}) => ( + + {contractProfile.methods + .filter(method => method.name !== 'constructor') + .map(method => )} + +); + +ContractFunctions.propTypes = { + contractProfile: PropTypes.object +}; + +export default ContractFunctions; + diff --git a/embark-ui/src/components/ContractLayout.js b/embark-ui/src/components/ContractLayout.js index 71b838a7..edd8882f 100644 --- a/embark-ui/src/components/ContractLayout.js +++ b/embark-ui/src/components/ContractLayout.js @@ -9,6 +9,7 @@ import { import ContractContainer from '../containers/ContractContainer'; import ContractLoggerContainer from '../containers/ContractLoggerContainer'; +import ContractFunctionsContainer from '../containers/ContractFunctionsContainer'; import ContractProfileContainer from '../containers/ContractProfileContainer'; import ContractSourceContainer from '../containers/ContractSourceContainer'; @@ -72,6 +73,7 @@ const ContractLayout = ({match}) => ( + diff --git a/embark-ui/src/containers/ContractContainer.js b/embark-ui/src/containers/ContractContainer.js index 925135e8..3389abf8 100644 --- a/embark-ui/src/containers/ContractContainer.js +++ b/embark-ui/src/containers/ContractContainer.js @@ -3,7 +3,6 @@ import {connect} from 'react-redux'; import PropTypes from 'prop-types'; import {withRouter} from 'react-router-dom'; -import {contract as contractAction} from '../actions'; import Contract from '../components/Contract'; import DataWrapper from "../components/DataWrapper"; import {getContract} from "../reducers/selectors"; @@ -27,14 +26,10 @@ function mapStateToProps(state, props) { } ContractContainer.propTypes = { - match: PropTypes.object, contract: PropTypes.object, error: PropTypes.string }; export default withRouter(connect( - mapStateToProps, - { - fetchContract: contractAction.request - } + mapStateToProps )(ContractContainer)); diff --git a/embark-ui/src/containers/ContractFunctionsContainer.js b/embark-ui/src/containers/ContractFunctionsContainer.js new file mode 100644 index 00000000..adc2859f --- /dev/null +++ b/embark-ui/src/containers/ContractFunctionsContainer.js @@ -0,0 +1,45 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; +import {withRouter} from 'react-router-dom'; + +import {contractProfile as contractProfileAction} from '../actions'; +import ContractFunctions from '../components/ContractFunctions'; +import DataWrapper from "../components/DataWrapper"; +import {getContractProfile} from "../reducers/selectors"; + +class ContractFunctionsContainer extends Component { + componentDidMount() { + this.props.fetchContractProfile(this.props.match.params.contractName); + } + + render() { + return ( + ( + + )} /> + ); + } +} + +function mapStateToProps(state, props) { + return { + contractProfile: getContractProfile(state, props.match.params.contractName), + error: state.errorMessage, + loading: state.loading + }; +} + +ContractFunctionsContainer.propTypes = { + match: PropTypes.object, + contractProfile: PropTypes.object, + fetchContractProfile: PropTypes.func, + error: PropTypes.string +}; + +export default withRouter(connect( + mapStateToProps, + { + fetchContractProfile: contractProfileAction.request + } +)(ContractFunctionsContainer)); From 01913851c9bdcede0576233e6044de477529e38a Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 16 Aug 2018 11:35:24 +0100 Subject: [PATCH 2/6] Call Function API --- embark-ui/src/components/ContractFunctions.js | 8 ++- embark-ui/src/containers/ContractContainer.js | 1 + .../src/containers/ContractsContainer.js | 2 +- lib/contracts/contracts.js | 51 ++++++++++++++++--- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/embark-ui/src/components/ContractFunctions.js b/embark-ui/src/components/ContractFunctions.js index 762ae6f3..27a700e8 100644 --- a/embark-ui/src/components/ContractFunctions.js +++ b/embark-ui/src/components/ContractFunctions.js @@ -18,7 +18,7 @@ const ContractFunction = ({method}) => ( {method.inputs.length > 0 && {method.inputs.map(input => ( - + ))} @@ -32,11 +32,15 @@ const ContractFunction = ({method}) => ( ); +ContractFunction.propTypes = { + method: PropTypes.object +}; + const ContractFunctions = ({contractProfile}) => ( {contractProfile.methods .filter(method => method.name !== 'constructor') - .map(method => )} + .map(method => )} ); diff --git a/embark-ui/src/containers/ContractContainer.js b/embark-ui/src/containers/ContractContainer.js index 3389abf8..5c8f516f 100644 --- a/embark-ui/src/containers/ContractContainer.js +++ b/embark-ui/src/containers/ContractContainer.js @@ -26,6 +26,7 @@ function mapStateToProps(state, props) { } ContractContainer.propTypes = { + match: PropTypes.object, contract: PropTypes.object, error: PropTypes.string }; diff --git a/embark-ui/src/containers/ContractsContainer.js b/embark-ui/src/containers/ContractsContainer.js index 94b0839f..1d769ede 100644 --- a/embark-ui/src/containers/ContractsContainer.js +++ b/embark-ui/src/containers/ContractsContainer.js @@ -27,7 +27,7 @@ function mapStateToProps(state) { ContractsContainer.propTypes = { contracts: PropTypes.array, - fetchContracts: PropTypes.func, + fetchContracts: PropTypes.func }; export default connect( diff --git a/lib/contracts/contracts.js b/lib/contracts/contracts.js index eccf5b64..49b4ec38 100644 --- a/lib/contracts/contracts.js +++ b/lib/contracts/contracts.js @@ -1,3 +1,4 @@ +/*global web3*/ let toposort = require('toposort'); let async = require('async'); const cloneDeep = require('clone-deep'); @@ -83,6 +84,36 @@ class ContractsManager { } ); + plugin.registerAPICall( + 'get', + '/embark-api/contract/:contractName/function', + (req, res) => { + async.parallel({ + contract: (callback) => { + self.events.request('contracts:contract', req.params.contractName, (contract) => callback(null, contract)); + }, + account: (callback) => { + self.events.request("blockchain:defaultAccount:get", (account) => callback(null, account)); + } + }, function (err, result) { + if (err) { + return res.send({error: err.message}); + } + const {account, contract} = result; + const contractObj = new web3.eth.Contract(contract.abiDefinition, contract.deployedAddress); + const abi = contract.abiDefinition.find(definition => definition.name === req.query.method); + const funcCall = (abi.constant === true || abi.stateMutability === 'view' || abi.stateMutability === 'pure') ? 'call' : 'send'; + contractObj.methods[req.query.method].apply(this, req.query.inputs)[funcCall]({from: account}, (err, result) => { + if (err) { + return res.send({error: err.message}); + } + + res.send({result: result}); + }); + }); + } + ); + plugin.registerAPICall( 'get', '/embark-api/contracts', @@ -203,7 +234,10 @@ class ContractsManager { } if (parentContract === undefined) { - self.logger.error(__("{{className}}: couldn't find instanceOf contract {{parentContractName}}", {className: className, parentContractName: parentContractName})); + self.logger.error(__("{{className}}: couldn't find instanceOf contract {{parentContractName}}", { + className: className, + parentContractName: parentContractName + })); let suggestion = utils.proposeAlternative(parentContractName, dictionary, [className, parentContractName]); if (suggestion) { self.logger.warn(__('did you mean "%s"?', suggestion)); @@ -216,7 +250,10 @@ class ContractsManager { } if (contract.code !== undefined) { - self.logger.error(__("{{className}} has code associated to it but it's configured as an instanceOf {{parentContractName}}", {className: className, parentContractName: parentContractName})); + self.logger.error(__("{{className}} has code associated to it but it's configured as an instanceOf {{parentContractName}}", { + className: className, + parentContractName: parentContractName + })); } contract.code = parentContract.code; @@ -261,8 +298,8 @@ class ContractsManager { // look in code for dependencies let libMatches = (contract.code.match(/:(.*?)(?=_)/g) || []); for (let match of libMatches) { - self.contractDependencies[className] = self.contractDependencies[className] || []; - self.contractDependencies[className].push(match.substr(1)); + self.contractDependencies[className] = self.contractDependencies[className] || []; + self.contractDependencies[className].push(match.substr(1)); } // look in arguments for dependencies @@ -358,7 +395,7 @@ class ContractsManager { self.compileError = true; self.events.emit("status", __("Compile/Build error")); self.logger.error(__("Error Compiling/Building contracts: ") + err); - }else{ + } else { self.compileError = false; } self.logger.trace("finished".underline); @@ -398,8 +435,8 @@ class ContractsManager { let orderedDependencies; try { - orderedDependencies = toposort(converted_dependencies.filter((x) => x[0] !== x[1])).reverse(); - } catch(e) { + orderedDependencies = toposort(converted_dependencies.filter((x) => x[0] !== x[1])).reverse(); + } catch (e) { this.logger.error((__("Error: ") + e.message).red); this.logger.error(__("there are two or more contracts that depend on each other in a cyclic manner").bold.red); this.logger.error(__("Embark couldn't determine which one to deploy first").red); From 376034da36d5af65a605ec4018c25d66d5ea87c5 Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 16 Aug 2018 12:17:13 +0100 Subject: [PATCH 3/6] Make call successfully --- embark-ui/src/actions/index.js | 7 ++ embark-ui/src/api/index.js | 4 + embark-ui/src/components/ContractFunctions.js | 96 ++++++++++++------- .../containers/ContractFunctionsContainer.js | 18 +++- embark-ui/src/reducers/index.js | 1 + embark-ui/src/reducers/selectors.js | 4 + embark-ui/src/sagas/index.js | 8 +- lib/contracts/contracts.js | 8 +- templates/demo/chains.json | 23 ++++- 9 files changed, 125 insertions(+), 44 deletions(-) diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index 45ebf9c1..174a0b35 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -111,6 +111,13 @@ export const contractFile = { failure: (error) => action(CONTRACT_FILE[FAILURE], {error}) }; +export const CONTRACT_FUNCTION = createRequestTypes('CONTRACT_FUNCTION'); +export const contractFunction = { + post: (contractName, method, inputs) => action(CONTRACT_FUNCTION[REQUEST], {contractName, method, inputs}), + success: (result, payload) => action(CONTRACT_FUNCTION[SUCCESS], {contractFunctions: [{...result, ...payload}]}), + failure: (error) => action(CONTRACT_FUNCTION[FAILURE], {error}) +}; + export const VERSIONS = createRequestTypes('VERSIONS'); export const versions = { request: () => action(VERSIONS[REQUEST]), diff --git a/embark-ui/src/api/index.js b/embark-ui/src/api/index.js index 2de94a6f..07925650 100644 --- a/embark-ui/src/api/index.js +++ b/embark-ui/src/api/index.js @@ -68,6 +68,10 @@ export function fetchContract(payload) { return get(`/contract/${payload.contractName}`); } +export function postContractFunction(payload) { + return post(`/contract/${payload.contractName}/function`, payload); +} + export function fetchVersions() { return get('/versions'); } diff --git a/embark-ui/src/components/ContractFunctions.js b/embark-ui/src/components/ContractFunctions.js index 27a700e8..8a561818 100644 --- a/embark-ui/src/components/ContractFunctions.js +++ b/embark-ui/src/components/ContractFunctions.js @@ -1,5 +1,5 @@ import PropTypes from "prop-types"; -import React from 'react'; +import React, {Component} from 'react'; import { Page, Grid, @@ -8,44 +8,74 @@ import { Card } from "tabler-react"; -const ContractFunction = ({method}) => ( - - - - - {method.name} - - {method.inputs.length > 0 && - - {method.inputs.map(input => ( - - - - ))} - - } - - - - - - -); +class ContractFunction extends Component { + constructor(props) { + super(props) + this.state = { inputs: {} }; + } + + handleChange(e, name) { + let newInputs = this.state.inputs; + newInputs[name] = e.target.value; + this.setState({ inputs: newInputs}); + } + + handleCall(e) { + e.preventDefault(); + const inputs = this.props.method.inputs.map(input => this.state.inputs[input.name]); + this.props.postContractFunction(this.props.contractProfile.name, this.props.method.name, inputs); + } + + render() { + return ( + + + + + {this.props.method.name} + + {this.props.method.inputs.length > 0 && + + {this.props.method.inputs.map(input => ( + + this.handleChange(e, input.name)}/> + + ))} + + } + + + + + + + ); + } +} + ContractFunction.propTypes = { - method: PropTypes.object + contractProfile: PropTypes.object, + method: PropTypes.object, + contractFunctions: PropTypes.arrayOf(PropTypes.object), + postContractFunction: PropTypes.func }; -const ContractFunctions = ({contractProfile}) => ( - - {contractProfile.methods - .filter(method => method.name !== 'constructor') - .map(method => )} - -); +const ContractFunctions = (props) => { + const {contractProfile} = props; + return ( + + {contractProfile.methods + .filter(method => method.name !== 'constructor') + .map(method => )} + + ) +}; ContractFunctions.propTypes = { - contractProfile: PropTypes.object + contractProfile: PropTypes.object, + contractFunctions: PropTypes.arrayOf(PropTypes.object), + postContractFunction: PropTypes.func }; export default ContractFunctions; diff --git a/embark-ui/src/containers/ContractFunctionsContainer.js b/embark-ui/src/containers/ContractFunctionsContainer.js index adc2859f..c9f951b6 100644 --- a/embark-ui/src/containers/ContractFunctionsContainer.js +++ b/embark-ui/src/containers/ContractFunctionsContainer.js @@ -3,10 +3,10 @@ import {connect} from 'react-redux'; import PropTypes from 'prop-types'; import {withRouter} from 'react-router-dom'; -import {contractProfile as contractProfileAction} from '../actions'; +import {contractProfile as contractProfileAction, contractFunction as contractFunctionAction} from '../actions'; import ContractFunctions from '../components/ContractFunctions'; import DataWrapper from "../components/DataWrapper"; -import {getContractProfile} from "../reducers/selectors"; +import {getContractProfile, getContractFunctions} from "../reducers/selectors"; class ContractFunctionsContainer extends Component { componentDidMount() { @@ -15,8 +15,12 @@ class ContractFunctionsContainer extends Component { render() { return ( - ( - + ( + )} /> ); } @@ -25,6 +29,7 @@ class ContractFunctionsContainer extends Component { function mapStateToProps(state, props) { return { contractProfile: getContractProfile(state, props.match.params.contractName), + contractFunctions: getContractFunctions(state, props.match.params.contractName), error: state.errorMessage, loading: state.loading }; @@ -33,6 +38,8 @@ function mapStateToProps(state, props) { ContractFunctionsContainer.propTypes = { match: PropTypes.object, contractProfile: PropTypes.object, + contractFunctions: PropTypes.arrayOf(PropTypes.object), + postContractFunction: PropTypes.func, fetchContractProfile: PropTypes.func, error: PropTypes.string }; @@ -40,6 +47,7 @@ ContractFunctionsContainer.propTypes = { export default withRouter(connect( mapStateToProps, { - fetchContractProfile: contractProfileAction.request + fetchContractProfile: contractProfileAction.request, + postContractFunction: contractFunctionAction.post } )(ContractFunctionsContainer)); diff --git a/embark-ui/src/reducers/index.js b/embark-ui/src/reducers/index.js index c6836e7b..fe4c7c11 100644 --- a/embark-ui/src/reducers/index.js +++ b/embark-ui/src/reducers/index.js @@ -13,6 +13,7 @@ const entitiesDefaultState = { contracts: [], contractProfiles: [], contractFiles: [], + contractFunctions: [], contractLogs: [], commands: [], messages: [], diff --git a/embark-ui/src/reducers/selectors.js b/embark-ui/src/reducers/selectors.js index d733e65f..4928df8a 100644 --- a/embark-ui/src/reducers/selectors.js +++ b/embark-ui/src/reducers/selectors.js @@ -66,6 +66,10 @@ export function getContractFile(state, filename) { return state.entities.contractFiles.find((contractFile => contractFile.filename === filename)); } +export function getContractFunctions(state, contractName) { + return state.entities.contractFunctions.filter((contractFunction => contractFunction.contractName === contractName)); +} + export function getVersions(state) { return state.entities.versions; } diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 2518f85e..45aa793c 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -5,7 +5,7 @@ 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, - ensRecord, ensRecords, contractLogs, contractFile} = actions; + ensRecord, ensRecords, contractLogs, contractFile, contractFunction} = actions; function *doRequest(entity, apiFn, payload) { const {response, error} = yield call(apiFn, payload); @@ -32,6 +32,7 @@ export const fetchContracts = doRequest.bind(null, contracts, api.fetchContracts export const fetchContract = doRequest.bind(null, contract, api.fetchContract); export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile); export const fetchContractFile = doRequest.bind(null, contractFile, api.fetchContractFile); +export const postContractFunction = doRequest.bind(null, contractFunction, api.postContractFunction); export const fetchFiddle = doRequest.bind(null, fiddle, api.fetchFiddle); export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage); export const fetchEnsRecord = doRequest.bind(null, ensRecord, api.fetchEnsRecord); @@ -93,6 +94,10 @@ export function *watchFetchContractFile() { yield takeEvery(actions.CONTRACT_FILE[actions.REQUEST], fetchContractFile); } +export function *watchPostContractFunction() { + yield takeEvery(actions.CONTRACT_FUNCTION[actions.REQUEST], postContractFunction); +} + export function *watchFetchVersions() { yield takeEvery(actions.VERSIONS[actions.REQUEST], fetchVersions); } @@ -200,6 +205,7 @@ export default function *root() { fork(watchFetchContracts), fork(watchFetchContractProfile), fork(watchFetchContractFile), + fork(watchPostContractFunction), fork(watchListenToMessages), fork(watchSendMessage), fork(watchFetchContract), diff --git a/lib/contracts/contracts.js b/lib/contracts/contracts.js index 49b4ec38..9ec557a5 100644 --- a/lib/contracts/contracts.js +++ b/lib/contracts/contracts.js @@ -85,12 +85,12 @@ class ContractsManager { ); plugin.registerAPICall( - 'get', + 'post', '/embark-api/contract/:contractName/function', (req, res) => { async.parallel({ contract: (callback) => { - self.events.request('contracts:contract', req.params.contractName, (contract) => callback(null, contract)); + self.events.request('contracts:contract', req.body.contractName, (contract) => callback(null, contract)); }, account: (callback) => { self.events.request("blockchain:defaultAccount:get", (account) => callback(null, account)); @@ -101,9 +101,9 @@ class ContractsManager { } const {account, contract} = result; const contractObj = new web3.eth.Contract(contract.abiDefinition, contract.deployedAddress); - const abi = contract.abiDefinition.find(definition => definition.name === req.query.method); + const abi = contract.abiDefinition.find(definition => definition.name === req.body.method); const funcCall = (abi.constant === true || abi.stateMutability === 'view' || abi.stateMutability === 'pure') ? 'call' : 'send'; - contractObj.methods[req.query.method].apply(this, req.query.inputs)[funcCall]({from: account}, (err, result) => { + contractObj.methods[req.body.method].apply(this, req.body.inputs)[funcCall]({from: account}, (err, result) => { if (err) { return res.send({error: err.message}); } diff --git a/templates/demo/chains.json b/templates/demo/chains.json index 0967ef42..f057cbbc 100644 --- a/templates/demo/chains.json +++ b/templates/demo/chains.json @@ -1 +1,22 @@ -{} +{ + "0xf957b38706f783689e96d5b17c2c24a2a1b2093b8e7c5f43e748878a73456984": { + "contracts": { + "0xa72e15b4fc637528e678fdcab66f178b753f609a6d2a66564f1676fde4d51ed3": { + "name": "SimpleStorage", + "address": "0x595dE951E7458Cf6e49606C91294A764098248AD" + }, + "0x05a320b7f23891fc8c8c40312e0ae5a104d4734b3c0692eb24f9e99fca0ada3f": { + "name": "ENSRegistry", + "address": "0x07dF106F12b516a36e29e23b2acF1e47b26Cb81B" + }, + "0xa971bc944c276c3a2f98bdad24d102d00615680517267a9e48cf7091ce512c88": { + "name": "Resolver", + "address": "0x8e43bD6a0357385D5a35980baCa7A2644459038b" + }, + "0x618f78549c37a154c4931bb2f10e4b92fbf8004fc1dfa6c11b3276257d3c84f0": { + "name": "FIFSRegistrar", + "address": "0xf2086ADff73E5B6412c1ca089250b77f9B28984B" + } + } + } +} From 81993cfc9e49b143259d7b77d1c515efb5db99a3 Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 16 Aug 2018 14:13:36 +0100 Subject: [PATCH 4/6] Show result --- embark-ui/src/components/ContractFunctions.js | 64 ++++++++++++++++--- .../containers/ContractFunctionsContainer.js | 3 +- lib/contracts/contracts.js | 22 ++++--- templates/demo/chains.json | 23 +------ 4 files changed, 70 insertions(+), 42 deletions(-) diff --git a/embark-ui/src/components/ContractFunctions.js b/embark-ui/src/components/ContractFunctions.js index 8a561818..e52289f3 100644 --- a/embark-ui/src/components/ContractFunctions.js +++ b/embark-ui/src/components/ContractFunctions.js @@ -5,15 +5,31 @@ import { Grid, Form, Button, + List, Card } from "tabler-react"; class ContractFunction extends Component { constructor(props) { - super(props) + super(props); this.state = { inputs: {} }; } + buttonTitle() { + const { method } =this.props; + if (method.name === 'constructor') { + return 'Deploy'; + } + + return (method.mutability === 'view' || method.mutability === 'pure') ? 'Call' : 'Send'; + } + + inputsAsArray(){ + return this.props.method.inputs + .map(input => this.state.inputs[input.name]) + .filter(value => value); + } + handleChange(e, name) { let newInputs = this.state.inputs; newInputs[name] = e.target.value; @@ -22,8 +38,11 @@ class ContractFunction extends Component { handleCall(e) { e.preventDefault(); - const inputs = this.props.method.inputs.map(input => this.state.inputs[input.name]); - this.props.postContractFunction(this.props.contractProfile.name, this.props.method.name, inputs); + this.props.postContractFunction(this.props.contractProfile.name, this.props.method.name, this.inputsAsArray()); + } + + callDisabled() { + return this.inputsAsArray().length !== this.props.method.inputs.length; } render() { @@ -34,17 +53,25 @@ class ContractFunction extends Component { {this.props.method.name} - {this.props.method.inputs.length > 0 && {this.props.method.inputs.map(input => ( this.handleChange(e, input.name)}/> ))} + - } - + + {this.props.contractFunctions.map(contractFunction => ( + + {contractFunction.inputs.length > 0 &&

Inputs: {contractFunction.inputs.join(', ')}

} + Result: {contractFunction.result} +
+ ))} +
@@ -53,7 +80,6 @@ class ContractFunction extends Component { } } - ContractFunction.propTypes = { contractProfile: PropTypes.object, method: PropTypes.object, @@ -61,22 +87,40 @@ ContractFunction.propTypes = { postContractFunction: PropTypes.func }; +const filterContractFunctions = (contractFunctions, contractName, method) => { + return contractFunctions.filter((contractFunction) => ( + contractFunction.contractName === contractName && contractFunction.method === method + )); +}; + const ContractFunctions = (props) => { const {contractProfile} = props; + return ( {contractProfile.methods - .filter(method => method.name !== 'constructor') - .map(method => )} + .filter((method) => { + return props.onlyConstructor ? method.name === 'constructor' : method.name !== 'constructor'; + }) + .map(method => )} - ) + ); }; ContractFunctions.propTypes = { + onlyConstructor: PropTypes.bool, contractProfile: PropTypes.object, contractFunctions: PropTypes.arrayOf(PropTypes.object), postContractFunction: PropTypes.func }; +ContractFunctions.defaultProps = { + onlyConstructor: false +}; + export default ContractFunctions; diff --git a/embark-ui/src/containers/ContractFunctionsContainer.js b/embark-ui/src/containers/ContractFunctionsContainer.js index c9f951b6..a5296193 100644 --- a/embark-ui/src/containers/ContractFunctionsContainer.js +++ b/embark-ui/src/containers/ContractFunctionsContainer.js @@ -20,7 +20,8 @@ class ContractFunctionsContainer extends Component { render={({contractProfile, contractFunctions, postContractFunction}) => ( + constructor={true} + postContractFunction={postContractFunction}/> )} /> ); } diff --git a/lib/contracts/contracts.js b/lib/contracts/contracts.js index 9ec557a5..a0a74ca0 100644 --- a/lib/contracts/contracts.js +++ b/lib/contracts/contracts.js @@ -95,21 +95,25 @@ class ContractsManager { account: (callback) => { self.events.request("blockchain:defaultAccount:get", (account) => callback(null, account)); } - }, function (err, result) { - if (err) { - return res.send({error: err.message}); + }, function (error, result) { + if (error) { + return res.send({error: error.message}); } const {account, contract} = result; const contractObj = new web3.eth.Contract(contract.abiDefinition, contract.deployedAddress); const abi = contract.abiDefinition.find(definition => definition.name === req.body.method); const funcCall = (abi.constant === true || abi.stateMutability === 'view' || abi.stateMutability === 'pure') ? 'call' : 'send'; - contractObj.methods[req.body.method].apply(this, req.body.inputs)[funcCall]({from: account}, (err, result) => { - if (err) { - return res.send({error: err.message}); - } + try { + contractObj.methods[req.body.method].apply(this, req.body.inputs)[funcCall]({from: account}, (error, result) => { + if (error) { + return res.send({result: error.message}); + } - res.send({result: result}); - }); + return res.send({result}); + }); + } catch (e) { + return res.send({result: e.message}); + } }); } ); diff --git a/templates/demo/chains.json b/templates/demo/chains.json index f057cbbc..0967ef42 100644 --- a/templates/demo/chains.json +++ b/templates/demo/chains.json @@ -1,22 +1 @@ -{ - "0xf957b38706f783689e96d5b17c2c24a2a1b2093b8e7c5f43e748878a73456984": { - "contracts": { - "0xa72e15b4fc637528e678fdcab66f178b753f609a6d2a66564f1676fde4d51ed3": { - "name": "SimpleStorage", - "address": "0x595dE951E7458Cf6e49606C91294A764098248AD" - }, - "0x05a320b7f23891fc8c8c40312e0ae5a104d4734b3c0692eb24f9e99fca0ada3f": { - "name": "ENSRegistry", - "address": "0x07dF106F12b516a36e29e23b2acF1e47b26Cb81B" - }, - "0xa971bc944c276c3a2f98bdad24d102d00615680517267a9e48cf7091ce512c88": { - "name": "Resolver", - "address": "0x8e43bD6a0357385D5a35980baCa7A2644459038b" - }, - "0x618f78549c37a154c4931bb2f10e4b92fbf8004fc1dfa6c11b3276257d3c84f0": { - "name": "FIFSRegistrar", - "address": "0xf2086ADff73E5B6412c1ca089250b77f9B28984B" - } - } - } -} +{} From eaefd39b96917f70c675947a36a6714b7c7b8dc0 Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 16 Aug 2018 16:18:07 +0100 Subject: [PATCH 5/6] Deploy contract --- embark-ui/src/actions/index.js | 7 +++ embark-ui/src/api/index.js | 4 ++ embark-ui/src/components/ContractLayout.js | 2 + .../containers/ContractDeploymentContainer.js | 54 +++++++++++++++++++ .../containers/ContractFunctionsContainer.js | 1 - embark-ui/src/reducers/index.js | 1 + embark-ui/src/reducers/selectors.js | 4 ++ embark-ui/src/sagas/index.js | 8 ++- lib/contracts/contracts.js | 34 ++++++++++-- 9 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 embark-ui/src/containers/ContractDeploymentContainer.js diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index 174a0b35..c839e103 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -118,6 +118,13 @@ export const contractFunction = { failure: (error) => action(CONTRACT_FUNCTION[FAILURE], {error}) }; +export const CONTRACT_DEPLOY = createRequestTypes('CONTRACT_DEPLOY'); +export const contractDeploy = { + post: (contractName, method, inputs) => action(CONTRACT_DEPLOY[REQUEST], {contractName, method, inputs}), + success: (result, payload) => action(CONTRACT_DEPLOY[SUCCESS], {contractDeploys: [{...result, ...payload}]}), + failure: (error) => action(CONTRACT_DEPLOY[FAILURE], {error}) +}; + export const VERSIONS = createRequestTypes('VERSIONS'); export const versions = { request: () => action(VERSIONS[REQUEST]), diff --git a/embark-ui/src/api/index.js b/embark-ui/src/api/index.js index 07925650..3718d061 100644 --- a/embark-ui/src/api/index.js +++ b/embark-ui/src/api/index.js @@ -72,6 +72,10 @@ export function postContractFunction(payload) { return post(`/contract/${payload.contractName}/function`, payload); } +export function postContractDeploy(payload) { + return post(`/contract/${payload.contractName}/deploy`, payload); +} + export function fetchVersions() { return get('/versions'); } diff --git a/embark-ui/src/components/ContractLayout.js b/embark-ui/src/components/ContractLayout.js index edd8882f..b9950471 100644 --- a/embark-ui/src/components/ContractLayout.js +++ b/embark-ui/src/components/ContractLayout.js @@ -10,6 +10,7 @@ import { import ContractContainer from '../containers/ContractContainer'; import ContractLoggerContainer from '../containers/ContractLoggerContainer'; import ContractFunctionsContainer from '../containers/ContractFunctionsContainer'; +import ContractDeploymentContainer from '../containers/ContractDeploymentContainer'; import ContractProfileContainer from '../containers/ContractProfileContainer'; import ContractSourceContainer from '../containers/ContractSourceContainer'; @@ -73,6 +74,7 @@ const ContractLayout = ({match}) => ( + diff --git a/embark-ui/src/containers/ContractDeploymentContainer.js b/embark-ui/src/containers/ContractDeploymentContainer.js new file mode 100644 index 00000000..b1bf2741 --- /dev/null +++ b/embark-ui/src/containers/ContractDeploymentContainer.js @@ -0,0 +1,54 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; +import {withRouter} from 'react-router-dom'; + +import {contractProfile as contractProfileAction, contractDeploy as contractDeployAction} from '../actions'; +import ContractFunctions from '../components/ContractFunctions'; +import DataWrapper from "../components/DataWrapper"; +import {getContractProfile, getContractDeploys} from "../reducers/selectors"; + +class ContractDeploymentContainer extends Component { + componentDidMount() { + this.props.fetchContractProfile(this.props.match.params.contractName); + } + + render() { + return ( + ( + + )} /> + ); + } +} + +function mapStateToProps(state, props) { + return { + contractProfile: getContractProfile(state, props.match.params.contractName), + contractDeploys: getContractDeploys(state, props.match.params.contractName), + error: state.errorMessage, + loading: state.loading + }; +} + +ContractDeploymentContainer.propTypes = { + match: PropTypes.object, + contractProfile: PropTypes.object, + contractFunctions: PropTypes.arrayOf(PropTypes.object), + postContractDeploy: PropTypes.func, + fetchContractProfile: PropTypes.func, + error: PropTypes.string +}; + +export default withRouter(connect( + mapStateToProps, + { + fetchContractProfile: contractProfileAction.request, + postContractDeploy: contractDeployAction.post + } +)(ContractDeploymentContainer)); diff --git a/embark-ui/src/containers/ContractFunctionsContainer.js b/embark-ui/src/containers/ContractFunctionsContainer.js index a5296193..cf273c99 100644 --- a/embark-ui/src/containers/ContractFunctionsContainer.js +++ b/embark-ui/src/containers/ContractFunctionsContainer.js @@ -20,7 +20,6 @@ class ContractFunctionsContainer extends Component { render={({contractProfile, contractFunctions, postContractFunction}) => ( )} /> ); diff --git a/embark-ui/src/reducers/index.js b/embark-ui/src/reducers/index.js index fe4c7c11..c3d7f283 100644 --- a/embark-ui/src/reducers/index.js +++ b/embark-ui/src/reducers/index.js @@ -14,6 +14,7 @@ const entitiesDefaultState = { contractProfiles: [], contractFiles: [], contractFunctions: [], + contractDeploys: [], contractLogs: [], commands: [], messages: [], diff --git a/embark-ui/src/reducers/selectors.js b/embark-ui/src/reducers/selectors.js index 4928df8a..b7ef021a 100644 --- a/embark-ui/src/reducers/selectors.js +++ b/embark-ui/src/reducers/selectors.js @@ -70,6 +70,10 @@ export function getContractFunctions(state, contractName) { return state.entities.contractFunctions.filter((contractFunction => contractFunction.contractName === contractName)); } +export function getContractDeploys(state, contractName) { + return state.entities.contractDeploys.filter((contractDeploy => contractDeploy.contractName === contractName)); +} + export function getVersions(state) { return state.entities.versions; } diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 45aa793c..668767a1 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -5,7 +5,7 @@ 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, - ensRecord, ensRecords, contractLogs, contractFile, contractFunction} = actions; + ensRecord, ensRecords, contractLogs, contractFile, contractFunction, contractDeploy} = actions; function *doRequest(entity, apiFn, payload) { const {response, error} = yield call(apiFn, payload); @@ -33,6 +33,7 @@ export const fetchContract = doRequest.bind(null, contract, api.fetchContract); export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile); export const fetchContractFile = doRequest.bind(null, contractFile, api.fetchContractFile); export const postContractFunction = doRequest.bind(null, contractFunction, api.postContractFunction); +export const postContractDeploy = doRequest.bind(null, contractDeploy, api.postContractDeploy); export const fetchFiddle = doRequest.bind(null, fiddle, api.fetchFiddle); export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage); export const fetchEnsRecord = doRequest.bind(null, ensRecord, api.fetchEnsRecord); @@ -98,6 +99,10 @@ export function *watchPostContractFunction() { yield takeEvery(actions.CONTRACT_FUNCTION[actions.REQUEST], postContractFunction); } +export function *watchPostContractDeploy() { + yield takeEvery(actions.CONTRACT_DEPLOY[actions.REQUEST], postContractDeploy); +} + export function *watchFetchVersions() { yield takeEvery(actions.VERSIONS[actions.REQUEST], fetchVersions); } @@ -206,6 +211,7 @@ export default function *root() { fork(watchFetchContractProfile), fork(watchFetchContractFile), fork(watchPostContractFunction), + fork(watchPostContractDeploy), fork(watchListenToMessages), fork(watchSendMessage), fork(watchFetchContract), diff --git a/lib/contracts/contracts.js b/lib/contracts/contracts.js index a0a74ca0..7e7840c8 100644 --- a/lib/contracts/contracts.js +++ b/lib/contracts/contracts.js @@ -95,7 +95,7 @@ class ContractsManager { account: (callback) => { self.events.request("blockchain:defaultAccount:get", (account) => callback(null, account)); } - }, function (error, result) { + }, (error, result) => { if (error) { return res.send({error: error.message}); } @@ -109,10 +109,38 @@ class ContractsManager { return res.send({result: error.message}); } - return res.send({result}); + res.send({result}); }); } catch (e) { - return res.send({result: e.message}); + res.send({result: e.message}); + } + }); + } + ); + + plugin.registerAPICall( + 'post', + '/embark-api/contract/:contractName/deploy', + (req, res) => { + async.parallel({ + contract: (callback) => { + self.events.request('contracts:contract', req.body.contractName, (contract) => callback(null, contract)); + }, + account: (callback) => { + self.events.request("blockchain:defaultAccount:get", (account) => callback(null, account)); + } + }, async (error, result) => { + if (error) { + return res.send({error: error.message}); + } + const {account, contract} = result; + const contractObj = new web3.eth.Contract(contract.abiDefinition); + try { + let gas = await contractObj.deploy({data: `0x${contract.code}`, arguments: req.body.inputs}).estimateGas(); + let newContract = await contractObj.deploy({data: `0x${contract.code}`, arguments: req.body.inputs}).send({from: account, gas}); + res.send({result: newContract._address}); + } catch (e) { + res.send({result: e.message}); } }); } From 918391a2a46dfb69fb2f2d6e6214123c444a21b0 Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Tue, 21 Aug 2018 11:38:05 +0100 Subject: [PATCH 6/6] PR feedback: Use request --- lib/contracts/contracts.js | 45 +++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/contracts/contracts.js b/lib/contracts/contracts.js index 7e7840c8..5f599db5 100644 --- a/lib/contracts/contracts.js +++ b/lib/contracts/contracts.js @@ -100,20 +100,22 @@ class ContractsManager { return res.send({error: error.message}); } const {account, contract} = result; - const contractObj = new web3.eth.Contract(contract.abiDefinition, contract.deployedAddress); const abi = contract.abiDefinition.find(definition => definition.name === req.body.method); const funcCall = (abi.constant === true || abi.stateMutability === 'view' || abi.stateMutability === 'pure') ? 'call' : 'send'; - try { - contractObj.methods[req.body.method].apply(this, req.body.inputs)[funcCall]({from: account}, (error, result) => { - if (error) { - return res.send({result: error.message}); - } - res.send({result}); - }); - } catch (e) { - res.send({result: e.message}); - } + self.events.request("blockchain:contract:create", {abi: contract.abiDefinition, address: contract.deployedAddress}, (contractObj) => { + try { + contractObj.methods[req.body.method].apply(this, req.body.inputs)[funcCall]({from: account}, (error, result) => { + if (error) { + return res.send({result: error.message}); + } + + res.send({result}); + }); + } catch (e) { + res.send({result: e.message}); + } + }); }); } ); @@ -129,19 +131,22 @@ class ContractsManager { account: (callback) => { self.events.request("blockchain:defaultAccount:get", (account) => callback(null, account)); } - }, async (error, result) => { + }, (error, result) => { if (error) { return res.send({error: error.message}); } const {account, contract} = result; - const contractObj = new web3.eth.Contract(contract.abiDefinition); - try { - let gas = await contractObj.deploy({data: `0x${contract.code}`, arguments: req.body.inputs}).estimateGas(); - let newContract = await contractObj.deploy({data: `0x${contract.code}`, arguments: req.body.inputs}).send({from: account, gas}); - res.send({result: newContract._address}); - } catch (e) { - res.send({result: e.message}); - } + + self.events.request("blockchain:contract:create", {abi: contract.abiDefinition}, async (contractObj) => { + try { + const params = {data: `0x${contract.code}`, arguments: req.body.inputs}; + let gas = await contractObj.deploy(params).estimateGas(); + let newContract = await contractObj.deploy(params).send({from: account, gas}); + res.send({result: newContract._address}); + } catch (e) { + res.send({result: e.message}); + } + }); }); } );