Merge pull request #36 from status-im/features/backend_tab/functions
Features/backend tab/functions
This commit is contained in:
commit
6c2489d5e0
|
@ -111,6 +111,20 @@ 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 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]),
|
||||
|
|
|
@ -68,6 +68,14 @@ export function fetchContract(payload) {
|
|||
return get(`/contract/${payload.contractName}`);
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
import PropTypes from "prop-types";
|
||||
import React, {Component} from 'react';
|
||||
import {
|
||||
Page,
|
||||
Grid,
|
||||
Form,
|
||||
Button,
|
||||
List,
|
||||
Card
|
||||
} from "tabler-react";
|
||||
|
||||
class ContractFunction extends Component {
|
||||
constructor(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;
|
||||
this.setState({ inputs: newInputs});
|
||||
}
|
||||
|
||||
handleCall(e) {
|
||||
e.preventDefault();
|
||||
this.props.postContractFunction(this.props.contractProfile.name, this.props.method.name, this.inputsAsArray());
|
||||
}
|
||||
|
||||
callDisabled() {
|
||||
return this.inputsAsArray().length !== this.props.method.inputs.length;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Grid.Row>
|
||||
<Grid.Col>
|
||||
<Card>
|
||||
<Card.Header>
|
||||
<Card.Title>{this.props.method.name}</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
{this.props.method.inputs.map(input => (
|
||||
<Form.Group key={input.name} label={input.name}>
|
||||
<Form.Input placeholder={input.type} onChange={(e) => this.handleChange(e, input.name)}/>
|
||||
</Form.Group>
|
||||
))}
|
||||
<Button color="primary" disabled={this.callDisabled()} onClick={(e) => this.handleCall(e)}>
|
||||
{this.buttonTitle()}
|
||||
</Button>
|
||||
</Card.Body>
|
||||
<Card.Footer>
|
||||
<List>
|
||||
{this.props.contractFunctions.map(contractFunction => (
|
||||
<List.Item key={contractFunction.result}>
|
||||
{contractFunction.inputs.length > 0 && <p>Inputs: {contractFunction.inputs.join(', ')}</p>}
|
||||
<strong>Result: {contractFunction.result}</strong>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ContractFunction.propTypes = {
|
||||
contractProfile: PropTypes.object,
|
||||
method: PropTypes.object,
|
||||
contractFunctions: PropTypes.arrayOf(PropTypes.object),
|
||||
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 (
|
||||
<Page.Content title={contractProfile.name + ' Functions'}>
|
||||
{contractProfile.methods
|
||||
.filter((method) => {
|
||||
return props.onlyConstructor ? method.name === 'constructor' : method.name !== 'constructor';
|
||||
})
|
||||
.map(method => <ContractFunction key={method.name}
|
||||
method={method}
|
||||
contractFunctions={filterContractFunctions(props.contractFunctions, contractProfile.name, method.name)}
|
||||
contractProfile={contractProfile}
|
||||
postContractFunction={props.postContractFunction} />)}
|
||||
</Page.Content>
|
||||
);
|
||||
};
|
||||
|
||||
ContractFunctions.propTypes = {
|
||||
onlyConstructor: PropTypes.bool,
|
||||
contractProfile: PropTypes.object,
|
||||
contractFunctions: PropTypes.arrayOf(PropTypes.object),
|
||||
postContractFunction: PropTypes.func
|
||||
};
|
||||
|
||||
ContractFunctions.defaultProps = {
|
||||
onlyConstructor: false
|
||||
};
|
||||
|
||||
export default ContractFunctions;
|
||||
|
|
@ -9,6 +9,8 @@ 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';
|
||||
|
||||
|
@ -72,6 +74,8 @@ const ContractLayout = ({match}) => (
|
|||
<Grid.Col md={9}>
|
||||
<Switch>
|
||||
<Route exact path="/embark/contracts/:contractName/overview" component={ContractContainer} />
|
||||
<Route exact path="/embark/contracts/:contractName/deployment" component={ContractDeploymentContainer} />
|
||||
<Route exact path="/embark/contracts/:contractName/functions" component={ContractFunctionsContainer} />
|
||||
<Route exact path="/embark/contracts/:contractName/source" component={ContractSourceContainer} />
|
||||
<Route exact path="/embark/contracts/:contractName/profiler" component={ContractProfileContainer} />
|
||||
<Route exact path="/embark/contracts/:contractName/logger" component={ContractLoggerContainer} />
|
||||
|
|
|
@ -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";
|
||||
|
@ -33,8 +32,5 @@ ContractContainer.propTypes = {
|
|||
};
|
||||
|
||||
export default withRouter(connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchContract: contractAction.request
|
||||
}
|
||||
mapStateToProps
|
||||
)(ContractContainer));
|
||||
|
|
|
@ -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 (
|
||||
<DataWrapper shouldRender={this.props.contractProfile !== undefined }
|
||||
{...this.props}
|
||||
render={({contractProfile, contractDeploys, postContractDeploy}) => (
|
||||
<ContractFunctions contractProfile={contractProfile}
|
||||
contractFunctions={contractDeploys}
|
||||
onlyConstructor
|
||||
postContractFunction={postContractDeploy}/>
|
||||
)} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
|
@ -0,0 +1,53 @@
|
|||
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, contractFunction as contractFunctionAction} from '../actions';
|
||||
import ContractFunctions from '../components/ContractFunctions';
|
||||
import DataWrapper from "../components/DataWrapper";
|
||||
import {getContractProfile, getContractFunctions} from "../reducers/selectors";
|
||||
|
||||
class ContractFunctionsContainer extends Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchContractProfile(this.props.match.params.contractName);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DataWrapper shouldRender={this.props.contractProfile !== undefined }
|
||||
{...this.props}
|
||||
render={({contractProfile, contractFunctions, postContractFunction}) => (
|
||||
<ContractFunctions contractProfile={contractProfile}
|
||||
contractFunctions={contractFunctions}
|
||||
postContractFunction={postContractFunction}/>
|
||||
)} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
ContractFunctionsContainer.propTypes = {
|
||||
match: PropTypes.object,
|
||||
contractProfile: PropTypes.object,
|
||||
contractFunctions: PropTypes.arrayOf(PropTypes.object),
|
||||
postContractFunction: PropTypes.func,
|
||||
fetchContractProfile: PropTypes.func,
|
||||
error: PropTypes.string
|
||||
};
|
||||
|
||||
export default withRouter(connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchContractProfile: contractProfileAction.request,
|
||||
postContractFunction: contractFunctionAction.post
|
||||
}
|
||||
)(ContractFunctionsContainer));
|
|
@ -27,7 +27,7 @@ function mapStateToProps(state) {
|
|||
|
||||
ContractsContainer.propTypes = {
|
||||
contracts: PropTypes.array,
|
||||
fetchContracts: PropTypes.func,
|
||||
fetchContracts: PropTypes.func
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -13,6 +13,8 @@ const entitiesDefaultState = {
|
|||
contracts: [],
|
||||
contractProfiles: [],
|
||||
contractFiles: [],
|
||||
contractFunctions: [],
|
||||
contractDeploys: [],
|
||||
contractLogs: [],
|
||||
commands: [],
|
||||
messages: [],
|
||||
|
|
|
@ -66,6 +66,14 @@ 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 getContractDeploys(state, contractName) {
|
||||
return state.entities.contractDeploys.filter((contractDeploy => contractDeploy.contractName === contractName));
|
||||
}
|
||||
|
||||
export function getVersions(state) {
|
||||
return state.entities.versions;
|
||||
}
|
||||
|
|
|
@ -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, contractDeploy} = actions;
|
||||
|
||||
function *doRequest(entity, apiFn, payload) {
|
||||
const {response, error} = yield call(apiFn, payload);
|
||||
|
@ -32,6 +32,8 @@ 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 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);
|
||||
|
@ -93,6 +95,14 @@ export function *watchFetchContractFile() {
|
|||
yield takeEvery(actions.CONTRACT_FILE[actions.REQUEST], fetchContractFile);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -200,6 +210,8 @@ export default function *root() {
|
|||
fork(watchFetchContracts),
|
||||
fork(watchFetchContractProfile),
|
||||
fork(watchFetchContractFile),
|
||||
fork(watchPostContractFunction),
|
||||
fork(watchPostContractDeploy),
|
||||
fork(watchListenToMessages),
|
||||
fork(watchSendMessage),
|
||||
fork(watchFetchContract),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/*global web3*/
|
||||
let toposort = require('toposort');
|
||||
let async = require('async');
|
||||
const cloneDeep = require('clone-deep');
|
||||
|
@ -83,6 +84,73 @@ class ContractsManager {
|
|||
}
|
||||
);
|
||||
|
||||
plugin.registerAPICall(
|
||||
'post',
|
||||
'/embark-api/contract/:contractName/function',
|
||||
(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));
|
||||
}
|
||||
}, (error, result) => {
|
||||
if (error) {
|
||||
return res.send({error: error.message});
|
||||
}
|
||||
const {account, contract} = result;
|
||||
const abi = contract.abiDefinition.find(definition => definition.name === req.body.method);
|
||||
const funcCall = (abi.constant === true || abi.stateMutability === 'view' || abi.stateMutability === 'pure') ? 'call' : 'send';
|
||||
|
||||
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});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
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));
|
||||
}
|
||||
}, (error, result) => {
|
||||
if (error) {
|
||||
return res.send({error: error.message});
|
||||
}
|
||||
const {account, contract} = result;
|
||||
|
||||
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});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
plugin.registerAPICall(
|
||||
'get',
|
||||
'/embark-api/contracts',
|
||||
|
@ -203,7 +271,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 +287,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 +335,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 +432,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 +472,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);
|
||||
|
|
Loading…
Reference in New Issue