refactor(embark-ui): improve cockpit's contracts explorer

Rename contract "Transactions" tab to "Log". Display and allow filtering of all
contract methods. Disable debug button for pure/view functions and the
constructor.

Revise the filtering logic so that filters are combined together. Make the
status filter a drop down menu like the others.

Revise styling for consistent row height, alignment of text, and button sizes;
use a monospaced font in some cases to achieve the effect.

Handle enter/return correctly in forms within a contract's Interact tab.

Remove event rows from a contract's Interact tab.

Track pure/view calls in the blockchain proxy so they can be logged server-side
by console listener and reported in Cockpit within a contract's Log tab.

Eliminate double logging in the contracts manager. Ensure contracts deployed on
a fresh `embark run` have an `address` / `deployedAddress` property.
This commit is contained in:
Michael Bradley, Jr 2019-03-05 16:45:18 -06:00 committed by Michael Bradley
parent b15467f64a
commit 9afdbd9848
18 changed files with 422 additions and 257 deletions

View File

@ -14,4 +14,4 @@ contract SimpleStorage {
function get() public view returns (uint retVal) {
return storedData;
}
}
}

View File

@ -31,4 +31,3 @@ ContractDetail.propTypes = {
};
export default ContractDetail;

View File

@ -5,7 +5,7 @@ import { TabContent, TabPane, Nav, NavItem, NavLink, Card, CardBody, CardTitle }
import classnames from 'classnames';
import ContractDetail from '../components/ContractDetail';
import ContractTransactionsContainer from '../containers/ContractTransactionsContainer';
import ContractLogContainer from '../containers/ContractLogContainer';
import ContractOverviewContainer from '../containers/ContractOverviewContainer';
class ContractLayout extends React.Component {
@ -53,7 +53,7 @@ class ContractLayout extends React.Component {
className={classnames({ active: this.state.activeTab === '3' })}
onClick={() => { this.toggle('3'); }}
>
<FontAwesomeIcon className="mr-2" name="list-alt" />Transactions
<FontAwesomeIcon className="mr-2" name="list-alt" />Log
</NavLink>
</NavItem>
</Nav>
@ -65,13 +65,13 @@ class ContractLayout extends React.Component {
<ContractDetail contract={this.props.contract} />
</TabPane>
<TabPane tabId="3">
<ContractTransactionsContainer contract={this.props.contract} />
<ContractLogContainer contract={this.props.contract} />
</TabPane>
</TabContent>
</CardBody>
</Card>
</React.Fragment>
)
);
}
}

View File

@ -0,0 +1,173 @@
import PropTypes from "prop-types";
import React from 'react';
import {Row, Col, Table, FormGroup, Label, Input, Form} from 'reactstrap';
import DebugButton from './DebugButton';
import "./ContractLog.scss";
const TX_STATES = {Any: '', Success: '0x1', Fail: '0x0'};
const EVENT = 'event';
const FUNCTION = 'function';
class ContractLog extends React.Component {
constructor(props) {
super(props);
this.state = {method: '', event: '', status: TX_STATES['Any']};
}
getMethods() {
if (!this.props.contract.abiDefinition) {
return [];
}
return this.props.contract.abiDefinition.filter(method => method.type === FUNCTION);
}
getEvents() {
if (!this.props.contract.abiDefinition) {
return [];
}
return this.props.contract.abiDefinition.filter(method => method.type === EVENT);
}
updateState(key, value) {
this.setState({[key]: value});
}
dataToDisplay() {
return this.props.contractLogs.map(contractLog => {
const events = this.props.contractEvents
.filter(contractEvent => contractEvent.transactionHash === contractLog.transactionHash)
.map(contractEvent => contractEvent.event);
contractLog.events = events;
return contractLog;
}).filter(contractLog => {
if (this.state.status && contractLog.status !== this.state.status) {
return false;
}
if (this.state.method && this.state.event) {
return this.state.method === contractLog.functionName &&
contractLog.events.includes(this.state.event);
}
if (this.state.method) {
return this.state.method === contractLog.functionName;
}
if (this.state.event) {
return contractLog.events.includes(this.state.event);
}
return true;
});
}
render() {
return (
<React.Fragment>
<Form>
<Row>
<Col md={4}>
<FormGroup>
<Label htmlFor="functions">Functions</Label>
<Input type="select"
name="functions"
id="functions"
onChange={(event) => this.updateState('method', event.target.value)}
value={this.state.method}>
<option value="">(all)</option>
{this.getMethods().map((method, index) => (
<option value={method.name} key={index}>
{method.name}
</option>))}
<option value="constructor">constructor</option>
</Input>
</FormGroup>
</Col>
<Col md={4}>
<FormGroup>
<Label htmlFor="events">Events</Label>
<Input type="select"
name="events"
id="events"
onChange={(event) => this.updateState('event', event.target.value)}
value={this.state.event}>
<option value="">(all)</option>
{this.getEvents().map((event, index) => (
<option value={event.name} key={index}>
{event.name}
</option>))}
</Input>
</FormGroup>
</Col>
<Col md={4}>
<FormGroup>
<Label htmlFor="events">Status</Label>
<Input type="select"
name="status"
id="status"
onChange={(event) => this.updateState('status', event.target.value)}
value={this.state.status}>
{Object.keys(TX_STATES).map((key, index) => (
<option value={TX_STATES[key]} key={index}>
{key === 'Any' ? '(any)' : key}
</option>
))}
</Input>
</FormGroup>
</Col>
</Row>
</Form>
<Row>
<Col className="overflow-auto">
<Table className="contract-log">
<thead>
<tr>
<th/>
<th>Invocation</th>
<th>Events</th>
<th>Gas</th>
<th>Block</th>
<th>Status</th>
<th>Transaction</th>
</tr>
</thead>
<tbody>
{
this.dataToDisplay().map((log, index) => {
return (
<tr key={'log-' + index}>
<td><DebugButton contracts={[this.props.contract]}
transaction={
{...log,
hash: log.transactionHash,
isCall: log.kind === 'call',
isConstructor: log.functionName === 'constructor'}}/></td>
<td>{`${log.name}.${log.functionName}(${log.paramString})`}</td>
<td>{log.events.join(', ')}</td>
<td>{log.gasUsed}</td>
<td>{log.blockNumber}</td>
<td>{log.status}</td>
<td>{log.transactionHash}</td>
</tr>
);
})
}
</tbody>
</Table>
</Col>
</Row>
</React.Fragment>
);
}
}
ContractLog.propTypes = {
contractLogs: PropTypes.array,
contractEvents: PropTypes.array,
contract: PropTypes.object.isRequired
};
export default ContractLog;

View File

@ -0,0 +1,6 @@
table.contract-log {
td, th {
height: 4.2em;
vertical-align: middle !important;
}
}

View File

@ -1,16 +0,0 @@
.contract-function-container .gas-price-form #gasPrice {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.contract-function-container .gas-price-form button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border: 0;
box-shadow: none !important;
}
.contract-function-container .card-header.closed {
border-bottom: none;
border-radius: 0.25rem;
}

View File

@ -23,7 +23,7 @@ import {formatContractForDisplay} from '../utils/presentation';
import FontAwesome from 'react-fontawesome';
import classnames from 'classnames';
import "./ContractOverview.css";
import "./ContractOverview.scss";
class ContractFunction extends Component {
constructor(props) {
@ -45,7 +45,7 @@ class ContractFunction extends Component {
return 'Deploy';
}
return ContractFunction.isPureCall(method) ? 'Call' : 'Send';
return ContractFunction.isPureCall(method) ? 'call' : 'send';
}
inputsAsArray() {
@ -70,7 +70,22 @@ class ContractFunction extends Component {
handleCall(e) {
e.preventDefault();
this.props.postContractFunction(this.props.contractName, this.props.method.name, this.inputsAsArray(), this.state.inputs.gasPrice * 1000000000);
this.props.postContractFunction(
this.props.contractName,
this.props.method.name,
this.inputsAsArray(),
this.state.inputs.gasPrice * 1000000000
);
}
handleKeyPress(e) {
if (e.key === 'Enter') {
if (this.callDisabled()) {
e.preventDefault();
} else {
this.handleCall(e);
}
}
}
callDisabled() {
@ -95,38 +110,62 @@ class ContractFunction extends Component {
});
}
makeBadge(color, codeColor, text) {
const badgeDark = this.state.functionCollapse;
const _codeColor = badgeDark ? 'white' : codeColor;
return (
<Badge color={color} className={classnames({
'badge-dark': badgeDark,
'contract-function-badge': true,
'float-right': true,
'p-2': true
})}>
<code className={classnames({
[`code-${_codeColor}`]: true,
})}>
{text}
</code>
</Badge>
);
}
render() {
if (ContractFunction.isEvent(this.props.method)) {
return <React.Fragment/>;
}
return (
<Card className="contract-function-container">
<CardHeader
className={classnames({
collapsable: !ContractFunction.isEvent(this.props.method),
'border-bottom-0': !this.state.functionCollapse,
'rounded': !this.state.functionCollapse
})}
onClick={() => this.toggleFunction()}>
<CardTitle>
{ContractFunction.isPureCall(this.props.method) && Boolean(this.props.method.inputs.length) &&
<Badge color="warning" className="float-right p-2">call</Badge>
}
{ContractFunction.isPureCall(this.props.method) && !this.props.method.inputs.length &&
<Button color="warning" size="sm" className="float-right" onClick={(e) => this.handleCall(e)}>call</Button>
}
{ContractFunction.isEvent(this.props.method) &&
<Badge color="info" className="float-right p-2">event</Badge>
}
{this.props.method.name}({this.props.method.inputs.map(input => input.name).join(', ')})
<span className="contract-function-signature">
{`${this.props.method.name}` +
`(${this.props.method.inputs.map(i => i.name).join(', ')})`}
</span>
<div>
{(ContractFunction.isPureCall(this.props.method) &&
this.makeBadge('success', 'white', 'call')) ||
this.makeBadge('warning', 'black', 'send')}
</div>
</CardTitle>
</CardHeader>
{!ContractFunction.isEvent(this.props.method) &&
<Collapse isOpen={this.state.functionCollapse} className="relative">
<CardBody>
<Form method="post" inline>
<Form inline>
{this.props.method.inputs.map(input => (
<FormGroup key={input.name}>
<Label for={input.name} className="mr-2 font-weight-bold">{input.name}</Label>
<Input name={input.name} id={input.name} placeholder={input.type}
onChange={(e) => this.handleChange(e, input.name)}/>
<Label for={input.name} className="mr-2 font-weight-bold contract-function-input">
{input.name}
</Label>
<Input name={input.name}
id={input.name}
placeholder={input.type}
onChange={(e) => this.handleChange(e, input.name)}
onKeyPress={(e) => this.handleKeyPress(e)}/>
</FormGroup>
))}
</Form>
@ -140,12 +179,15 @@ class ContractFunction extends Component {
</Row>
<Row>
<Collapse isOpen={this.state.optionsCollapse} className="pl-3">
<Form method="post" inline className="gas-price-form ">
<Form inline className="gas-price-form">
<FormGroup key="gasPrice">
<Label for="gasPrice" className="mr-2">Gas Price (in GWei)(optional)</Label>
<Input name="gasPrice" id="gasPrice" placeholder="uint256"
<Label for="gasPrice" className="mr-2">Gas Price (in GWei) (optional)</Label>
<Input name="gasPrice"
id="gasPrice"
placeholder="uint256"
value={this.state.inputs.gasPrice || ''}
onChange={(e) => this.handleChange(e, 'gasPrice')}/>
onChange={(e) => this.handleChange(e, 'gasPrice')}
onKeyPress={(e) => this.handleKeyPress(e)}/>
<Button onClick={(e) => this.autoSetGasPrice(e)}
title="Automatically set the gas price to what is currently in the estimator (default: safe low)">
Auto-set
@ -165,24 +207,40 @@ class ContractFunction extends Component {
</Row>
</Col>
}
<Button className="contract-function-button float-right" color="primary" disabled={this.callDisabled()}
onClick={(e) => this.handleCall(e)}>
<Button
className={classnames({
'btn-sm': true,
'contract-function-button': true,
'contract-function-button-with-margin-top': this.state.gasPriceCollapse,
'float-right': true})}
color="primary"
disabled={this.callDisabled()}
onClick={(e) => this.handleCall(e)}>
{this.buttonTitle()}
</Button>
<div className="clearfix"/>
</CardBody>
{this.props.contractFunctions && this.props.contractFunctions.length > 0 && <CardFooter>
<ListGroup>
{this.props.contractFunctions.map(contractFunction => (
<ListGroupItem key={contractFunction.result}>
{contractFunction.inputs.length > 0 && <p>Input(s): {contractFunction.inputs.join(', ')}</p>}
<strong>Result: {JSON.stringify(contractFunction.result)}</strong>
{this.props.contractFunctions.map((contractFunction, idx) => (
<ListGroupItem key={idx}>
{contractFunction.inputs.length > 0 &&
<p>Input(s): &nbsp;
<span className="contract-function-input-values">
{contractFunction.inputs.join(', ')}
</span>
</p>}
Result: &nbsp;
<strong>
<span className="contract-function-result">
{JSON.stringify(contractFunction.result).slice(1, -1)}
</span>
</strong>
</ListGroupItem>
))}
</ListGroup>
</CardFooter>}
</Collapse>}
</Collapse>
</Card>
);
}
@ -217,7 +275,8 @@ const ContractOverview = (props) => {
.filter((method) => {
return props.onlyConstructor ? method.type === 'constructor' : method.type !== 'constructor';
})
.map(method => <ContractFunction key={method.name} contractName={contract.className}
.map(method => <ContractFunction key={method.name}
contractName={contract.className}
method={method}
contractFunctions={filterContractFunctions(props.contractFunctions, contract.className, method.name)}
postContractFunction={props.postContractFunction}/>)}
@ -237,4 +296,3 @@ ContractOverview.defaultProps = {
};
export default ContractOverview;

View File

@ -0,0 +1,67 @@
.contract-function-badge {
cursor: default;
code {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 13.1333px !important;
font-weight: 700 !important;
}
.code-black {
color: black !important;
}
.code-white {
color: white !important;
}
}
.contract-function-button {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 13.1333px !important;
font-weight: 700 !important;
}
.contract-function-button-with-margin-top {
margin-top: 8px;
}
.contract-function-container {
.card-header.closed {
border-bottom: none;
border-radius: 0.25rem;
}
.card-title {
align-items: center;
display: flex;
margin-bottom: auto;
}
.card-title div {
margin-left: auto;
}
.gas-price-form {
#gasPrice {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border: 0;
box-shadow: none !important;
}
}
}
.contract-function-input {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
margin: 0 5px;
}
.contract-function-input-values, .contract-function-result, .contract-function-signature {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
}

View File

@ -1,151 +0,0 @@
import PropTypes from "prop-types";
import React from 'react';
import {Row, Col, Table, FormGroup, Label, Input, Form} from 'reactstrap';
import DebugButton from './DebugButton'
const TX_STATES = {Success: '0x1', Fail: '0x0', Any: ''};
const EVENT = 'event';
const FUNCTION = 'function';
const CONSTRUCTOR = 'constructor';
const PURE = 'pure';
const VIEW = 'view';
class ContractTransactions extends React.Component {
constructor(props) {
super(props);
this.state = {method: '', event: '', status: TX_STATES['Any']};
}
getMethods() {
if (!this.props.contract.abiDefinition) {
return [];
}
return this.props.contract.abiDefinition.filter(method => method.mutability !== VIEW && method.mutability !== PURE && method.constant !== true && (method.type === FUNCTION || method.type === CONSTRUCTOR));
}
getEvents() {
if (!this.props.contract.abiDefinition) {
return [];
}
return this.props.contract.abiDefinition.filter(method => method.type === EVENT);
}
updateState(key, value) {
this.setState({[key]: value});
}
dataToDisplay() {
return this.props.contractLogs.map(contractLog => {
const events = this.props.contractEvents
.filter(contractEvent => contractEvent.transactionHash === contractLog.transactionHash)
.map(contractEvent => contractEvent.event);
contractLog.events = events;
return contractLog;
}).filter(contractLog => {
if (this.state.status && contractLog.status !== this.state.status) {
return false;
}
if (this.state.method || this.state.event) {
return this.state.method === contractLog.functionName || contractLog.events.includes(this.state.event);
}
return true;
});
}
render() {
return (
<React.Fragment>
<Form>
<Row>
<Col md={6}>
<FormGroup>
<Label htmlFor="functions">Functions</Label>
<Input type="select" name="functions" id="functions" onChange={(event) => this.updateState('method', event.target.value)} value={this.state.method}>
<option value=""/>
{this.getMethods().map((method, index) => <option value={method.name} key={index}>{method.type === CONSTRUCTOR ? CONSTRUCTOR : method.name}</option>)}
</Input>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup>
<Label htmlFor="events">Events</Label>
<Input type="select" name="events" id="events" onChange={(event) => this.updateState('event', event.target.value)} value={this.state.event}>
<option value=""/>
{this.getEvents().map((event, index) => <option value={event.name} key={index}>{event.name}</option>)}
</Input>
</FormGroup>
</Col>
<Col>
<FormGroup row>
<Col md="3">
<Label>Tx Status</Label>
</Col>
<Col md="9">
{Object.keys(TX_STATES).map(key => (
<FormGroup key={key} check inline>
<Input className="form-check-input"
type="radio"
id={key}
name={key}
value={TX_STATES[key]}
onChange={(event) => this.updateState('status', event.target.value)}
checked={TX_STATES[key] === this.state.status} />
<Label check className="form-check-label" htmlFor={key}>{key}</Label>
</FormGroup>
))}
</Col>
</FormGroup>
</Col>
</Row>
</Form>
<Row>
<Col className="overflow-auto">
<Table>
<thead>
<tr>
<th/>
<th>Call</th>
<th>Events</th>
<th>Gas Used</th>
<th>Block number</th>
<th>Status</th>
<th>Transaction hash</th>
</tr>
</thead>
<tbody>
{
this.dataToDisplay().map((log, index) => {
return (
<tr key={'log-' + index}>
<td><DebugButton forceDebuggable transaction={{hash: log.transactionHash}}/></td>
<td>{`${log.name}.${log.functionName}(${log.paramString})`}</td>
<td>{log.events.join(', ')}</td>
<td>{log.gasUsed}</td>
<td>{log.blockNumber}</td>
<td>{log.status}</td>
<td>{log.transactionHash}</td>
</tr>
);
})
}
</tbody>
</Table>
</Col>
</Row>
</React.Fragment>
);
}
}
ContractTransactions.propTypes = {
contractLogs: PropTypes.array,
contractEvents: PropTypes.array,
contract: PropTypes.object.isRequired
};
export default ContractTransactions;

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Button} from "reactstrap";
@ -13,12 +12,20 @@ class DebugButton extends React.Component {
isDebuggable() {
return this.props.forceDebuggable ||
(this.props.contracts && this.props.contracts.find(contract => contract.address === this.props.transaction.to));
(!this.props.transaction.isCall &&
!this.props.transaction.isConstructor &&
this.props.contracts &&
this.props.contracts.find(contract => {
const address = this.props.transaction.to || this.props.transaction.address;
return contract.address &&
address &&
(contract.address.toLowerCase() === address.toLowerCase());
}));
}
render() {
if (!this.isDebuggable()) {
return <React.Fragment />
return <React.Fragment/>;
}
return (
<Button color="primary" onClick={() => this.onClick()}>
@ -42,4 +49,4 @@ DebugButton.propTypes = {
contracts: PropTypes.arrayOf(PropTypes.object)
};
export default withRouter(DebugButton);
export default withRouter(DebugButton);

View File

@ -21,7 +21,7 @@ export const TextEditorToolbarTabs = {
Interact: { label: 'Interact', icon: 'bolt' },
Details: { label: 'Details', icon: 'info-circle' },
Debugger: { label: 'Debugger', icon: 'bug' },
Transactions: { label: 'Transactions', icon: 'list-alt' },
Log: { label: 'Log', icon: 'list-alt' },
Browser: { label: 'Browser', icon: 'eye' }
};

View File

@ -3,11 +3,11 @@ import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {contractEvents as contractEventsAction, contractLogs as contractLogsAction, listenToContractLogs, listenToContractEvents} from '../actions';
import ContractTransactions from '../components/ContractTransactions';
import ContractLog from '../components/ContractLog';
import DataWrapper from "../components/DataWrapper";
import {getContractLogsByContract, getContractEventsByContract} from "../reducers/selectors";
class ContractTransactionsContainer extends Component {
class ContractLogContainer extends Component {
componentDidMount() {
if (this.props.contractLogs.length === 0) {
this.props.listenToContractLogs();
@ -23,9 +23,9 @@ class ContractTransactionsContainer extends Component {
render() {
return (
<DataWrapper shouldRender={this.props.contractLogs !== undefined } {...this.props} render={() => (
<ContractTransactions contractLogs={this.props.contractLogs}
contractEvents={this.props.contractEvents}
contract={this.props.contract}/>
<ContractLog contractLogs={this.props.contractLogs}
contractEvents={this.props.contractEvents}
contract={this.props.contract}/>
)} />
);
}
@ -38,7 +38,7 @@ function mapStateToProps(state, props) {
};
}
ContractTransactionsContainer.propTypes = {
ContractLogContainer.propTypes = {
contract: PropTypes.object,
contractLogs: PropTypes.array,
contractEvents: PropTypes.array,
@ -57,4 +57,4 @@ export default connect(
fetchContractEvents: contractEventsAction.request,
listenToContractEvents: listenToContractEvents
}
)(ContractTransactionsContainer);
)(ContractLogContainer);

View File

@ -261,4 +261,3 @@ export default withRouter(connect(
fetchContracts: contractsAction.request
},
)(EditorContainer));

View File

@ -7,7 +7,7 @@ import classNames from 'classnames';
import Preview from '../components/Preview';
import {getContractsByPath, getPreviewUrl} from "../reducers/selectors";
import ContractDetail from '../components/ContractDetail';
import ContractTransactionsContainer from './ContractTransactionsContainer';
import ContractLogContainer from './ContractLogContainer';
import ContractOverviewContainer from '../containers/ContractOverviewContainer';
import ContractDebuggerContainer from '../containers/ContractDebuggerContainer';
import { TextEditorToolbarTabs } from '../components/TextEditorToolbar';
@ -25,11 +25,11 @@ class TextEditorAsideContainer extends Component {
<ContractDetail key={index} contract={contract}/>
</React.Fragment>
);
case TextEditorToolbarTabs.Transactions.label:
case TextEditorToolbarTabs.Log.label:
return (
<React.Fragment>
<h2>{contract.className} - Transactions</h2>
<ContractTransactionsContainer key={index} contract={contract}/>
<h2>{contract.className} - Log</h2>
<ContractLogContainer key={index} contract={contract}/>
</React.Fragment>
);
case TextEditorToolbarTabs.Interact.label:

View File

@ -32,6 +32,7 @@
"webpackDone": "webpackDone"
},
"blockchain": {
"call": "eth_call",
"clients": {
"geth": "geth",
"parity": "parity"

View File

@ -66,6 +66,14 @@ class Proxy {
if (Object.values(METHODS_TO_MODIFY).includes(req.method)) {
this.toModifyPayloads[req.id] = req.method;
}
if (req.method === constants.blockchain.call) {
this.commList[req.id] = {
kind: 'call',
type: 'contract-log',
address: req.params[0].to,
data: req.params[0].data
};
}
if (req.method === constants.blockchain.transactionMethods.eth_sendTransaction) {
this.commList[req.id] = {
type: 'contract-log',
@ -97,10 +105,16 @@ class Proxy {
if (!res) return;
try {
if (this.commList[res.id]) {
this.commList[res.id].transactionHash = res.result;
this.transactions[res.result] = {
commListId: res.id
};
if (this.commList[res.id].kind === 'call') {
this.commList[res.id].result = res.result;
this.sendIpcMessage(this.commList[res.id]);
delete this.commList[res.id];
} else {
this.commList[res.id].transactionHash = res.result;
this.transactions[res.result] = {
commListId: res.id
};
}
} else if (this.receipts[res.id] && res.result && res.result.blockNumber) {
// TODO find out why commList[receipts[res.id]] is sometimes not defined
if (!this.commList[this.receipts[res.id]]) {
@ -109,17 +123,7 @@ class Proxy {
this.commList[this.receipts[res.id]].blockNumber = res.result.blockNumber;
this.commList[this.receipts[res.id]].gasUsed = res.result.gasUsed;
this.commList[this.receipts[res.id]].status = res.result.status;
if (this.ipc.connected && !this.ipc.connecting) {
this.ipc.request('log', this.commList[this.receipts[res.id]]);
} else {
const message = this.commList[this.receipts[res.id]];
this.ipc.connecting = true;
this.ipc.connect(() => {
this.ipc.connecting = false;
this.ipc.request('log', message);
});
}
this.sendIpcMessage(this.commList[this.receipts[res.id]]);
delete this.transactions[this.commList[this.receipts[res.id]].transactionHash];
delete this.commList[this.receipts[res.id]];
delete this.receipts[res.id];
@ -131,6 +135,18 @@ class Proxy {
}
}
sendIpcMessage(message) {
if (this.ipc.connected && !this.ipc.connecting) {
this.ipc.request('log', message);
} else {
this.ipc.connecting = true;
this.ipc.connect(() => {
this.ipc.connecting = false;
this.ipc.request('log', message);
});
}
}
async serve(host, port, ws, origin, accounts, certOptions={}) {
const start = Date.now();
await (function waitOnTarget() {

View File

@ -65,7 +65,7 @@ class ConsoleListener {
functionName: 'constructor',
paramString: '',
address: receipt.contractAddress,
status: receipt.status,
status: receipt.status ? '0x1' : '0x0',
gasUsed: receipt.gasUsed,
blockNumber: receipt.blockNumber,
transactionHash: receipt.transactionHash
@ -78,14 +78,13 @@ class ConsoleListener {
}
_onIpcLogRequest(request) {
if (request.type !== 'contract-log') {
return this.logger.info(JSON.stringify(request));
}
if (!this.contractsDeployed) return;
let {address, data, transactionHash, blockNumber, gasUsed, status} = request;
const {address, data} = request;
const contract = this.addressToContract[address];
if (!contract) {
@ -94,16 +93,25 @@ class ConsoleListener {
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
});
}
const {name, silent} = contract;
if (silent && !this.outputDone) {
return;
}
const {functionName, paramString} = getTransactionParams(contract, data);
if (request.kind === 'call') {
const log = Object.assign({}, request, {name, functionName, paramString});
log.status = '0x1';
return this.events.emit('contracts:log', log);
}
let {transactionHash, blockNumber, gasUsed, status} = request;
gasUsed = utils.hexToNumber(gasUsed);
blockNumber = utils.hexToNumber(blockNumber);
const log = Object.assign({}, request, {name, functionName, paramString, gasUsed, blockNumber});
this.events.emit('contracts:log', log);
this.logger.info(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`);
this.events.emit('blockchain:tx', {name: name, functionName: functionName, paramString: paramString, transactionHash: transactionHash, gasUsed: gasUsed, blockNumber: blockNumber, status: status});

View File

@ -61,6 +61,18 @@ class ContractsManager {
});
self.events.on("deploy:contract:deployed", (_contract) => {
const contract = self.contracts[_contract.className];
if (contract) {
if (!_contract.address && _contract.deployedAddress) {
_contract.address = _contract.deployedAddress;
}
if (!contract.address && _contract.address) {
contract.address = _contract.address;
}
if (!contract.deployedAddress && _contract.deployedAddress) {
contract.deployedAddress = _contract.deployedAddress;
}
}
self.events.emit('contractsState', self.contractsState());
});
@ -140,24 +152,10 @@ class ContractsManager {
if(funcCall === 'call') {
contractLog.status = '0x1';
self.events.emit('contracts:log', contractLog);
return res.send({result});
}
self.events.request("blockchain:get", web3 => {
web3.eth.getTransaction(result, (err, tx) => {
contractLog = Object.assign(contractLog, {
data: tx.input,
status: '0x1',
gasUsed: tx.gas,
blockNumber: tx.blockNumber,
transactionHash: tx.hash
});
self.events.emit('contracts:log', contractLog);
res.send({result});
});
});
res.send({result});
});
} catch (e) {
if (funcCall === 'call' && e.message === constants.blockchain.gasAllowanceError) {